大手空调配件 發表於 2019-9-29 10:16:00

React + TypeScript 实现泛型组件

<br><p></p><table class="d-block">
<tbody class="d-block">
    <tr class="d-block">
      <td class="d-block comment-body markdown-bodyjs-comment-body">
<h2>泛型类型</h2>
<p>TypeScript 中,类型(interface, type)是可以声明成泛型的,这很常见。</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-k">interface</span> <span class="pl-en">Props</span>&lt;<span class="pl-en">T</span>&gt; {
content<span class="pl-k">:</span> <span class="pl-en">T</span>;
}</pre></div>
<p>这表明 <code>Props</code> 接口定义了这么一种类型:</p>
<ul>
<li>它是包含一个 <code>content</code> 字段的对象</li>
<li>该 <code>content</code> 字段的类型由使用时的泛型 <code>T</code> 决定</li>
</ul>
<div class="highlight highlight-source-ts"><pre><span class="pl-k">type</span> <span class="pl-en">StringProps</span> <span class="pl-k">=</span> <span class="pl-en">Props</span>&lt;<span class="pl-c1">string</span>&gt;;
<p><span class="pl-k">let</span> props<span class="pl-k">:</span> <span class="pl-en">StringProps</span>;</p>
<p><span class="pl-smi">props</span> <span class="pl-k">=</span> {<br>
<span class="pl-c"><span class="pl-c">//</span> 🚨 Type 'number' is not assignable to type 'string'.ts(2322)</span><br>
content: <span class="pl-c1">42</span><br>
};</p>
<p><span class="pl-smi">props</span> <span class="pl-k">=</span> {<br>
<span class="pl-c"><span class="pl-c">//</span> ✅</span><br>
content: <span class="pl-s"><span class="pl-pds">"</span>hello<span class="pl-pds">"</span></span><br>
};</p></pre></div><p></p>
<p>或者,TypeScript 能够跟使用时候提供的值自动推断出类型 <code>T</code>,无需显式指定:</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-k">interface</span> <span class="pl-en">Props</span>&lt;<span class="pl-en">T</span>&gt; {
content<span class="pl-k">:</span> <span class="pl-en">T</span>;
}
<p><span class="pl-k">function</span> Foo&lt;<span class="pl-en">T</span>&gt;(<span class="pl-v">props</span><span class="pl-k">:</span> <span class="pl-en">Props</span>&lt;<span class="pl-en">T</span>&gt;) {<br>
<span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-smi">props</span>);<br>
}</p>
<p><span class="pl-c"><span class="pl-c">/**</span> 此时 Foo 的完整签名为: function Foo&lt;number&gt;(props: Props&lt;number&gt;): void <span class="pl-c">*/</span></span><br>
<span class="pl-en">Foo</span>({ content: <span class="pl-c1">42</span> });</p>
<p><span class="pl-c"><span class="pl-c">/**</span> 此时 Foo 的完整签名为: function Foo&lt;string&gt;(props: Props&lt;string&gt;): void <span class="pl-c">*/</span></span><br>
<span class="pl-en">Foo</span>({ content: <span class="pl-s"><span class="pl-pds">"</span>hello<span class="pl-pds">"</span></span> });</p></pre></div><p></p>
<p>上面因为 <code>Foo</code> 函数接收 <code>Props&lt;T&gt;</code> 作为入参,意味着我们在调用 <code>Foo</code> 的时候需要传递类型 <code>T</code> 以确定 <code>Props&lt;T&gt;</code>,所以 <code>Foo</code> 函数也变成了泛型。</p>
<p>当调用 <code>Foo({ content: 42 })</code> 的时候,TypeScript 自动解析出 <code>T</code> 为 <code>number</code>,此时对应的函数签名为:</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-k">function</span> Foo&lt;<span class="pl-c1">number</span>&gt;(<span class="pl-v">props</span><span class="pl-k">:</span> <span class="pl-en">Props</span>&lt;<span class="pl-c1">number</span>&gt;)<span class="pl-k">:</span> <span class="pl-c1">void</span>;</pre></div>
<p>而我们并没有显式地指定其中的类型 <code>T</code>,像这样 <code>Foo&lt;number&gt;({ content: 42 });</code>。</p>
<h2>泛型组件</h2>
<p>将上面的 <code>Foo</code> 函数返回 JSX 元素,就成了一个 React 组件。因为它是泛型函数,它所形成的组件也就成了 <strong>泛型组件/Generic Components</strong>。</p>
<div class="highlight highlight-source-tsx"><pre><span class="pl-k">function</span> Foo&lt;<span class="pl-en">T</span>&gt;(<span class="pl-v">props</span><span class="pl-k">:</span> <span class="pl-en">Props</span>&lt;<span class="pl-en">T</span>&gt;) {
<span class="pl-k">return</span> &lt;<span class="pl-ent">div</span>&gt; <span class="pl-pse">{</span><span class="pl-smi">props</span>.<span class="pl-c1">content</span><span class="pl-pse">}</span>&lt;/<span class="pl-ent">div</span>&gt;;
}
<p><span class="pl-k"><span class="pl-k">const</span></span> App <span class="pl-k">=</span> () <span class="pl-k">=&gt;</span> {<br>
<span class="pl-k">return</span> (<br>
&lt;<span class="pl-ent">div</span> <span class="pl-e">className</span><span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>App<span class="pl-pds">"</span></span>&gt;<br>
&lt;<span class="pl-ent"><span class="pl-c1">Foo</span></span> <span class="pl-e">content</span><span class="pl-k">=</span><span class="pl-pse">{</span><span class="pl-c1">42</span><span class="pl-pse">}</span>&gt;&lt;/<span class="pl-ent"><span class="pl-c1">Foo</span></span>&gt;<br>
&lt;<span class="pl-ent"><span class="pl-c1">Foo</span></span>&lt;<span class="pl-c1">string</span>&gt; <span class="pl-e">content</span><span class="pl-k">=</span><span class="pl-pse">{</span><span class="pl-s"><span class="pl-pds">"</span>hello<span class="pl-pds">"</span></span><span class="pl-pse">}</span>&gt;&lt;/<span class="pl-ent"><span class="pl-c1">Foo</span></span>&gt;<br>
&lt;/<span class="pl-ent">div</span>&gt;<br>
);<br>
};</p></pre></div><p></p>
<p>一如上面的讨论,因为 TypeScript 可根据传入的实际值解析泛型类型,所以 <code>&lt;Foo&lt;string&gt; content={"hello"}&gt;&lt;/Foo&gt;</code> 中 <code>string</code> 是可选的,这里只为展示,让你看到其实 React 组件还可以这么玩。</p>
<p>为了进一步理解泛型组件,再看下非泛型情况下上面的组件是长怎样的。</p>
<div class="highlight highlight-source-tsx"><pre><span class="pl-k">interface</span> <span class="pl-en">Props</span> {
content<span class="pl-k">:</span> <span class="pl-c1">string</span>;
}
<p><span class="pl-k">function</span> Foo(<span class="pl-v">props</span><span class="pl-k">:</span> <span class="pl-en">Props</span>) {<br>
<span class="pl-k">return</span> &lt;<span class="pl-ent">div</span>&gt;<span class="pl-pse">{</span><span class="pl-smi">props</span>.<span class="pl-c1">content</span><span class="pl-pse">}</span>&lt;/<span class="pl-ent">div</span>&gt;;<br>
}</p>
<p><span class="pl-k"><span class="pl-k">const</span></span> App <span class="pl-k">=</span> () <span class="pl-k">=&gt;</span> {<br>
<span class="pl-k">return</span> (<br>
&lt;<span class="pl-ent">div</span> <span class="pl-e">className</span><span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>App<span class="pl-pds">"</span></span>&gt;<br>
<span class="pl-pse">{</span><span class="pl-c"><span class="pl-c">/<em></em></span><em> 🚨 Type 'number' is not assignable to type 'string'.ts(2322) <span class="pl-c"></span></em>/</span><span class="pl-pse">}</span><br>
&lt;<span class="pl-ent"><span class="pl-c1">Foo</span></span> <span class="pl-e">content</span><span class="pl-k">=</span><span class="pl-pse">{</span><span class="pl-c1">42</span><span class="pl-pse">}</span>&gt;&lt;/<span class="pl-ent"><span class="pl-c1">Foo</span></span>&gt;<br>
&lt;<span class="pl-ent"><span class="pl-c1">Foo</span></span> <span class="pl-e">content</span><span class="pl-k">=</span><span class="pl-pse">{</span><span class="pl-s"><span class="pl-pds">"</span>hello<span class="pl-pds">"</span></span><span class="pl-pse">}</span>&gt;&lt;/<span class="pl-ent"><span class="pl-c1">Foo</span></span>&gt;<br>
&lt;/<span class="pl-ent">div</span>&gt;<br>
);<br>
};</p></pre></div><p></p>
<p>以上,便是一个 React 组件常规的写法。它定义的入参 <code>Props</code> 只接收 <code>string</code> 类型。由此也看出泛型的优势,即大部分代码可复用的情况下,将参数变成泛型后,不同类型的入参可复用同一组件,不用为新类型新写一个组件。</p>
<p>除了函数组件,对于类类型的组件来说,也是一样可泛型化的。</p>
<div class="highlight highlight-source-tsx"><pre><span class="pl-k">interface</span> <span class="pl-en">Props</span>&lt;<span class="pl-en">T</span>&gt; {
content<span class="pl-k">:</span> <span class="pl-en">T</span>;
}
<p><span class="pl-k">class</span> <span class="pl-en">Bar</span>&lt;<span class="pl-en">T</span>&gt; <span class="pl-k">extends</span> <span class="pl-en">React</span>.<span class="pl-e">Component</span>&lt;<span class="pl-en">Props</span>&lt;<span class="pl-en">T</span>&gt;&gt; {<br>
render() {<br>
<span class="pl-k">return</span> &lt;<span class="pl-ent">div</span>&gt;<span class="pl-pse">{</span><span class="pl-c1">this</span>.<span class="pl-smi">props</span>.<span class="pl-c1">content</span><span class="pl-pse">}</span>&lt;/<span class="pl-ent">div</span>&gt;;<br>
}<br>
}</p>
<p><span class="pl-k"><span class="pl-k">const</span></span> App <span class="pl-k">=</span> () <span class="pl-k">=&gt;</span> {<br>
<span class="pl-k">return</span> (<br>
&lt;<span class="pl-ent">div</span> <span class="pl-e">className</span><span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>App<span class="pl-pds">"</span></span>&gt;<br>
&lt;<span class="pl-ent"><span class="pl-c1">Bar</span></span> <span class="pl-e">content</span><span class="pl-k">=</span><span class="pl-pse">{</span><span class="pl-c1">42</span><span class="pl-pse">}</span>&gt;&lt;/<span class="pl-ent"><span class="pl-c1">Bar</span></span>&gt;<br>
&lt;<span class="pl-ent"><span class="pl-c1">Bar</span></span>&lt;<span class="pl-c1">string</span>&gt; <span class="pl-e">content</span><span class="pl-k">=</span><span class="pl-pse">{</span><span class="pl-s"><span class="pl-pds">"</span>hello<span class="pl-pds">"</span></span><span class="pl-pse">}</span>&gt;&lt;/<span class="pl-ent"><span class="pl-c1">Bar</span></span>&gt;<br>
&lt;/<span class="pl-ent">div</span>&gt;<br>
);<br>
};</p></pre></div><p></p>
<h2>一个更加真实的示例</h2>
<p>一个更加实用的示例是列表组件。列表中的分页加载,滚动刷新逻辑等,对于所有列表数据都是通用的,将这个列表组件书写成泛型便可和任意类型列表数据结合,而无须通过其他方式来达到复用的目的,将列表元素声明成 <code>any</code> 或 <code>Record&lt;string,any&gt;</code> 等类型。</p>
<p>先看不使用泛型情况下,如何实现这么一个列表组件。此处只看列表元素的展示以阐述泛型的作用,其他逻辑比如数据加载等先忽略。</p>
<p><em>列表组件 List.tsx</em></p>
<div class="highlight highlight-source-tsx"><pre><span class="pl-k">interface</span> <span class="pl-en">Item</span> {
[<span class="pl-v">prop</span><span class="pl-k">:</span> <span class="pl-c1">string</span>]<span class="pl-k">:</span> <span class="pl-c1">any</span>;
}
<p><span class="pl-k">interface</span> <span class="pl-en">Props</span> {<br>
list<span class="pl-k">:</span> <span class="pl-en">Item</span>[];<br>
children<span class="pl-k">:</span> (<span class="pl-v">item</span><span class="pl-k">:</span> <span class="pl-en">Item</span>, <span class="pl-v">index</span><span class="pl-k">:</span> <span class="pl-c1">number</span>) <span class="pl-k">=&gt;</span> <span class="pl-en">React</span>.<span class="pl-en">ReactNode</span>;<br>
}</p>
<p><span class="pl-k">function</span> List({ <span class="pl-v">list</span>, <span class="pl-v">children</span> }<span class="pl-k">:</span> <span class="pl-en">Props</span>) {<br>
<span class="pl-c"><span class="pl-c">//</span> 列表中其他逻辑...</span><br>
<span class="pl-k">return</span> &lt;<span class="pl-ent">div</span>&gt;<span class="pl-pse">{</span><span class="pl-smi">list</span>.<span class="pl-en">map</span>(<span class="pl-smi">children</span>)<span class="pl-pse">}</span>&lt;/<span class="pl-ent">div</span>&gt;;<br>
}</p></pre></div><p></p>
<p>上面,为了尽可能满足大部分数据类型,将列表的元素类型定义成了 <code>: any;</code> 的形式,其实和 <code>Record&lt;string,any&gt;</code> 没差。在这里已经可以看到类型的丢失了,因为出现了 <code>any</code>,而我们使用 TypeScript 的首要准则是尽量避免 <code>any</code>。</p>
<p>然后是使用上面所定义的列表组件:</p>
<div class="highlight highlight-source-tsx"><pre><span class="pl-k">interface</span> <span class="pl-en">User</span> {
id<span class="pl-k">:</span> <span class="pl-c1">number</span>;
name<span class="pl-k">:</span> <span class="pl-c1">string</span>;
}
<span class="pl-k"><span class="pl-k">const</span></span> data<span class="pl-k">:</span> <span class="pl-en">User</span>[] <span class="pl-k">=</span> [
{
    id: <span class="pl-c1">1</span>,
    name: <span class="pl-s"><span class="pl-pds">"</span>wayou<span class="pl-pds">"</span></span>
},
{
    id: <span class="pl-c1">1</span>,
    name: <span class="pl-s"><span class="pl-pds">"</span>niuwayong<span class="pl-pds">"</span></span>
}
];
<p><span class="pl-k"><span class="pl-k">const</span></span> App <span class="pl-k">=</span> () <span class="pl-k">=&gt;</span> {<br>
<span class="pl-k">return</span> (<br>
&lt;<span class="pl-ent">div</span> <span class="pl-e">className</span><span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>App<span class="pl-pds">"</span></span>&gt;<br>
&lt;<span class="pl-ent"><span class="pl-c1">List</span></span> <span class="pl-e">list</span><span class="pl-k">=</span><span class="pl-pse">{</span><span class="pl-smi">data</span><span class="pl-pse">}</span>&gt;<br>
<span class="pl-pse">{</span><span class="pl-v">item</span> <span class="pl-k">=&gt;</span> {<br>
<span class="pl-c"><span class="pl-c">//</span> 😭 此处 <code>item.name</code> 类型为 <code>any</code></span><br>
<span class="pl-k">return</span> &lt;<span class="pl-ent">div</span> <span class="pl-e">key</span><span class="pl-k">=</span><span class="pl-pse">{</span><span class="pl-smi">item</span>.<span class="pl-c1">name</span><span class="pl-pse">}</span>&gt;<span class="pl-pse">{</span><span class="pl-smi">item</span>.<span class="pl-c1">name</span><span class="pl-pse">}</span>&lt;/<span class="pl-ent">div</span>&gt;;<br>
}<span class="pl-pse">}</span><br>
&lt;/<span class="pl-ent"><span class="pl-c1">List</span></span>&gt;<br>
&lt;/<span class="pl-ent">div</span>&gt;<br>
);<br>
};</p></pre></div><p></p>
<p>这里使用时,<code>item.name</code> 的类型已经成了 <code>any</code>。对于简单数据来说,还可以接收这样类型的丢失,但对于复杂类型,类型的丢失就完全享受不到 TypeScript 所带来的类型便利了。</p>
<p>上面的实现还有个问题是它规定了列表元素必需是对象,理所应当地就不能处理元始类型数组了,比如无法渲染 <code>['wayou','niuwayong']</code> 这样的输入。</p>
<p>下面使用泛型改造上面的列表组件,让它支持外部传入类型。</p>
<div class="highlight highlight-source-tsx"><pre><span class="pl-k">interface</span> <span class="pl-en">Props</span>&lt;<span class="pl-en">T</span>&gt; {
list<span class="pl-k">:</span> <span class="pl-en">T</span>[];
children<span class="pl-k">:</span> (<span class="pl-v">item</span><span class="pl-k">:</span> <span class="pl-en">T</span>, <span class="pl-v">index</span><span class="pl-k">:</span> <span class="pl-c1">number</span>) <span class="pl-k">=&gt;</span> <span class="pl-en">React</span>.<span class="pl-en">ReactNode</span>;
}
<p><span class="pl-k">function</span> List&lt;<span class="pl-en">T</span>&gt;({ <span class="pl-v">list</span>, <span class="pl-v">children</span> }<span class="pl-k">:</span> <span class="pl-en">Props</span>&lt;<span class="pl-en">T</span>&gt;) {<br>
<span class="pl-c"><span class="pl-c">//</span> 列表中其他逻辑...</span><br>
<span class="pl-k">return</span> &lt;<span class="pl-ent">div</span>&gt;<span class="pl-pse">{</span><span class="pl-smi">list</span>.<span class="pl-en">map</span>(<span class="pl-smi">children</span>)<span class="pl-pse">}</span>&lt;/<span class="pl-ent">div</span>&gt;;<br>
}</p></pre></div><p></p>
<p>改造后,列表元素的类型完全由使用的地方决定,作为列表组件,内部它无须关心,同时对于外部传递的 <code>children</code> 回调中 <code>item</code> 入参,类型也没有丢失。</p>
<p>使用改造后的泛型列表:</p>
<div class="highlight highlight-source-tsx"><pre><span class="pl-k">interface</span> <span class="pl-en">User</span> {
id<span class="pl-k">:</span> <span class="pl-c1">number</span>;
name<span class="pl-k">:</span> <span class="pl-c1">string</span>;
}
<span class="pl-k"><span class="pl-k">const</span></span> data<span class="pl-k">:</span> <span class="pl-en">User</span>[] <span class="pl-k">=</span> [
{
    id: <span class="pl-c1">1</span>,
    name: <span class="pl-s"><span class="pl-pds">"</span>wayou<span class="pl-pds">"</span></span>
},
{
    id: <span class="pl-c1">1</span>,
    name: <span class="pl-s"><span class="pl-pds">"</span>niuwayong<span class="pl-pds">"</span></span>
}
];
<p><span class="pl-k"><span class="pl-k">const</span></span> App <span class="pl-k">=</span> () <span class="pl-k">=&gt;</span> {<br>
<span class="pl-k">return</span> (<br>
&lt;<span class="pl-ent">div</span> <span class="pl-e">className</span><span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>App<span class="pl-pds">"</span></span>&gt;<br>
&lt;<span class="pl-ent"><span class="pl-c1">List</span></span> <span class="pl-e">list</span><span class="pl-k">=</span><span class="pl-pse">{</span><span class="pl-smi">data</span><span class="pl-pse">}</span>&gt;<br>
<span class="pl-pse">{</span><span class="pl-v">item</span> <span class="pl-k">=&gt;</span> {<br>
<span class="pl-c"><span class="pl-c">//</span> 😁 此处 <code>item</code> 类型为 <code>User</code></span><br>
<span class="pl-k">return</span> &lt;<span class="pl-ent">div</span> <span class="pl-e">key</span><span class="pl-k">=</span><span class="pl-pse">{</span><span class="pl-smi">item</span>.<span class="pl-c1">name</span><span class="pl-pse">}</span>&gt;<span class="pl-pse">{</span><span class="pl-smi">item</span>.<span class="pl-c1">name</span><span class="pl-pse">}</span>&lt;/<span class="pl-ent">div</span>&gt;;<br>
}<span class="pl-pse">}</span><br>
&lt;/<span class="pl-ent"><span class="pl-c1">List</span></span>&gt;<br>
&lt;<span class="pl-ent"><span class="pl-c1">List</span></span> <span class="pl-e">list</span><span class="pl-k">=</span><span class="pl-pse">{</span>[<span class="pl-s"><span class="pl-pds">"</span>wayou<span class="pl-pds">"</span></span>, <span class="pl-s"><span class="pl-pds">"</span>niuwayong<span class="pl-pds">"</span></span>]<span class="pl-pse">}</span>&gt;<br>
<span class="pl-pse">{</span><span class="pl-v">item</span> <span class="pl-k">=&gt;</span> {<br>
<span class="pl-c"><span class="pl-c">//</span> 😁 此处 <code>item</code> 类型为 <code>string</code></span><br>
<span class="pl-k">return</span> &lt;<span class="pl-ent">div</span> <span class="pl-e">key</span><span class="pl-k">=</span><span class="pl-pse">{</span><span class="pl-smi">item</span><span class="pl-pse">}</span>&gt;<span class="pl-pse">{</span><span class="pl-smi">item</span><span class="pl-pse">}</span>&lt;/<span class="pl-ent">div</span>&gt;;<br>
}<span class="pl-pse">}</span><br>
&lt;/<span class="pl-ent"><span class="pl-c1">List</span></span>&gt;<br>
&lt;/<span class="pl-ent">div</span>&gt;<br>
);<br>
};</p></pre></div></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_generic_components.html
頁: [1]
查看完整版本: React + TypeScript 实现泛型组件