Reactjs 在TypeScript中使用高阶组件在使用时省略React属性

Reactjs 在TypeScript中使用高阶组件在使用时省略React属性,reactjs,typescript,Reactjs,Typescript,我试图在TypeScript中编写一个高阶组件,它接受某个React组件类,包装它,并返回一个省略了其中一个声明属性的类型。这就是我尝试过的: interface MyProps { hello: string; world: number; } interface MyState { } function HocThatMagicallyProvidesProperty<P, S, T extends {new(...args:any[]): React.Component&

我试图在TypeScript中编写一个高阶组件,它接受某个React组件类,包装它,并返回一个省略了其中一个声明属性的类型。这就是我尝试过的:

interface MyProps {
  hello: string;
  world: number;
}

interface MyState { }

function HocThatMagicallyProvidesProperty<P, S, T extends {new(...args:any[]): React.Component<Exclude<P, "world">, S>}>(constructor: T): T {
  throw new Error('test');
}

const MyComponent = HocThatMagicallyProvidesProperty(class MyComponent extends React.Component<MyProps, MyState> {
  constructor(props: MyProps, context: any) {
    super(props, context);
  }

  public render() {
    return <div></div>;
  }
})

function usingThatComponent() {
  return (
    <MyComponent
      hello="test"
    />
  );
}
接口MyProps{
你好:字符串;
世界:数字;
}
接口MyState{}
函数hocthatmagicallyprovideproperty(构造函数:T):T{
抛出新错误(“测试”);
}
const MyComponent=hocthatmagicallyprovideproperty(类MyComponent扩展React.Component{
构造函数(props:MyProps,context:any){
超级(道具、背景);
}
公共渲染(){
返回;
}
})
使用ATComponent()的函数{
返回(
);
}
但是,在使用该组件时,我得到错误:

类型“{hello:string;}”不可分配给类型“IntrinsicatAttributes&IntrinsicClassAttributes&Readonly…”。 类型“{hello:string;}”不可分配给类型“Readonly”。 类型“{hello:string;}”中缺少属性“world”

我也尝试过这个特别声明:

function HocThatMagicallyProvidesProperty<P, S, T extends {new(...args:any[]): React.Component<P, S>}>(constructor: T): {new(...args:any[]): React.Component<Exclude<P, "world">, S>} {
  throw new Error('test');
}
函数hocthatmagicallyprovideproperty(构造函数:T):{new(…args:any[]):React.Component}{
抛出新错误(“测试”);
}
但是,这既不适用于类的使用,也不适用于实际的HOC调用


如何定义高阶组件,以便在使用类时不必传入
world
属性?

如果未正确使用
Exclude
,Exclude将从第一个类型参数中排除第二个类型参数,因此
Exclude='a'
可以使用
Exclude
keyof
Pick
从类型中省略属性:
Pick>

问题的第二部分是,函数调用的类型参数与您预期的不同,在VS代码中悬停,我们在对HOC的调用中看到了以下内容:

function HocThatMagicallyProvidesProperty<{}, {}, typeof MyComponent>(constructor: typeof MyComponent): typeof MyComponent

更简单的版本,允许包装类和函数组件。 为了可读性,有点明确的命名(希望如此)

//旧版本:
//导出类型tpropmit=(
//挑
// )
//变革的结束
//新版:2018-08-25
导出类型tpropmit=(
T扩展任何
?挑选
:从不
)
//变革的结束
使用somepropsset导出命名空间{
导出类型TPropsInject={
世界:数字
}
}
使用somepropsset导出函数<
TPropsOrig扩展为somepropsset.TPropsInject,
TPropsNew=tpropmit
> (
WrappedComponent:React.ComponentType
):React.ComponentType
{
常量injectProps:withSomePropsSet.TPropsInject={
世界:123,
}
return(props:TPropsNew):React.ReactElement=>(
)
}
使用方法:

type TMyProps = {
  hello ?:string
  world  :number
}
class MyCompCls extends React.Component<TMyProps, {}> {
  public render () {
    return (<div>{this.props.hello} {this.props.world}</div>)
  }
}
const MyCompFn = (props :TMyProps) => {
  return (<div>{props.hello} {props.world}</div>)
}

const MyCompClsWrapped = withSomePropsSet(MyCompCls)
const MyCompFnWrapped  = withSomePropsSet(MyCompFn)

const UsagePreWrap = (<>
  <MyCompCls                                 /> {/* INVALID */}
  <MyCompCls  hello = 'test'                 /> {/* INVALID */}
  <MyCompCls  world = {123}                  /> {/* OK */}
  <MyCompCls  hello = 'test'  world = {123}  /> {/* OK */}

  <MyCompFn                                 /> {/* INVALID */}
  <MyCompFn  hello = 'test'                 /> {/* INVALID */}
  <MyCompFn  world = {123}                  /> {/* OK */}
  <MyCompFn  hello = 'test'  world = {123}  /> {/* OK */}
</>)

const UsagePostWrap = (<>
  <MyCompClsWrapped                                /> {/* OK*/}
  <MyCompClsWrapped hello = 'test'                 /> {/* OK */}
  <MyCompClsWrapped world = {123}                  /> {/* INVALID */}
  <MyCompClsWrapped hello = 'test'  world = {123}  /> {/* INVALID */}

  <MyCompFnWrapped                                   /> {/* OK */}
  <MyCompFnWrapped    hello = 'test'                 /> {/* OK */}
  <MyCompFnWrapped    world = {123}                  /> {/* INVALID */}
  <MyCompFnWrapped    hello = 'test'  world = {123}  /> {/* INVALID */}
</>)
类型TMyProps={
喂?:字符串
世界:数字
}
类mycopcls扩展了React.Component{
公开渲染(){
return({this.props.hello}{this.props.world})
}
}
常量mycopfn=(道具:TMyProps)=>{
return({props.hello}{props.world})
}
常量mycopclswrapped=带有某些属性集(mycopcls)
常量mycopfnwrapped=带有某些属性集(mycopfn)
const UsagePreWrap=(
{/*无效*/}
{/*无效*/}
{/*好的*/}
{/*好的*/}
{/*无效*/}
{/*无效*/}
{/*好的*/}
{/*好的*/}
)
const UsagePostWrap=(
{/*好的*/}
{/*好的*/}
{/*无效*/}
{/*无效*/}
{/*好的*/}
{/*好的*/}
{/*无效*/}
{/*无效*/}
)
更新
  • 2018-08-25: 更新了“tpropmit”实用程序类型以更好地处理联合类型 感谢“”对以下github问题的评论:


在TypeScript Gitter上的一些人的帮助下,我们最终得到了以下解决方案:

import { Dissoc } from 'subtractiontype.ts';

export interface ValidationProps {
  onValidationStateChange: (valid: boolean) => void;
}

type OuterProps<P> = Dissoc<P, keyof ValidationProps>;

export function IsValidatableComponent<P extends ValidationProps>(WrappedComponent: React.ComponentClass<P>): React.ComponentType<OuterProps<P>> {
  // ...
}

// usage:

export const RulesetFieldEditor = IsValidatableComponent(class RulesetFieldEditor extends ... {
})
从'减法类型.ts'导入{Dissoc};
导出接口验证道具{
onValidationStateChange:(有效:布尔)=>void;
}
类型OuterProps

=Dissoc; 导出函数IsValidatableComponent(WrappedComponent:React.ComponentClass

):React.ComponentType{ // ... } //用法: 导出常量RulesetFieldEditor=IsValidatableComponent(类RulesetFieldEditor扩展{ })


类型系统中没有识别类装饰符变异的功能。@AluanHaddad我更新了问题,询问如何使用高阶组件,因为这会导致类型系统识别变异的类。是的,这是正确的方法,但我认为您没有正确地使用
排除
type TMyProps = {
  hello ?:string
  world  :number
}
class MyCompCls extends React.Component<TMyProps, {}> {
  public render () {
    return (<div>{this.props.hello} {this.props.world}</div>)
  }
}
const MyCompFn = (props :TMyProps) => {
  return (<div>{props.hello} {props.world}</div>)
}

const MyCompClsWrapped = withSomePropsSet(MyCompCls)
const MyCompFnWrapped  = withSomePropsSet(MyCompFn)

const UsagePreWrap = (<>
  <MyCompCls                                 /> {/* INVALID */}
  <MyCompCls  hello = 'test'                 /> {/* INVALID */}
  <MyCompCls  world = {123}                  /> {/* OK */}
  <MyCompCls  hello = 'test'  world = {123}  /> {/* OK */}

  <MyCompFn                                 /> {/* INVALID */}
  <MyCompFn  hello = 'test'                 /> {/* INVALID */}
  <MyCompFn  world = {123}                  /> {/* OK */}
  <MyCompFn  hello = 'test'  world = {123}  /> {/* OK */}
</>)

const UsagePostWrap = (<>
  <MyCompClsWrapped                                /> {/* OK*/}
  <MyCompClsWrapped hello = 'test'                 /> {/* OK */}
  <MyCompClsWrapped world = {123}                  /> {/* INVALID */}
  <MyCompClsWrapped hello = 'test'  world = {123}  /> {/* INVALID */}

  <MyCompFnWrapped                                   /> {/* OK */}
  <MyCompFnWrapped    hello = 'test'                 /> {/* OK */}
  <MyCompFnWrapped    world = {123}                  /> {/* INVALID */}
  <MyCompFnWrapped    hello = 'test'  world = {123}  /> {/* INVALID */}
</>)
import { Dissoc } from 'subtractiontype.ts';

export interface ValidationProps {
  onValidationStateChange: (valid: boolean) => void;
}

type OuterProps<P> = Dissoc<P, keyof ValidationProps>;

export function IsValidatableComponent<P extends ValidationProps>(WrappedComponent: React.ComponentClass<P>): React.ComponentType<OuterProps<P>> {
  // ...
}

// usage:

export const RulesetFieldEditor = IsValidatableComponent(class RulesetFieldEditor extends ... {
})