当前位置: 首页 > 知识库问答 >
问题:

用TypeScript编写高阶组件

符畅
2023-03-14

我正在用TypeScript编写一个React高阶组件(HOC)。HOC应该比包装组件多接受一个道具,所以我写了以下内容:

type HocProps {
    // Contains the prop my HOC needs
    thingy: number
}
type Component<P> = React.ComponentClass<P> | React.StatelessComponent<P>
interface ComponentDecorator<TChildProps> {
    (component: Component<TChildProps>): Component<HocProps & TChildProps>;
}
const hoc = function<TChildProps>(): (component: Component<TChildProps>) => Component<HocProps & TChildProps) {
    return (Child: Component<TChildProps>) => {
        class MyHOC extends React.Component<HocProps & TChildProps, void> {
            // Implementation skipped for brevity
        }
        return MyHOC;
    }
}
export default hoc;

换句话说,hoc是一个生成实际hoc的函数。这个HOC(我相信)是一个接受组件的函数。因为我事先不知道包裹的组件是什么,所以我使用一个通用类型tchildrops来定义包裹组件的支柱的形状。该函数还返回一个组件。返回的组件接受包装组件的道具(同样,使用通用的tchildrops)和它自身所需的一些道具(typeHocProps)。使用返回的组件时,应提供所有道具(包括HocProps和包装的组件的道具)。

现在,当我尝试使用我的HOC时,我会执行以下操作:

// outside parent component
const WrappedChildComponent = hoc()(ChildComponent);

// inside parent component
render() {
    return <WrappedChild
                thingy={ 42 } 
                // Prop `foo` required by ChildComponent
                foo={ 'bar' } />
}

但是我得到一个打字脚本错误:

TS2339: Property 'foo' does not exist on type 'IntrinsicAttributes & HocProps & {} & { children? ReactNode; }'

在我看来,TypeScript并没有用ChildComponent所需的道具形状取代tchildrops。我怎样才能让TypeScript做到这一点?


共有3个答案

孙弘博
2023-03-14

我找到了一种让它工作的方法:使用提供的type参数调用hoc,如下所示:

import ChildComponent, { Props as ChildComponentProps } from './child';
const WrappedChildComponent = hoc<ChildComponentProps>()(ChildComponent);

但我真的不喜欢它。它要求我导出孩子的道具(我不想这样做),我感觉我在告诉打字脚本它应该能够推断出的东西。

易炳
2023-03-14

如果您要求的是,是否可以定义一个可以向组件添加新道具的HOC,比如说“thingy”,而不修改组件的道具定义以包含“thingy”,我认为这是不可能的。

这是因为在代码中的某个时刻,您最终会得到:

render() {
    return (
        <WrappedComponent thingy={this.props.thingy} {...this.props}/>
    );
}

如果WrappedComponent的props定义中不包含thingy,那么这将始终抛出一个错误。孩子必须知道自己收到了什么。一开始,我想不出将道具传递给不知道该道具的组件的原因。如果没有错误,您将无法在子组件中引用该道具。

我认为诀窍是将HOC定义为围绕子道具的泛型,然后在子接口中显式地包含你的propthingy或任何东西。

interface HocProps {
    // Contains the prop my HOC needs
    thingy: number;
}

const hoc = function<P extends HocProps>(
    WrappedComponent: new () => React.Component<P, any>
) {
    return class MyHOC extends React.Component<P, any> {
        render() {
            return (
                <WrappedComponent {...this.props}/>
            );
        }
    }
}
export default hoc;

// Example child class

// Need to make sure the Child class includes 'thingy' in its props definition or
// this will throw an error below where we assign `const Child = hoc(ChildClass)`
interface ChildClassProps {
    thingy: number;
}

class ChildClass extends React.Component<ChildClassProps, void> {
    render() {
        return (
            <h1>{this.props.thingy}</h1>
        );
    }
}

const Child = hoc(ChildClass);

当然,这个例子并没有起到任何作用。实际上,HOC应该做一些逻辑来为child prop提供一个值。例如,您可能有一个组件,它显示一些重复更新的通用数据。您可以采用不同的方式更新它,并创建HOC来分离该逻辑。

您有一个组件:

interface ChildComponentProps {
    lastUpdated: number;
    data: any;
}

class ChildComponent extends React.Component<ChildComponentProps, void> {
    render() {
        return (
            <div>
                <h1>{this.props.lastUpdated}</h1>
                <p>{JSON.stringify(this.props.data)}</p>
            </div>
        );
    }
}

然后,仅使用setInterval在固定时间间隔更新子组件的示例可能是:

interface AutoUpdateProps {
    lastUpdated: number;
}

export function AutoUpdate<P extends AutoUpdateProps>(
    WrappedComponent: new () => React.Component<P, any>,
    updateInterval: number
) {
    return class extends React.Component<P, any> {
        autoUpdateIntervalId: number;
        lastUpdated: number;

        componentWillMount() {
            this.lastUpdated = 0;
            this.autoUpdateIntervalId = setInterval(() => {
                this.lastUpdated = performance.now();
                this.forceUpdate();
            }, updateInterval);
        }

        componentWillUnMount() {
            clearInterval(this.autoUpdateIntervalId);
        }

        render() {
            return (
                <WrappedComponent lastUpdated={this.lastUpdated} {...this.props}/>
            );
        }
    }
}

然后我们可以创建一个组件,每秒更新一次我们的孩子,如下所示:

const Child = AutoUpdate(ChildComponent, 1000);
韩麒
2023-03-14

派对有点晚了。我喜欢使用省略TypeScript实用程序类型来解决这个问题。链接到留档:https://www.typescriptlang.org/docs/handbook/utility-types.html#omittk

import React, {ComponentType} from 'react';

export interface AdditionalProps {
    additionalProp: string;
}

export function hoc<P extends AdditionalProps>(WrappedComponent: ComponentType<P>) : ComponentType<Omit<P, 'additionalProp'>> {
    const additionalProp = //...
    return props => (
        <WrappedComponent
            additionalProp={additionalProp}
            {...props as any}
        />
    );
}
 类似资料:
  • 本文向大家介绍用Typescript编写的ReactJS组件,包括了用Typescript编写的ReactJS组件的使用技巧和注意事项,需要的朋友参考一下 示例 实际上,您可以像在Facebook的示例中那样在Typescript中使用ReactJS的组件。只需将“ jsx”文件的扩展名替换为“ tsx”: 但是为了充分利用Typescript的主要功能(静态类型检查),应该做几件事: 1)将Re

  • 问题内容: 我正在使用Typescript为我的React项目编写一个高阶组件,这基本上是一个函数,它接受React组件作为参数并返回一个环绕它的新组件。 然而,正如预期的那样,TS抱怨“导出函数的返回类型具有或正在使用私有名称” Anonymous class”。 有问题的功能: 该错误是合理的,因为未导出包装器函数的返回类,而其他模块导入则该函数无法知道返回值是什么。但是我无法在此函数外部声明

  • Examples Using the `withRouter` utility 如果你想应用里每个组件都处理路由对象,你可以使用withRouter高阶组件。下面是如何使用它: import { withRouter } from 'next/router' const ActiveLink = ({ children, router, href }) => { const style =

  • 我有一个HOC,它为包装的组件提供一个属性。 我想写的打字稿类型定义,正确地公开由这个HOC创建的组件的道具类型:基本上复制(推断)包装组件的道具类型,并删除我的HOC提供的属性。有可能吗? 在下面的示例中,我有一个HOC,它为任何接受它的包装组件提供了prop。因此,组件应该有props,不包括,这意味着只有。然而TypeScript显然仍然认为组件也应该获得属性。 我使用Typescript

  • 我对打字稿非常兴奋。如何设置函数参数的类型? 我怎样才能更好地输入?我想指定它必须是一个接受数字并返回数字的函数。 然后我能为这种类型做一个“接口”或一些简写,这样我就能让我的高阶函数签名更具可读性吗?

  • 本文向大家介绍写一个react的高阶组件并说明你对高阶组件的理解相关面试题,主要包含被问及写一个react的高阶组件并说明你对高阶组件的理解时的应答技巧和注意事项,需要的朋友参考一下 定义高阶组件 import React, { Component } from 'react'; const simpleHoc = WrappedComponent => { console.log('simple