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);
這種方式就比較通用一些,將 相關資源 |