Reputation: 2947
I have an es6 class User
and a global function map()
given below:
class User {
constructor(public name: string) {}
}
const map = <T, R>(project: (value: T) => R) => {}
Instead of writing the following:
map((value) => new User(value))
I want to (somehow) write something like:
map(new User)
I am not sure if this is possible or not.
Upvotes: 11
Views: 905
Reputation: 2659
If you want a generic solution, and can't modify the classes, you could use a higher-order function:
function make(klass) {
return (...params) => new klass(...params);
}
// this:
map((value) => new User(value));
// becomes this:
map(make(User));
Upvotes: 1
Reputation: 3449
This is a well known "problem" in the oop vs fp debate in JS - class constructors vs function constructors
since es6 classes need the new
operator, it is impossible to write something like map(new User)
you need a wrapper around the class constructor that creates instances via a function call. IMO, @baboo's approach is way to go
class MyClass {
// ...
static create(...args) {
return new MyClass(...args)
}
}
const a = new MyClass('hello', [])
const b = MyClass.create('world', 123])
you can read more about the problems of new
here.
also, checkout daggy - Library for creating tagged constructors
Upvotes: 2
Reputation: 25790
The pattern you are describing is called a scope-safe constructor. It can be implemented by overloading a constructor so it works with and without the new
keyword.
interface User {
name: string;
}
interface UserConstructor {
new (name: string): User;
(name: string): User;
}
The same trick is used for global objects like Array
or Date
.
We need to recognize whether the new
keyword was used:
const User = function (this: User | void, name: string): User {
if (!(this instanceof User)) {
return new User(name);
}
this.name = name;
return this;
} as UserConstructor;
Your class has just become new
-agnostic.
console.log(
new User('Bob'),
User('Alice'),
);
Which enabled us to write:
['Alice', 'Bob'].map(User); // $ExpectType User[]
Upvotes: 2
Reputation: 386578
You could add a check with new.target
if the function is called without new and call then function with new
.
function Person(name) {
if (!new.target) return new Person(...arguments);
this.name = name;
}
var names = ['Jane', 'Dan', 'Grace', 'Paul'],
instances = names.map(Person);
console.log(instances);
Upvotes: 2
Reputation: 4258
You can create a static function in your class that takes the value param and returns a new User:
class User {
static createUser(value) {
return new User(value)
}
}
And then use:
map(User.createUser)
Upvotes: 6
Reputation: 249636
You can't do it directly. If you control the target function (ie it's not the stock map
function) you can have it take a constructor instead of the function:
class User { constructor(private id: number) { }}
function map<TIn, T>(value: TIn, ctor: new (a: TIn) => T): T{
return new ctor(value)
}
map(10, User)
Another more flexible solution is to use a helper function that transform the constructor into the desired function, although it's not much shorter then the original version:
class User { constructor(private id: number) { }}
function ctor<TIn, T>(ctor: new (a: TIn) => T): (value: TIn) => T{
return value => new ctor(value)
}
[10, 11].map(ctor(User));
Upvotes: 5