React + TypeScript 默认 Props 的处理
<table class="d-block"><tbody class="d-block">
<tr class="d-block">
<td class="d-block comment-body markdown-body js-comment-body rgh-linkified-code">
<h2>React 中的默认 Props</h2>
<p>通过组件的 <code>defaultProps</code> 属性可为其 <code>Props</code> 指定默认值。</p>
<p>以下示例来自 React 官方文档 - Default Prop Values:</p>
<div class="highlight highlight-source-js"><pre><span class="pl-k">class</span> <span class="pl-en">Greeting</span> <span class="pl-k">extends</span> <span class="pl-e">React</span>.<span class="pl-smi">Component</span> {
<span class="pl-en">render</span>() {
<span class="pl-k">return</span> (
<span class="pl-k"><</span>h1<span class="pl-k">></span>Hello, {<span class="pl-c1">this</span>.<span class="pl-smi">props</span>.<span class="pl-c1">name</span>}<span class="pl-k"><</span><span class="pl-k">/</span>h1<span class="pl-k">></span>
);
}
}
<p><span class="pl-c"><span class="pl-c">//</span> Specifies the default values for props:</span><br>
<span class="pl-smi">Greeting</span>.<span class="pl-smi">defaultProps</span> <span class="pl-k">=</span> {<br>
name<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">'</span>Stranger<span class="pl-pds">'</span></span><br>
};</p>
<p><span class="pl-c"><span class="pl-c">//</span> Renders "Hello, Stranger":</span><br>
<span class="pl-smi">ReactDOM</span>.<span class="pl-en">render</span>(<br>
<span class="pl-k"><</span>Greeting <span class="pl-k">/</span><span class="pl-k">></span>,<br>
<span class="pl-c1">document</span>.<span class="pl-c1">getElementById</span>(<span class="pl-s"><span class="pl-pds">'</span>example<span class="pl-pds">'</span></span>)<br>
);</p></pre></div><p></p>
<p>如果编译过程使用了 Babel 的 transform-class-properties 插件,还可以这么写:</p>
<div class="highlight highlight-source-js"><pre><span class="pl-k">class</span> <span class="pl-en">Greeting</span> <span class="pl-k">extends</span> <span class="pl-e">React</span>.<span class="pl-smi">Component</span> {
<span class="pl-k">static</span> defaultProps <span class="pl-k">=</span> {
name<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">'</span>stranger<span class="pl-pds">'</span></span>
}
<p><span class="pl-en">render</span>() {<br>
<span class="pl-k">return</span> (<br>
<span class="pl-k"><</span>div<span class="pl-k">></span>Hello, {<span class="pl-c1">this</span>.<span class="pl-smi">props</span>.<span class="pl-c1">name</span>}<span class="pl-k"><</span><span class="pl-k">/</span>div<span class="pl-k">></span><br>
)<br>
}<br>
}</p></pre></div><p></p>
<h2>加入 TypeScript</h2>
<p>加入 TypeScript 后</p>
<div class="highlight highlight-source-tsx"><pre><span class="pl-k">interface</span> <span class="pl-en">Props</span> {
name<span class="pl-k">?</span><span class="pl-k">:</span> <span class="pl-c1">string</span>;
}
<p><span class="pl-k">class</span> <span class="pl-en">Greeting</span> <span class="pl-k">extends</span> <span class="pl-en">React</span>.<span class="pl-e">Component</span><<span class="pl-en">Props</span>, {}> {<br>
<span class="pl-k">static</span> defaultProps <span class="pl-k">=</span> {<br>
name: <span class="pl-s"><span class="pl-pds">"</span>stranger<span class="pl-pds">"</span></span>,<br>
};</p>
<p>render() {<br>
<span class="pl-k">return</span> <<span class="pl-ent">div</span>>Hello, <span class="pl-pse">{</span><span class="pl-c1">this</span>.<span class="pl-smi">props</span>.<span class="pl-c1">name</span><span class="pl-pse">}</span></<span class="pl-ent">div</span>>;<br>
}<br>
}</p></pre></div><p></p>
<p>此时不支持直接通过类访问 <code>defaultProps</code> 来赋值以设置默认属性,因为 <code>React.Component</code> 类型上并没有该属性。</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-c"><span class="pl-c">//</span> 🚨Property 'defualtProps' does not exist on type 'typeof Greeting'.ts(2339)</span>
<span class="pl-smi">Greeting</span>.<span class="pl-smi">defualtProps</span> <span class="pl-k">=</span> {
name: <span class="pl-s"><span class="pl-pds">"</span>stranger<span class="pl-pds">"</span></span>,
};</pre></div>
<h3>默认属性的类型</h3>
<p>上面虽然实现了通过 <code>defaultProps</code> 来指定属性的默认值,但 <code>defaultProps</code> 的类型是不受约束的,和 <code>Props</code> 没有关联上。以至于我们可以在 <code>defaultProps</code> 里面放任何值,显然这是不科学的。</p>
<div class="highlight highlight-source-diff"><pre>class Greeting extends React.Component<Props, {}> {
static defaultProps = {
name: "stranger",
// 并不会报错
<span class="pl-mi1"><span class="pl-mi1">+</span> foo: 1,</span>
<span class="pl-mi1"><span class="pl-mi1">+</span> bar: {},</span>
};
// ...
}</pre></div>
<p>同时对于同一字段,我们不得不书写两次代码。一次是定义组件的 <code>Props</code>,另一次是在 <code>defaultProps</code> 里。如果属性有增删或名称有变更,两个地方都需要改。</p>
<p>为了后面演示方便,现在给组件新增一个必填属性 <code>age:number</code>。</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-k">interface</span> <span class="pl-en">Props</span> {
age<span class="pl-k">:</span> <span class="pl-c1">number</span>;
name<span class="pl-k">?</span><span class="pl-k">:</span> <span class="pl-c1">string</span>;
}
<p><span class="pl-k">class</span> <span class="pl-en">Greeting</span> <span class="pl-k">extends</span> <span class="pl-en">React</span>.<span class="pl-e">Component</span><<span class="pl-en">Props</span>, {}> {<br>
<span class="pl-k">static</span> defaultProps <span class="pl-k">=</span> {<br>
name: <span class="pl-s"><span class="pl-pds">"</span>stranger<span class="pl-pds">"</span></span>,<br>
};</p>
<p>render() {<br>
<span class="pl-k"><span class="pl-k">const</span></span> { name, age } <span class="pl-k">=</span> <span class="pl-c1">this</span>.<span class="pl-smi">props</span>;<br>
<span class="pl-k">return</span> (<br>
<<span class="pl-en">div</span>><br>
<span class="pl-smi">Hello</span>, {<span class="pl-v">name</span>}, <span class="pl-smi">my</span> <span class="pl-smi">age</span> <span class="pl-smi">is</span> {<span class="pl-v">age</span>}<br>
<span class="pl-k"><</span><span class="pl-k">/</span><span class="pl-smi">div</span><span class="pl-k">></span><br>
);<br>
}<br>
}</p></pre></div><p></p>
<p>通过可选属性抽取出来,利用 <code>typeof</code> 获取其类型和必传属性结合来形成组件的 <code>Props</code> 可解决上面提到的两个问题。</p>
<p>所以优化后的代码成了:</p>
<div class="highlight highlight-source-tsx"><pre><span class="pl-k"><span class="pl-k">const</span></span> defaultProps <span class="pl-k">=</span> {
name: <span class="pl-s"><span class="pl-pds">"</span>stranger<span class="pl-pds">"</span></span>,
};
<p><span class="pl-k">type</span> <span class="pl-en">Props</span> <span class="pl-k">=</span> {<br>
age<span class="pl-k">:</span> <span class="pl-c1">number</span>;<br>
} <span class="pl-k">&</span> <span class="pl-en">Partial</span><<span class="pl-k">typeof</span> <span class="pl-en">defaultProps</span>>;</p>
<p><span class="pl-k">class</span> <span class="pl-en">Greeting</span> <span class="pl-k">extends</span> <span class="pl-en">React</span>.<span class="pl-e">Component</span><<span class="pl-en">Props</span>, {}> {<br>
<span class="pl-k">static</span> defaultProps <span class="pl-k">=</span> <span class="pl-smi">defaultProps</span>;</p>
<p>render() {<br>
<span class="pl-k"><span class="pl-k">const</span></span> { name, age } <span class="pl-k">=</span> <span class="pl-c1">this</span>.<span class="pl-smi">props</span>;<br>
<span class="pl-k">return</span> (<br>
<<span class="pl-ent">div</span>><br>
Hello, <span class="pl-pse">{</span><span class="pl-smi">name</span><span class="pl-pse">}</span>, my age is <span class="pl-pse">{</span><span class="pl-smi">age</span><span class="pl-pse">}</span><br>
</<span class="pl-ent">div</span>><br>
);<br>
}<br>
}</p></pre></div><p></p>
<p>注意我们的 <code>Props</code> 是通过和 <code>typeof defaultProps</code> 组合而形成的,可选属性中的 <code>name</code> 字段在整个代码中只书写了一次。</p>
<p>当我们更新了 <code>defaultProps</code> 时整个组件的 <code>Props</code> 也同步更新,所以 <code>defaultProps</code> 中的字段一定是组件所需要的字段。</p>
<h2>默认值的判空检查优化</h2>
<p>讲道理,如果属性提供了默认值,在使用时,可不再需要判空,因为其一定是有值的。但 TypeScript 在编译时并不知道,因为有默认值的属性是被定义成可选的 <code>?</code>。</p>
<p>比如我们尝试访问 <code>name</code> 属性的长度,</p>
<div class="highlight highlight-source-tsx"><pre><span class="pl-k">class</span> <span class="pl-en">Greeting</span> <span class="pl-k">extends</span> <span class="pl-en">React</span>.<span class="pl-e">Component</span><<span class="pl-en">Props</span>, {}> {
<span class="pl-k">static</span> defaultProps <span class="pl-k">=</span> <span class="pl-smi">defaultProps</span>;
<p>render() {<br>
<span class="pl-k"><span class="pl-k">const</span></span> { name } <span class="pl-k">=</span> <span class="pl-c1">this</span>.<span class="pl-smi">props</span>;<br>
<span class="pl-k">return</span> (<br>
<<span class="pl-ent">div</span>><br>
<span class="pl-pse">{</span><span class="pl-c"><span class="pl-c">/<em></em></span><em> 🚨Object is possibly 'undefined'.ts(2532) <span class="pl-c"></span></em>/</span><span class="pl-pse">}</span><br>
name length is <span class="pl-pse">{</span><span class="pl-smi">name</span>.<span class="pl-c1">length</span><span class="pl-pse">}</span><br>
</<span class="pl-ent">div</span>><br>
);<br>
}<br>
}</p></pre></div><p></p>
<p>因为此时我们的 <code>Props</code> 实际上是:</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-k">type</span> <span class="pl-en">Props</span> <span class="pl-k">=</span> {
age<span class="pl-k">:</span> <span class="pl-c1">number</span>;
} <span class="pl-k">&</span> <span class="pl-en">Partial</span><<span class="pl-k">typeof</span> <span class="pl-en">defaultProps</span>>;
<span class="pl-c"><span class="pl-c">//</span> 相当于:</span>
<span class="pl-k">type</span> <span class="pl-en">Props</span> <span class="pl-k">=</span> {
age<span class="pl-k">:</span> <span class="pl-c1">number</span>;
name<span class="pl-k">?</span><span class="pl-k">:</span> <span class="pl-c1">string</span>;
};</pre></div>
<p>修正方法有多个,最简单的是使用非空判定符/Non-null assertion operator。</p>
<h3>非空判定符</h3>
<div class="highlight highlight-source-diff"><pre><span class="pl-md"><span class="pl-md">-</span> name length is {name.length}</span>
<span class="pl-mi1"><span class="pl-mi1">+</span> name length is {name!.length}</span></pre></div>
<p>这意味着每一处使用的地方都需要做类似的操作,当程序复杂起来时不太可控。但多数情况下应付日常使用,这样已经够了。</p>
<h3>类型转换</h3>
<p>因为组件内部有默认值的保证,所以字段不可能为空,因此,可对组件内部使用非空的属性类型来定义组件,而对外仍暴露原来的版本。</p>
<div class="highlight highlight-source-diff"><pre>const Greeting = class extends React.Component<
<span class="pl-md"><span class="pl-md">-</span>Props,</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>Props & typeof defaultProps,</span>
{}
<span class="pl-mi1"><span class="pl-mi1">></span> {</span>
static defaultProps = defaultProps;
<p>render() {<br>
const { name } = this.props;<br>
return (<br>
<div><br>
<span class="pl-md"><span class="pl-md">-</span> name length is {name!.length}</span><br>
<span class="pl-mi1"><span class="pl-mi1">+</span> name length is {name.length}</span><br>
</div><br>
);<br>
}<br>
<span class="pl-md"><span class="pl-md">-</span>};</span><br>
<span class="pl-mi1"><span class="pl-mi1">+</span>} as React.ComponentClass<Props>;</span></p></pre></div><p></p>
<p>通过 <code>as React.ComponentClass<Props></code> 的类型转换,对外使用 <code>Greeting</code> 时属性中 <code>name</code> 还是可选的,但组件内部实际使用的是 <code>Props & typeof defaultProps</code>,而不是 <code>Partial<T></code> 版本的,所以规避了字段可能为空的报错。</p>
<h3>通过高阶组件的方式封装默认属性的处理</h3>
<p>通过定义一个高阶组件比如 <code>withDefaultProps</code> 将需要默认属性的组件包裹,将默认值的处理放到高阶组件中,同样可解决上述问题。</p>
<div class="highlight highlight-source-tsx"><pre><span class="pl-k">function</span> withDefaultProps<<span class="pl-en">P</span> <span class="pl-k">extends</span> <span class="pl-c1">object</span>, <span class="pl-en">DP</span> <span class="pl-k">extends</span> <span class="pl-en">Partial</span><<span class="pl-en">P</span>>>(
<span class="pl-v">dp</span><span class="pl-k">:</span> <span class="pl-en">DP</span>,
<span class="pl-v">component</span><span class="pl-k">:</span> <span class="pl-en">React</span>.<span class="pl-en">ComponentType</span><<span class="pl-en">P</span>>,
) {
<span class="pl-smi">component</span>.<span class="pl-smi">defaultProps</span> <span class="pl-k">=</span> <span class="pl-smi">dp</span>;
<span class="pl-k">type</span> <span class="pl-en">RequiredProps</span> <span class="pl-k">=</span> <span class="pl-en">Omit</span><<span class="pl-en">P</span>, <span class="pl-k">keyof</span> <span class="pl-en">DP</span>>;
<span class="pl-k">return</span> (<span class="pl-smi">component</span> <span class="pl-k">as</span> <span class="pl-en">React</span>.<span class="pl-en">ComponentType</span><<span class="pl-c1">any</span>>) <span class="pl-k">as</span> <span class="pl-en">React</span>.<span class="pl-en">ComponentType</span><
<span class="pl-en">RequiredProps</span> <span class="pl-k">&</span> <span class="pl-en">DP</span>
>;
}</pre></div>
<p>然后我们的组件则可以这样来写:</p>
<div class="highlight highlight-source-tsx"><pre><span class="pl-k"><span class="pl-k">const</span></span> defaultProps <span class="pl-k">=</span> {
name: <span class="pl-s"><span class="pl-pds">"</span>stranger<span class="pl-pds">"</span></span>,
};
<p><span class="pl-k">interface</span> <span class="pl-en">Props</span> {<br>
name<span class="pl-k">:</span> <span class="pl-c1">string</span>;<br>
age<span class="pl-k">:</span> <span class="pl-c1">number</span>;<br>
}</p>
<p><span class="pl-k"><span class="pl-k">const</span></span> _Greeting <span class="pl-k">=</span> <span class="pl-k">class</span> <span class="pl-k">extends</span> <span class="pl-en">React</span>.<span class="pl-e">Component</span><<span class="pl-en">Props</span>, {}> {<br>
<span class="pl-k">public</span> render() {<br>
<span class="pl-k"><span class="pl-k">const</span></span> { name } <span class="pl-k">=</span> <span class="pl-c1">this</span>.<span class="pl-smi">props</span>;<br>
<span class="pl-k">return</span> <<span class="pl-ent">div</span>>name length is <span class="pl-pse">{</span><span class="pl-smi">name</span>.<span class="pl-c1">length</span><span class="pl-pse">}</span></<span class="pl-ent">div</span>>;<br>
}<br>
};</p>
<p><span class="pl-k"><span class="pl-k">export</span></span> <span class="pl-k"><span class="pl-k">const</span></span> Greeting <span class="pl-k">=</span> <span class="pl-en">withDefaultProps</span>(<span class="pl-smi">defaultProps</span>, <span class="pl-smi">_Greeting</span>);</p></pre></div><p></p>
<p>这种方式就比较通用一些,将 <code>withDefaultProps</code> 抽取成一个公共组件,后续其他组件都可使用。但此种情况下就没有很好地利用已经定义好的默认值 <code>defaultProps</code> 中的字段,书写 <code>Props</code> 时还需要重复写一遍字段名。</p>
<h2>相关资源</h2>
<ul>
<li>React docs - Default Prop Values</li>
<li>Default property value in React component using TypeScript</li>
<li>React, TypeScript and defaultProps dilemma</li>
</ul></td>
</tr>
</tbody>
</table>
</div>
<div id="MySignature" role="contentinfo">
<div>
<img src="https://licensebuttons.net/l/by-nc-sa/3.0/88x31.png" style="vertical-align: middle">
<strong>CC BY-NC-SA 署名-非商业性使用-相同方式共享</strong>
</div><br><br>
来源:https://www.cnblogs.com/Wayou/p/react_typescript_default_props.html
頁:
[1]