Reactjs 在扩展时在静态和实例类成员之间通信类型

Reactjs 在扩展时在静态和实例类成员之间通信类型,reactjs,typescript,Reactjs,Typescript,在下面的示例中,我创建了一个“bundle”,其中包含一个react上下文和一个react类,react类为该上下文提供一个提供者。此“bundle”接受一些参数,在本例中表示为要重写的(抽象)类属性 (也可以使用更多的功能模式,我愿意接受任何模式允许我最大限度地利用推理和扩展) 上下文应该是“static-y”属性,但其类型取决于实例属性。我一直很难从类的“实例端”拥有的类型推断静态上下文类型 我最后遇到了一个如下所述的黑客攻击,其中派生类需要接收一个Self类型参数,并且必须从derived

在下面的示例中,我创建了一个“bundle”,其中包含一个react上下文和一个react类,react类为该上下文提供一个提供者。此“bundle”接受一些参数,在本例中表示为要重写的(抽象)类属性

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

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

我最后遇到了一个如下所述的黑客攻击,其中派生类需要接收一个
Self
类型参数,并且必须从
derived.prototype.getContext()
访问上下文。虽然这是可行的,但它是令人讨厌的,我想知道我是否可以避免它

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 State={hey:EntityType;ready:boolean}
抽象类CrudDetail<
EntityType扩展对象,
自扩展(CrudDetail)

重申,我是否可以:

  • 建立基类需要实现的参数契约(通过类、闭包或其他方式)
  • 契约实现是否是类型安全的
  • 正确键入消费上下文
  • 保持干燥。我的方法仍然需要提供
    Self
    类型参数和“this.constructor”hack

(我会粘贴一个codesandbox,但它仍然停留在TS 2.7上…

下面的代码为我编译,使用的技巧来自。我不确定你的意思是制作一个单一的上下文并将其转换为许多不同的类型;我已经更改了代码,使每个子类都有一个上下文,如果您愿意,我相信您可以将其更改回去

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>
  )
}
函数扩展(基:TBase,当前:TCurrent&ThisType):TCurrent&TBase{
返回Object.assign(当前、基本);
}
////////////////////
从“React”导入*作为React;
类型状态={hey:EntityType;ready:boolean}
type CrudDetailConstructor=typeof CrudDetail&{new(…args:any[]):any};
抽象类CrudDetail<
EntityType扩展对象,
>扩展React.PureComponent{
状态={
就绪:错误,
嘿:{}作为EntityType,
}
//可能参数的示例
抽象实体名:字符串
//这意味着要被覆盖
//上下文绑定在实例状态上
getContextValue(){
返回{
嘿:这个州,嘿,
}
}
//在这里声明,尽管这实际上存在于每个
//具体的子类。
私有静态上下文?:React.context;
public[“constructor”]:CrudDetailConstructor;
静态getContext(this:C):
反应。语境{
返回this.context | |(this.context=React.createContext({}/*UNSOUND*/);
}
render(){
让context=this.constructor.getContext();
返回(
{this.props.children}
)
}
}
////////////////////
类型Person={name:string}
类PersonCrud扩展了CrudDetail{
entityName=“Person”;
状态={
…(这是CrudDetail)声明,
姓名:“,
}
getContextValue=()=>{
让它=这个
返回extend(super.getContextValue(){
名称:this.state.name,
heyreadyName(){
返回that.state.ready&&this.hey&&this.name
},
})
}
}
常量Test2=()=>{
const Ctx=PersonCrud.getContext()
返回(
{ctx=>{ctx.heyreadyName()}
)
}

我完全不明白您为什么要使用上下文;考虑到您必须在
Test2
中手动向下传递上下文,它看起来并不比使用普通道具简单。你能举一个不那么琐碎的例子吗?我不是在质疑上下文的使用,但答案是标准的:消费者可以是任何层次的人,通过使用它,我避免了在每次道具/上下文签名更改时都需要几次手动重构。
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>
  )
}