原文地址:Notes on TypeScript: Render Props 本系列文章共17篇,此为第2篇
关于TypeScript的其他笔记
【译文】TypeScript笔记1/17:Pick,Exclude与高阶组件
引言
这些笔记有助于更好的理解TypeScript,并可以用来查询特殊情况下的TypeScript使用。例子基于TypeScript 3.2。
render props
(译者注:可参考react官网关于render props的介绍:render props)
render props 是一种受欢迎的、增强React组件功能的模式,可以和高阶组件hoc互换使用,至于是使用render props模式还是hoc,完全取决于个人习惯和具体的使用案例。
为了更好的理解这个主题,我们将用render props写一个组件。在前一篇TypeScript笔记中,我们写了一个接收 onChange 和 value 属性的 Input 组件,在这里我们将用render props的方式来重写这个hoc。
前一篇文章的hoc实现:(译者添加)
function withOnChange(WrappedComponent) {
return class OnChange extends React.Component {
state = {
value: ""
};
onChange = e => {
const target = e.target;
const value = target.checked ? target.checked : target.value;
this.setState({ value });
};
render() {
return (
<WrappedComponent
{...this.props}
onChange={this.onChange}
value={this.state.value}
/>
);
}
};
}
const Input = ({ value, onChange, className }) => (
<input className={className} value={value} onChange={onChange} />
);
render props 模式实现:
class OnChange extends React.Component {
state = {
value: this.props.initialValue
};
onChange = event => {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
this.setState({ value });
};
render() {
return this.props.render({
value: this.state.value,
onChange: this.onChange
});
}
}
在React应用里面使用重构之后的 onChange:
<OnChange
initialValue="hello"
render={onChangeProps => <Input {...props} {...onChangeProps} />}
/>
重用前一篇文章定义的大部分类型:
type InputProps = {
name: string,
type: string
};
type OnChangeProps = {
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
value: string
};
type ExpandedOnChangeProps = {
initialValue: string | boolean,
// (译者注:参数onChangeProps的类型应该是OnChangeProps吧?即render: (onChangeProps: OnChangeProps) => JSX.Element)
render: (onChangeProps: onChangeProps) => JSX.Element
};
type OnChangeState = {
value: string
};
Input 组件没有发生变化,这里的例子可以复用那个组件
const Input = ({ value, onChange, type, name }: InputProps & OnChangeProps) => (
<input type={type} name={name} value={value} onChange={onChange} />
);
到目前为止,一切准备就绪,让我们看看怎样编写OnChange。有意思的是,为了写OnChange组件,我们需要做的并不多。
class OnChange extends React.Component<ExpandedOnChangeProps, OnChangeState> {
state = {
value: this.props.initialValue
};
onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const target = event.target;
this.setState({ value: target.value });
};
render() {
return this.props.render({
value: this.state.value,
onChange: this.onChange
});
}
}
前一篇文章的hoc实现:(译者添加):
type WithOnChangeState = {
value: string | boolean;
}
function withOnChange<Props>(WrappedComponent: React.ComponentType<Props>) {
return class OnChange extends React.Component<Diff<Props, WithOnChangeProps>, WithOnChangeState> {
state = {
value: ""
};
onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
this.setState({ value });
};
render() {
return (
<WrappedComponent
{...this.props as Props}
onChange={this.onChange}
value={this.state.value}
/>
);
}
};
}
和hoc实现相比,render props只需要为 onChange 定义props和state,在这个例子中,只需要向下面这样使用已经存在的 ExpandedOnChangeProps 和 OnChangeState 来定义类组件
class OnChange extends React.Component<ExpandedOnChangeProps, OnChangeState>.
如果想要在应用的多个地方复用这个功能,可以通过定义一个新的组件,例如ControlledInput,将OnChange和Input结合在一起,从而允许开发者可以像定义initialValue一样定义name和type。
type ControlledInputProps = InputProps & { initialValue: string };
const ControlledInput = ({ initialValue, ...props }: ControlledInputProps) => (
<OnChange
initialValue={initialValue}
render={onChangeProps => <Input {...props} {...onChangeProps} />}
/>
);
现在ControlledInput就可以在其他的组件里使用了,并且如果传入name、type或initialValue的话,TypeScript将报错。
提高
如果想要传入render回调函数或者是children属性实现的话,我们可以对OnChange组件做出一些改变。之前的ExpandedOnChangeProps是这样的:
type ExpandedOnChangeProps = {
initialValue: string | boolean,
render: (onChangeProps: onChangeProps) => JSX.Element
};
一种方法是,把传入的回调函数作为children属性:
type ExpandedOnChangeProps = {
initialValue: string,
render?: (onChangeProps: onChangeProps) => JSX.Element,
children?: (onChangeProps: onChangeProps) => JSX.Element
};
但是上面的定义存在一个问题,render和children两个属性可以都传,也可以都不传。而我们想要的是确保只传入这两个属性之一,于是可以显示定义下面的RenderProps类型:
(译者注:为什么作者在第一行行首都会加个 | 呢?这是什么用法?还是一个小的error?对于ts才疏学浅了,后续探究一下)
type RenderProp =
| { render: (onChangeProps: OnChangeProps) => JSX.Element }
| { children: (onChangeProps: OnChangeProps) => JSX.Element };
于是可以重写成下面的ExpandedOnChangeProps定义:
type ExpandedOnChangeProps = {
initialValue: string
} & RenderProp;
最后更新render函数来处理这两种可能的情况:
class OnChange extends React.Component<ExpandedOnChangeProps, OnChangeState> {
state = {
value: this.props.initialValue
};
onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const target = event.target;
this.setState({ value: target.value });
};
render() {
if ("render" in this.props) {
return this.props.render({
value: this.state.value,
onChange: this.onChange
});
}
if ("children" in this.props) {
return this.props.children({
value: this.state.value,
onChange: this.onChange
});
}
throw new Error("A children or render prop has to be defined");
}
}
通过调用"render" in this.props,可以检查是否定义了render。同样也可以检查是否定义了children。如果这两个属性都没有定义,那么将抛出错误。
前面的ControlledInput例子可以重写:
const ControlledInput = ({
initialValue,
...props
}: InputProps & { initialValue: string }) => (
<OnChange initialValue={initialValue}>
{onChangeProps => <Input {...props} {...onChangeProps} />}
</OnChange>
);
现在,我们对于如何用TypeScript书写render props应该有了基本的了解。