在扩展静态和实例类成员之间的类型时

时间:2018-08-18 01:41:17

标签: reactjs typescript

在下面的示例中,我创建一个“ bundle”,其中包含一个react上下文和一个react类,该类为该上下文提供了一个提供程序。此“捆绑包”接受一些参数,在这种情况下,这些参数表示为要被覆盖的(抽象)类属性。

(还可以使用更多的功能模式,我愿意接受允许我最大程度地利用推理和扩展的任何模式)

上下文应为“ static-y”属性,但其类型取决于实例属性。我一直很难从类的“实例侧”拥有的类型中推断出静态上下文类型。

我最终遇到了如下所述的黑客攻击,派生类需要接收Self类型的参数,并且必须从Derived.prototype.getContext()访问上下文。尽管此方法有效,但它很hacky,我想知道是否可以避免。

type State<EntityType> = { hey: EntityType; ready: boolean }
abstract class CrudDetail<
  EntityType extends Object,
  Self extends CrudDetail<any, any>
> extends React.PureComponent<{}, State<EntityType>> {
  state = {
    ready: false,
    hey: {} as EntityType,
  }

  // exampĺes of possible parameters
  abstract entityName: string

  // this is meant to be overriden 
  // the context is tied on the instance state
  getContextValue() {
    return {
      hey: this.state.hey,
    }
  }

  // the type of this property depends on instance.getContextValue()
  // how can i access it from here?
  private static context = React.createContext({})

  // only instance members can access type parameters
  // so I came up with an instance member and a Self
  // type parameter to access the derived class type
  // This is kinda ugly and is the thing I'd like to avoid.
  getContext() {
    return (this.constructor as any).context as React.Context<ReturnType<Self['getContextValue']>>
  }

  render() {
    return (
      <CrudDetail.context.Provider value={this.getContextValue()}>
        {this.props.children}
      </CrudDetail.context.Provider>
    )
  }
}

消费

type Person = { name: string }

class PersonCrud extends CrudDetail<Person, PersonCrud> {
  state = {
    ...super.state,
    name: "",
  }

  getContextValue = () => {
    let that = this
    return extend(super.getContextValue(), {
      name: this.state.name,
      heyreadyName() {
        return that.state.ready && this.hey && this.name
      },
    })
  }
}

const Test2 = () => {
  const Ctx = PersonCrud.prototype.getContext()
  return (
    <PersonCrud>
      <Ctx.Consumer>{ctx => <div>{ctx.heyreadyName()}</div>}</Ctx.Consumer>
    </PersonCrud>
  )
}

({extend来自Extending the inferred "this"

重申,我是否可以:

  • 建立基类需要实现的参数协定(通过类,闭包或其他方式)
  • 使合同实施具有类型安全性
  • 正确输入了消费环境<Context.Consumer>
  • 变干。我的方法仍然需要提供Self类型参数和“ this.constructor” hack

(我粘贴了一个codeandbox,但不幸的是,它仍然停留在TS 2.7上...)

1 个答案:

答案 0 :(得分:0)

以下代码使用here中的技巧为我编译。我不确定您是要创建一个单一的上下文并将其转换为许多不同的类型。我已经更改了代码以使每个子类具有一个上下文,并且我确定您可以根据需要将其改回。

function extend<TBase, TCurrent>(base: TBase, current: TCurrent & ThisType<TCurrent & TBase>): TCurrent & TBase {
  return Object.assign(current, base);
}

////////////////////

import * as React from "react";

type State<EntityType> = { hey: EntityType; ready: boolean }

type CrudDetailConstructor = typeof CrudDetail & {new (...args: any[]): any};

abstract class CrudDetail<
  EntityType extends Object,
> extends React.PureComponent<{}, State<EntityType>> {
  state = {
    ready: false,
    hey: {} as EntityType,
  }

  // exampĺes of possible parameters
  abstract entityName: string

  // this is meant to be overriden 
  // the context is tied on the instance state
  getContextValue() {
    return {
      hey: this.state.hey,
    }
  }

  // Declare here, although this will actually exist on the static side of each
  // concrete subclass.
  private static context?: React.Context<any>;

  public ["constructor"]: CrudDetailConstructor;

  static getContext<C extends CrudDetailConstructor>(this: C):
    React.Context<ReturnType<InstanceType<C>["getContextValue"]>> {
    return this.context || (this.context = React.createContext({} /*UNSOUND*/));
  }

  render() {
    let context = this.constructor.getContext();
    return (
      <context.Provider value={this.getContextValue()}>
        {this.props.children}
      </context.Provider>
    )
  }
}

////////////////////

type Person = { name: string }

class PersonCrud extends CrudDetail<Person> {
  entityName = "Person";
  state = {
    ...(this as CrudDetail<Person>).state,
    name: "",
  }

  getContextValue = () => {
    let that = this
    return extend(super.getContextValue(), {
      name: this.state.name,
      heyreadyName() {
        return that.state.ready && this.hey && this.name
      },
    })
  }
}

const Test2 = () => {
  const Ctx = PersonCrud.getContext()
  return (
    <PersonCrud>
      <Ctx.Consumer>{ctx => <div>{ctx.heyreadyName()}</div>}</Ctx.Consumer>
    </PersonCrud>
  )
}