Написание компонента React более высокого порядка с помощью TypeScript
Я пишу React компонент более высокого порядка (HOC) С TypeScript. 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 (я считаю) функция, которая принимает Component
. Поскольку я заранее не знаю, каким будет обернутый компонент, я использую общий тип TChildProps
чтобы определить форму реквизита обернутого компонента. Этот функция также возвращает Component
. Возвращаемый компонент принимает реквизит для обернутого компонента (опять же, набранный с помощью универсального TChildProps
) и некоторые реквизиты, необходимые для себя (тип HocProps
). При использовании возвращаемого компонента, все реквизиты (как HocProps
и реквизит для обернутом Component
) должно быть предоставлено.
теперь, когда я пытаюсь использовать мой 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 не заменяет TChildProps
С формой реквизита, необходимого для ChildComponent
. Как я могу сделать TypeScript?
2 ответов
если вы спрашиваете, можно ли определить HOC, который может добавить новую опору, скажем, "thingy", к компоненту без изменения определения реквизита этого компонента, чтобы включить "thingy", я думаю, что это невозможно.
это потому, что в какой-то момент кода Вы получите:
render() {
return (
<WrappedComponent thingy={this.props.thingy} {...this.props}/>
);
}
и это всегда будет вызывать ошибку, если WrappedComponent
не входит thingy
в его определении реквизита. Ребенок должен знать, что он получает. Без рук, я не могу. подумайте о причине передачи опоры компоненту, который все равно не знает об этом. Вы не сможете ссылаться на эту опору в дочернем компоненте без ошибки.
Я думаю, что трюк состоит в том, чтобы определить HOC как общий вокруг реквизита ребенка, а затем просто включить вашу опору thingy
или что-то в интерфейсе этого ребенка явно.
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 действительно не делает что-нибудь. Действительно, HOC должен быть выполнение какой-то логики, чтобы обеспечить значение для дочерней опоры. Например, возможно, у вас есть компонент, который отображает некоторые общие данные, которые обновляются повторно. У вас могут быть разные способы обновления и создания 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>
);
}
}
а затем пример HOC, который просто обновляет дочерний компонент на фиксированном интервале, используя 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);
Я нашел один способ заставить его работать: вызывая hoc
с предоставленным аргументом типа, например:
import ChildComponent, { Props as ChildComponentProps } from './child';
const WrappedChildComponent = hoc<ChildComponentProps>()(ChildComponent);
но мне это не очень нравится. Это требует, чтобы я экспортировал реквизит ребенка (чего я бы предпочел не делать), и у меня такое чувство, что я говорю TypeScript что-то, что он должен быть в состоянии сделать.