在类中更改“ this”的类型

时间:2018-08-20 22:01:44

标签: typescript typescript-typings

我遇到以下问题,我输入的函数“ Foo”如下所示。

function Foo(...cons: any[]) {
    return function (this: {inject: any}) {
            return (bar: any) => {
                this.onChange  // notice here how i have access to onChange
                return reactC;
        }
    }
}

以上代码几乎是我想要的,基本上我希望添加的“ this”类型可以在“ bar”内部使用(忽略“ any”是我可以轻松获得的那些类型,我只希望正确键入“ this”。

实现如下。

export const TestImplementation: typeof Foo = Foo([Input])()(class TestClass {
    testFunction() {
        //this.inject
    }
});

//“ void”类型的“ this”上下文不能分配给“ any”类型的“ this”方法

和'this.inject'不存在。

不要认为我的实现是正确的,而是在寻找一种方法来改变类中“ this”的类型 https://preview.tinyurl.com/ycc9c4l5 <有此问题的安全链接到游乐场

2 个答案:

答案 0 :(得分:1)

从这个问题中并不清楚要实现什么,但是根据我的理解,您希望this的类型可以通过Foo函数针对作为参数传递的类进行修改。

除非通过继承,否则您不能为类的所有成员更改this的类型。但是,您可以使用this更改传递给函数的对象文字的ThisType<T>类型。 ThisType<T>是编译器的特殊标记(这很神奇),对于分配给T的任何对象文字,编译器将使用this作为ThisType<T>的类型。 / p>

function Foo<T>(...cons: T[]) {
    // Infer the type of the object literal passed in in TClass
    // use ThisType to let the compiler know this should contain 
    // all memebers of the object literal plus a merger of all memebrs passed in to cons  
    return function <TClass>(classMember: TClass & ThisType<TClass & UnionToIntersection<T>>) : TClass & UnionToIntersection<T> {
        return null as any; // implementation   
    }
}

type UnionToIntersection<U> = 
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never



class FakeClass {
    inject: string; // ignore this property doesn't matter
}

const TestFoo = Foo(new FakeClass())({
    TestTypes() {
        this.inject // this has the members of FakeClass
    }
})

TestFoo.TestTypes();

注意:此解决方案需要noImplicitThis: true才能起作用。另外UnionToIntersection是从here

提取的

如果要使用类,另一种选择是集中于继承部分。我们可以为您传递给Foo的类创建一个类型来充当伪基类。您不能直接传递该类,您将需要传递一个Foo调用的函数,该函数将带有用于新类的基类。

function Foo<T>(...cons: T[]) {  
    return function <TClass>(classMember: (base: Constructor<UnionToIntersection<T>>) => Constructor<TClass>) : Constructor<TClass> {
        return classMember(class {
            constructor() {
                Object.assign(this, ...cons);
            }
        } as any)   
    }
}

type UnionToIntersection<U> = 
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type Constructor<T> = new (...args: any[]) => T

class FakeClass {
    inject: string;
}

const TestFoo = Foo(new FakeClass())(b=> class extends b {
    TestTypes() {
        this.inject
    }
})

new TestFoo().TestTypes(); 

修改

根据我们在评论中讨论的内容,您希望结果成为一个类,并让其他类充当mixin。我们可以修改解决方案1来做到这一点

function Foo<T extends Constructor<any>[]>(cons: T) {  
    return function <TClass>(classMember: TClass & ThisType<TClass & MergeConstructorTypes<T>>): Constructor<TClass & MergeConstructorTypes<T>> {
        // Naive implementation, you might want to do better here 
        var cls = class { 
            constructor() {
                cons.forEach(c => c.apply(this));
            }
        };
        cons.forEach(c => Object.assign(cls.prototype, c.prototype));
        Object.assign(cls.prototype, classMember);
        return cls as any; 
    }
}

type Constructor<T> = new (...args: any[]) => T

type UnionToIntersection<U> = 
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type MergeConstructorTypes<T extends Constructor<any>[]> =
    UnionToIntersection<InstanceType<T[number]>>;

class FakeClassA {
    inject: string = "A"; // ignore this property doesn't matter
    logA() {
        console.log(this.inject);
    }
}

class FakeClassB {
    inject2: string = "B"; // ignore this property doesn't matter
    logB() {
        console.log(this.inject2);
    }
}


const TestFoo = Foo([FakeClassA, FakeClassB])({
    TestTypes() {
        console.log(this.inject) 
        console.log(this.inject2)
        this.logA();
        this.logB();
    }
})

new TestFoo().TestTypes();

注意在这种情况下,伪造的基类解决方案也可以使用,但是您对第一个解决方案表示了兴趣,因此我在这种情况下进行了举例说明。另外,上述解决方案使用3.0功能,并且再次需要noImplicitThis:true

希望它会有所帮助:)

答案 1 :(得分:0)

您好,将来所有看到这一点的人都应该感谢Titian早些时候有关“ this”的回答,因为它只能来自继承或操作ThisType,这对我来说是不可行的。我已经建好了。

export class InputMixin {
    state = {
        value: "asdsasads"
    }
    onChange(this: React.Component<any>, e: React.ChangeEvent<HTMLInputElement>) {
        e.persist();
        console.log(this, "THIS BINDING");
        this.setState((prevState: any) => ({value: e.target.value}))
    }
}

export class TestMixin {
    state = {
        myvalue: "myvalue"
    }
}

export type Constructor<T = {}> = new (...args: any[]) => T;
function Mixin<T extends Constructor<any>[]>(constructors: T): Constructor<MergeConstructorTypes<T>> {
    var cls = class {
        state = {
        }
        constructor() {
            constructors.forEach(c => {
                const oldState = this.state;
                c.apply(this);
                this.state = Object.assign({}, this.state, oldState);
            });
        }
    } as any;
    constructors.forEach((c: any) => {
        Object.assign(cls.prototype, c.prototype);
    });
    return cls as any;
}
type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type MergeConstructorTypes<T extends Constructor<any>[]> =
    UnionToIntersection<InstanceType<T[number]>>;


interface IProps {
    bleet: string;
}
export class CounterTest extends Mixin([TestMixin, InputMixin, React.Component as React.ComponentClass<IProps, any>]) {
    constructor(props: any) {
        super(props);
        this.state = {
            ...this.state,
            newState: ""
        };
        this.onChange = this.onChange.bind(this);
    }

    render() {
        return <Input value={this.state.value} onChange={this.onChange}/>
    }

现在,您可以定义独立于组件的“ mixins”并本质上使用它们的代码,这将停止无限重复真正的基本功能,该功能来自于由父级控制所有事物的原理。您不再需要每个需要子级输入的父级上的onChange(),只需将InputMixin混合在一起,然后为您键入所有内容,而所有内容都已经存在。通过简单地从原型中吸取所有内容并将其重新绑定到另一个类。

此外,它将所有“状态”合并到一个最终状态中,以便多个mixin可以实现“ state”,并且最终类将获得正确键入的所有mixins的状态。

感谢Gitter,感谢Titian。