和之前的文章一样,本文也要求你对render props有一些学问背景,假如没有官方文档可以会对你有很大的协助。本文将会运用函数作为children的render props形式以及连系React的context API来作为例子。假如你想运用类似于render这模样的render props,那也只须要把下面例子的children作为你要衬着的props即可。
为了展现render props,我们将要重写之前文章的makeCounter HOC。这里先展现HOC的版本:
export interface InjectedCounterProps {
value: number;
onIncrement(): void;
onDecrement(): void;
}
interface MakeCounterProps {
minValue?: number;
maxValue?: number;
}
interface MakeCounterState {
value: number;
}
const makeCounter = <P extends InjectedCounterProps>(
Component: React.ComponentType<P>
) =>
class MakeCounter extends React.Component<
Subtract<P, InjectedCounterProps> & MakeCounterProps,
MakeCounterState
> {
state: MakeCounterState = {
value: 0,
};
increment = () => {
this.setState(prevState => ({
value:
prevState.value === this.props.maxValue
? prevState.value
: prevState.value + 1,
}));
};
decrement = () => {
this.setState(prevState => ({
value:
prevState.value === this.props.minValue
? prevState.value
: prevState.value - 1,
}));
};
render() {
const { minValue, maxValue, ...props } = this.props;
return (
<Component
{...props as P}
value={this.state.value}
onIncrement={this.increment}
onDecrement={this.decrement}
/>
);
}
};
HOC向组件注入了value和两个回调函数(onIncrement 和 onDecrement),另外还在HOC内部运用minValue和maxValue两个props而没有通报给组件。我们议论了假如组件须要晓得这些值,如何不通报props可以会出现题目,而且假如运用多个HOC包装组件,注入的props的定名也可以与其他HOC注入的props争执。
makeCounter HOC将会被像下面如许重写:
interface InjectedCounterProps {
value: number;
onIncrement(): void;
onDecrement(): void;
}
interface MakeCounterProps {
minValue?: number;
maxValue?: number;
children(props: InjectedCounterProps): JSX.Element;
}
interface MakeCounterState {
value: number;
}
class MakeCounter extends React.Component<MakeCounterProps, MakeCounterState> {
state: MakeCounterState = {
value: 0,
};
increment = () => {
this.setState(prevState => ({
value:
prevState.value === this.props.maxValue
? prevState.value
: prevState.value + 1,
}));
};
decrement = () => {
this.setState(prevState => ({
value:
prevState.value === this.props.minValue
? prevState.value
: prevState.value - 1,
}));
};
render() {
return this.props.children({
value: this.state.value,
onIncrement: this.increment,
onDecrement: this.decrement,
});
}
}
这里有一些须要注重的变化。起首,injectedCounterProps被保存,因为我们须要定义一个props的interface在render props函数挪用上而不是通报给组件的props(和HOC一样)。MakeCounter(MakeCounterProps)的props已转变,加上以下内容:
children(props: InjectedCounterProps): JSX.Element;
这是render prop,然后组件内须要一个函数带上注入的props并返回JSX element。下面是它用来凸起显现这一点的示例:
interface CounterProps {
style: React.CSSProperties;
minValue?: number;
maxValue?: number;
}
const Counter = (props: CounterProps) => (
<MakeCounter minValue={props.minValue} maxValue={props.maxValue}>
{injectedProps => (
<div style={props.style}>
<button onClick={injectedProps.onDecrement}> - </button>
{injectedProps.value}
<button onClick={injectedProps.onIncrement}> + </button>
</div>
)}
</MakeCounter>
);
MakeCounter本身的组件声明变得简朴多了;它不再被包装在函数中,因为它不再是暂时的,输入也越发简朴,不须要泛型、做差值和范例的交集。它只要简朴的MakeCounterProps和MakeCounterState,就像其他任何组成部分一样:
class MakeCounter extends React.Component<
MakeCounterProps,
MakeCounterState
>
末了,render()的事情也变少了;它只是一个函数挪用并带上注入的props-不须要损坏和对象的props扩大运算符展开了!
return this.props.children({
value: this.state.value,
onIncrement: this.increment,
onDecrement: this.decrement,
});
然后,render prop组件许可对props的定名和在运用的灵活性上举行更多的掌握,这是和HOC等效的一个题目:
interface CounterProps {
style: React.CSSProperties;
value: number;
minCounterValue?: number;
maxCounterValue?: number;
}
const Counter = (props: CounterProps) => (
<MakeCounter
minValue={props.minCounterValue}
maxValue={props.maxCounterValue}
>
{injectedProps => (
<div>
<div>Some other value: {props.value}</div>
<div style={props.style}>
<button onClick={injectedProps.onDecrement}> - </button>
{injectedProps.value}
<button onClick={injectedProps.onIncrement}> + </button>
</div>
{props.minCounterValue !== undefined ? (
<div>Min value: {props.minCounterValue}</div>
) : null}
{props.maxCounterValue !== undefined ? (
<div>Max value: {props.maxCounterValue}</div>
) : null}
</div>
)}
</MakeCounter>
);
有了所有这些优点,特别是更简朴的输入,那末为何不一向运用render props呢?固然可以,如许做不会有任何题目,但要注重render props组件的一些题目。
起首,这里有一个关注点之外的题目;MakeCounter组件如今被放在了Counter组件内而不是包装了它,这使得断绝测试这两个组件越发难题。其次,因为props被注入到组件的衬着函数中,因而不能在生命周期要领中运用它们(条件是计数器被更改成类组件)。
这两个题目都很轻易处理,因为您可以运用render props组件简朴地天生一个新组件:
interface CounterProps extends InjectedCounterProps {
style: React.CSSProperties;
}
const Counter = (props: CounterProps) => (
<div style={props.style}>
<button onClick={props.onDecrement}> - </button>
{props.value}
<button onClick={props.onIncrement}> + </button>
</div>
);
interface WrappedCounterProps extends CounterProps {
minValue?: number;
maxValue?: number;
}
const WrappedCounter = ({
minValue,
maxValue,
...props
}: WrappedCounterProps) => (
<MakeCounter minValue={minValue} maxValue={maxValue}>
{injectedProps => <Counter {...props} {...injectedProps} />}
</MakeCounter>
);
另一个题目是,一般来说,它不太轻易,如今运用者须要编写许多榜样文件,特别是假如他们只想将组件包装在一个零丁的暂时文件中并按原样运用props。这可以经由过程从render props组件天生HOC来弥补:
import { Subtract, Omit } from 'utility-types';
import MakeCounter, { MakeCounterProps, InjectedCounterProps } from './MakeCounter';
type MakeCounterHocProps = Omit<MakeCounterProps, 'children'>;
const makeCounter = <P extends InjectedCounterProps>(
Component: React.ComponentType<P>
): React.SFC<Subtract<P, InjectedCounterProps> & MakeCounterHocProps> => ({
minValue,
maxValue,
...props
}: MakeCounterHocProps) => (
<MakeCounter minValue={minValue} maxValue={maxValue}>
{injectedProps => <Component {...props as P} {...injectedProps} />}
</MakeCounter>
);
在这里,上一篇文章的手艺,以及render props组件的现有范例,被用来天生HOC。这里唯一须要注重的是,我们必需从HOC的props中移除render prop(children),以便在运用时不暴露它:
type MakeCounterHocProps = Omit<MakeCounterProps, 'children'>;
末了,HOC和render props组件之间的衡量归结为灵活性和便利性。这可以经由过程起首编写render props组件,然后从中天生HOC来处理,这使运用者可以在两者之间举行挑选。这类要领在可重用组件库中愈来愈罕见,比方优异的render-fns库。
就TypeScript而言,毫无疑问,hocs的范例定义要难题很多;只管经由过程这两篇文章中的示例,它表明这类负担是由HOC的提供者而不是运用者负担的。在运用方面,可以以为运用HOC比运用render props组件更轻易。
在react v16.8.0之前,我发起运用render props组件以进步键入的灵活性和简朴性,假如须要,比方构建可重用的组件库,或许关于简朴在项目中运用的render props组件,我将仅从中天生HOC。在react v16.8.0中开释react hook以后,我强烈发起在可以的情况下对两个高阶组件或render props运用它们,因为它们的范例更简朴。