<span class="pl-k">type</span> <span class="pl-en">UnPromisify</span><<span class="pl-en">T</span>> <span class="pl-k">=</span> <span class="pl-en">T</span> <span class="pl-k">extends</span> <span class="pl-en">PromiseType</span><<span class="pl-k">infer</span> <span class="pl-en">U</span>> <span class="pl-k">?</span> <span class="pl-en">U</span> <span class="pl-k">:</span> <span class="pl-c1">never</span>;</pre></div>
<p>测试 <code>UnPromisify<T></code>:</p> <div class="highlight highlight-source-ts"><pre><span class="pl-k">async</span> <span class="pl-k">function</span> stringPromise() { <span class="pl-k">return</span> <span class="pl-s"><span class="pl-pds">"</span>string promise<span class="pl-pds">"</span></span>; }<span class="pl-k">async</span> <span class="pl-k">function</span> numberPromise() { <span class="pl-k">return</span> <span class="pl-c1">1</span>; }
<span class="pl-k">interface</span> <span class="pl-en">Person</span> { name<span class="pl-k">:</span> <span class="pl-c1">string</span>; age<span class="pl-k">:</span> <span class="pl-c1">number</span>; }
<span class="pl-k">async</span> <span class="pl-k">function</span> personPromise() { <span class="pl-k">return</span> { name: <span class="pl-s"><span class="pl-pds">"</span>Wayou<span class="pl-pds">"</span></span>, age: <span class="pl-c1">999</span> } <span class="pl-k">as</span> <span class="pl-en">Person</span>; }
<span class="pl-k">type</span> <span class="pl-en">extractStringPromise</span> <span class="pl-k">=</span> <span class="pl-en">UnPromisify</span><<span class="pl-k">typeof</span> <span class="pl-en">stringPromise</span>>; <span class="pl-c"><span class="pl-c">//</span> string</span>
<span class="pl-k">type</span> <span class="pl-en">extractNumberPromise</span> <span class="pl-k">=</span> <span class="pl-en">UnPromisify</span><<span class="pl-k">typeof</span> <span class="pl-en">numberPromise</span>>; <span class="pl-c"><span class="pl-c">//</span> number</span>
<span class="pl-k">type</span> <span class="pl-en">extractPersonPromise</span> <span class="pl-k">=</span> <span class="pl-en">UnPromisify</span><<span class="pl-k">typeof</span> <span class="pl-en">personPromise</span>>; <span class="pl-c"><span class="pl-c">//</span> Person</span></pre></div>
<h2>解析参数数组的类型</h2> <p>反解还可用在其他很多场景,比如解析函数入参的类型。</p> <div class="highlight highlight-source-ts"><pre><span class="pl-k">type</span> <span class="pl-en">VariadicFn</span><<span class="pl-en">A</span> <span class="pl-k">extends</span> <span class="pl-c1">any</span>\[\]> <span class="pl-k">=</span> (<span class="pl-k">...</span><span class="pl-v">args</span><span class="pl-k">:</span> <span class="pl-en">A</span>) <span class="pl-k">=></span> <span class="pl-c1">any</span>; <span class="pl-k">type</span> <span class="pl-en">ArgsType</span><<span class="pl-en">T</span>> <span class="pl-k">=</span> <span class="pl-en">T</span> <span class="pl-k">extends</span> <span class="pl-en">VariadicFn</span><<span class="pl-k">infer</span> <span class="pl-en">A</span>> <span class="pl-k">?</span> <span class="pl-en">A</span> <span class="pl-k">:</span> <span class="pl-c1">never</span>;<span class="pl-k">type</span> <span class="pl-en">Fn</span> <span class="pl-k">=</span> (<span class="pl-v">a</span><span class="pl-k">:</span> <span class="pl-c1">number</span>, <span class="pl-v">b</span><span class="pl-k">:</span> <span class="pl-c1">string</span>) <span class="pl-k">=></span> <span class="pl-c1">string</span>; <span class="pl-k">type</span> <span class="pl-en">Fn2Args</span> <span class="pl-k">=</span> <span class="pl-en">ArgsType</span><<span class="pl-en">Fn</span>>; <span class="pl-c"><span class="pl-c">//</span> [number, string]</span></pre></div>
<h2>另一个示例</h2> <p>假设我们编写了两个按钮组件,底层渲染的是 HTML 原生的 <code>button</code> 和 <code>a</code> 标签。为了组件最大化可定制,原生元素支持的属性该组件也需要支持,因此可这样来写组件的 props:</p> <div class="highlight highlight-source-tsx"><pre><span class="pl-k">type</span> <span class="pl-en">ButtonProps</span> <span class="pl-k">=</span> { color<span class="pl-k">:</span> <span class="pl-c1">string</span>; children<span class="pl-k">:</span> <span class="pl-en">React</span>.<span class="pl-en">ReactChildren</span>; } <span class="pl-k">&</span> <span class="pl-en">React</span>.<span class="pl-en">DetailedHTMLProps</span>< <span class="pl-en">React</span>.<span class="pl-en">ButtonHTMLAttributes</span><<span class="pl-en">HTMLButtonElement</span>>, <span class="pl-en">HTMLButtonElement</span> >;<span class="pl-k">type</span> <span class="pl-en">AnchorButtonProps</span> <span class="pl-k">=</span> { color<span class="pl-k">:</span> <span class="pl-c1">string</span>; disabled<span class="pl-k">:</span> <span class="pl-c1">boolean</span>; children<span class="pl-k">:</span> <span class="pl-en">React</span>.<span class="pl-en">ReactChildren</span>; } <span class="pl-k">&</span> <span class="pl-en">React</span>.<span class="pl-en">DetailedHTMLProps</span>< <span class="pl-en">React</span>.<span class="pl-en">AnchorHTMLAttributes</span><<span class="pl-en">HTMLAnchorElement</span>>, <span class="pl-en">HTMLAnchorElement</span> >;
<span class="pl-k">export</span> <span class="pl-k">function</span> Button({ <span class="pl-v">children</span>, <span class="pl-k">...</span><span class="pl-v">props</span> }<span class="pl-k">:</span> <span class="pl-en">ButtonProps</span>) { <span class="pl-c"><span class="pl-c">//</span>...</span> <span class="pl-k">return</span> <<span class="pl-ent">button</span> <span class="pl-pse">{</span><span class="pl-k">...</span><span class="pl-smi">props</span><span class="pl-pse">}</span>><span class="pl-pse">{</span><span class="pl-smi">children</span><span class="pl-pse">}</span></<span class="pl-ent">button</span>>; }
<span class="pl-k">export</span> <span class="pl-k">function</span> AnchorButton({ <span class="pl-v">children</span>, <span class="pl-k">...</span><span class="pl-v">props</span> }<span class="pl-k">:</span> <span class="pl-en">AnchorButtonProps</span>) { <span class="pl-c"><span class="pl-c">//</span>...</span> <span class="pl-k">return</span> <<span class="pl-ent">a</span> <span class="pl-pse">{</span><span class="pl-k">...</span><span class="pl-smi">props</span><span class="pl-pse">}</span>><span class="pl-pse">{</span><span class="pl-smi">children</span><span class="pl-pse">}</span></<span class="pl-ent">a</span>>; }</pre></div>
<p>单看 <code>Button</code> 和 <code>AnchorButton</code> 的属性,</p> <div class="highlight highlight-source-ts"><pre><span class="pl-k">type</span> <span class="pl-en">ButtonProps</span> <span class="pl-k">=</span> { color<span class="pl-k">:</span> <span class="pl-c1">string</span>; children<span class="pl-k">:</span> <span class="pl-en">React</span>.<span class="pl-en">ReactChildren</span>; } <span class="pl-k">&</span> <span class="pl-en">React</span>.<span class="pl-en">DetailedHTMLProps</span>< <span class="pl-en">React</span>.<span class="pl-en">ButtonHTMLAttributes</span><<span class="pl-en">HTMLButtonElement</span>>, <span class="pl-en">HTMLButtonElement</span> >;<span class="pl-k">type</span> <span class="pl-en">AnchorButtonProps</span> <span class="pl-k">=</span> { color<span class="pl-k">:</span> <span class="pl-c1">string</span>; disabled<span class="pl-k">:</span> <span class="pl-c1">boolean</span>; children<span class="pl-k">:</span> <span class="pl-en">React</span>.<span class="pl-en">ReactChildren</span>; } <span class="pl-k">&</span> <span class="pl-en">React</span>.<span class="pl-en">DetailedHTMLProps</span>< <span class="pl-en">React</span>.<span class="pl-en">AnchorHTMLAttributes</span><<span class="pl-en">HTMLAnchorElement</span>>, <span class="pl-en">HTMLAnchorElement</span> >;</pre></div>
<p>不难看出两者是有共性的,即可抽取成如下的形式:</p> <div class="highlight highlight-source-ts"><pre><span class="pl-k">type</span> <span class="pl-en">ExtendHTMLAttributes</span><<span class="pl-en">P</span>, <span class="pl-en">T</span>, <span class="pl-en">K</span>> <span class="pl-k">=</span> <span class="pl-en">P</span> <span class="pl-k">&</span> <span class="pl-en">React</span>.<span class="pl-en">DetailedHTMLProps</span><<span class="pl-en">T</span>, <span class="pl-en">K</span>>;</pre></div> <p>其中 <code>T</code> 呢又是 <code>T<K></code> 形式,即 <code>T</code> 中包含或有使用了 K。因此对使用者来说,如果传递了 <code>T<K></code> 形式,就没必要单独再传递一次 <code>K</code>,我们应该是能利用 <code>infer</code> 从 <code>T<K></code> 解析出 <code>K</code> 的。</p> <div class="highlight highlight-source-ts"><pre><span class="pl-c1">T</span> <span class="pl-smi">extends</span> <span class="pl-smi">React</span>.<span class="pl-smi">HtmlHTMLAttributes</span><span class="pl-k"><</span><span class="pl-smi">infer</span> <span class="pl-c1">K</span><span class="pl-k">></span> <span class="pl-k">?</span> <span class="pl-c1">K</span> <span class="pl-k">:</span> <span class="pl-c1">HTMLElement</span></pre></div> <p>所以抽取出来两种组件 Props 可公用的一个类型如下:</p> <div class="highlight highlight-source-ts"><pre><span class="pl-k">export</span> <span class="pl-k">type</span> <span class="pl-en">ExtendHTMLAttributes</span>< <span class="pl-c"><span class="pl-c">/\*\*</span> 组件自定义属性 <span class="pl-c">\*/</span></span> <span class="pl-en">P</span>, <span class="pl-c"><span class="pl-c">/\*\*</span> 原生 HTML 标签自有属性 <span class="pl-c">\*/</span></span> <span class="pl-en">T</span> <span class="pl-k">extends</span> <span class="pl-en">React</span>.<span class="pl-en">HtmlHTMLAttributes</span><<span class="pl-en">HTMLElement</span>> > <span class="pl-k">=</span> <span class="pl-en">P</span> <span class="pl-k">&</span> <span class="pl-en">React</span>.<span class="pl-en">DetailedHTMLProps</span>< <span class="pl-en">T</span>, <span class="pl-en">T</span> <span class="pl-k">extends</span> <span class="pl-en">React</span>.<span class="pl-en">HtmlHTMLAttributes</span><<span class="pl-k">infer</span> <span class="pl-en">K</span>> <span class="pl-k">?</span> <span class="pl-en">K</span> <span class="pl-k">:</span> <span class="pl-en">HTMLElement</span> >;</pre></div> <p>利用抽取的 <code>ExtendHTMLAttributes</code>,两种按钮的 Props 可重新书写成如下形式:</p> <div class="highlight highlight-source-ts"><pre><span class="pl-k">type</span> <span class="pl-en">ButtonProps</span> <span class="pl-k">=</span> <span class="pl-en">ExtendHTMLAttributes</span>< { color<span class="pl-k">:</span> <span class="pl-c1">string</span>; children<span class="pl-k">:</span> <span class="pl-en">React</span>.<span class="pl-en">ReactChildren</span>; }, <span class="pl-en">React</span>.<span class="pl-en">ButtonHTMLAttributes</span><<span class="pl-en">HTMLButtonElement</span>> >;<span class="pl-k">type</span> <span class="pl-en">AnchorButtonProps</span> <span class="pl-k">=</span> <span class="pl-en">ExtendHTMLAttributes</span>< { color<span class="pl-k">:</span> <span class="pl-c1">string</span>; disabled<span class="pl-k">:</span> <span class="pl-c1">boolean</span>; children<span class="pl-k">:</span> <span class="pl-en">React</span>.<span class="pl-en">ReactChildren</span>; }, <span class="pl-en">React</span>.<span class="pl-en">AnchorHTMLAttributes</span><<span class="pl-en">HTMLAnchorElement</span>> >;</pre></div>
<p>去掉了两者重叠的部分,看起来简洁了一些。关键后续编写其他组件时,如果想支持原生 HTML 属性,直接复用这里的 <code>ExtendHTMLAttributes</code> 类型即可。</p> <h2>相关资源</h2> <ul> <li><a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html" rel="nofollow">TypeScript 2.8 - Conditional Types</a></li> <li><a href="https://lorefnon.tech/2018/07/18/unwrapping-composite-types-in-typescript/" rel="nofollow">Unwrapping composite types in Typescript</a></li> </ul> </td> </tr> </tbody> </table>