React + TypeScript 默认 Props 的处理
- 2019 年 10 月 3 日
- 笔记
React 中的默认 Props通过组件的 以下示例来自 React 官方文档 – Default Prop Values: class Greeting extends React.Component { render() { return ( <h1>Hello, {this.props.name}</h1> ); } } // Specifies the default values for props: Greeting.defaultProps = { name: 'Stranger' }; // Renders "Hello, Stranger": ReactDOM.render( <Greeting />, document.getElementById('example') );
如果编译过程使用了 Babel 的 transform-class-properties 插件,还可以这么写: class Greeting extends React.Component { static defaultProps = { name: 'stranger' } render() { return ( <div>Hello, {this.props.name}</div> ) } }
加入 TypeScript加入 TypeScript 后 interface Props { name?: string; } class Greeting extends React.Component<Props, {}> { static defaultProps = { name: "stranger", }; render() { return <div>Hello, {this.props.name}</div>; } }
此时不支持直接通过类访问 // ?Property 'defualtProps' does not exist on type 'typeof Greeting'.ts(2339) Greeting.defualtProps = { name: "stranger", };
默认属性的类型上面虽然实现了通过 class Greeting extends React.Component<Props, {}> { static defaultProps = { name: "stranger", // 并不会报错 + foo: 1, + bar: {}, }; // ... }
同时对于同一字段,我们不得不书写两次代码。一次是定义组件的 为了后面演示方便,现在给组件新增一个必填属性 interface Props { age: number; name?: string; } class Greeting extends React.Component<Props, {}> { static defaultProps = { name: "stranger", }; render() { const { name, age } = this.props; return ( <div> Hello, {name}, my age is {age} </div> ); } }
通过可选属性抽取出来,利用 所以优化后的代码成了: const defaultProps = { name: "stranger", }; type Props = { age: number; } & Partial<typeof defaultProps>; class Greeting extends React.Component<Props, {}> { static defaultProps = defaultProps; render() { const { name, age } = this.props; return ( <div> Hello, {name}, my age is {age} </div> ); } }
注意我们的 当我们更新了 默认值的判空检查优化讲道理,如果属性提供了默认值,在使用时,可不再需要判空,因为其一定是有值的。但 TypeScript 在编译时并不知道,因为有默认值的属性是被定义成可选的 比如我们尝试访问 class Greeting extends React.Component<Props, {}> { static defaultProps = defaultProps; render() { const { name } = this.props; return ( <div> {/* ?Object is possibly 'undefined'.ts(2532) */} name length is {name.length} </div> ); } }
因为此时我们的 type Props = { age: number; } & Partial<typeof defaultProps>; // 相当于: type Props = { age: number; name?: string; };
修正方法有多个,最简单的是使用非空判定符/Non-null assertion operator。 非空判定符- name length is {name.length} + name length is {name!.length}
这意味着每一处使用的地方都需要做类似的操作,当程序复杂起来时不太可控。但多数情况下应付日常使用,这样已经够了。 类型转换因为组件内部有默认值的保证,所以字段不可能为空,因此,可对组件内部使用非空的属性类型来定义组件,而对外仍暴露原来的版本。 const Greeting = class extends React.Component< - Props, + Props & typeof defaultProps, {} > { static defaultProps = defaultProps; render() { const { name } = this.props; return ( <div> - name length is {name!.length} + name length is {name.length} </div> ); } -}; +} as React.ComponentClass<Props>;
通过 通过高阶组件的方式封装默认属性的处理通过定义一个高阶组件比如 function withDefaultProps<P extends object, DP extends Partial<P>>( dp: DP, component: React.ComponentType<P>, ) { component.defaultProps = dp; type RequiredProps = Omit<P, keyof DP>; return (component as React.ComponentType<any>) as React.ComponentType< RequiredProps & DP >; }
然后我们的组件则可以这样来写: const defaultProps = { name: "stranger", }; interface Props { name: string; age: number; } const _Greeting = class extends React.Component<Props, {}> { public render() { const { name } = this.props; return <div>name length is {name.length}</div>; } }; export const Greeting = withDefaultProps(defaultProps, _Greeting);
这种方式就比较通用一些,将 相关资源 |