TypeScript `infer` 关键字
<table class="d-block"><tbody class="d-block">
<tr class="d-block">
<td class="d-block comment-body markdown-bodyjs-comment-body">
<p>考察如下类型:</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-k">type</span> <span class="pl-en">PromiseType</span><<span class="pl-en">T</span>> <span class="pl-k">=</span> (<span class="pl-v">args</span><span class="pl-k">:</span> <span class="pl-c1">any</span>[]) <span class="pl-k">=></span> <span class="pl-en">Promise</span><<span class="pl-en">T</span>>;</pre></div>
<p>那么对于符合上面类型的一个方法,如何得知其 Promise 返回的类型?</p>
<p>譬如对于这么一个返回 <code>string</code> 类型的 Promise:</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>;
}</pre></div>
<h2><code>RetrunType</code></h2>
<p>如果你对 TypeScript 不是那么陌生,可能知道官方类型库中提供了 <code>RetrunType</code> 可获取方法的返回类型,其用法如下:</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-k">type</span> <span class="pl-en">stringPromiseReturnType</span> <span class="pl-k">=</span> <span class="pl-en">ReturnType</span><<span class="pl-k">typeof</span> <span class="pl-en">stringPromise</span>>; <span class="pl-c"><span class="pl-c">//</span> Promise<string></span></pre></div>
<p>确实拿到了方法的返回类型,不过是 <code>Promise<string></code>。但其实是想要返回里面的 <code>string</code>,所以和我们想要的还差点意思。</p>
<p>既然都能从一个方法反解其返回类型,肯定还能从 <code>Promsie<T></code> 中反解出 <code>T</code>。所以不不妨看看 <code>ReturnType</code> 的定义:</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-c"><span class="pl-c">/**</span></span>
<span class="pl-c"> * Obtain the return type of a function type</span>
<span class="pl-c"> <span class="pl-c">*/</span></span>
<span class="pl-k">type</span> <span class="pl-en">ReturnType</span><<span class="pl-en">T</span> <span class="pl-k">extends</span> (<span class="pl-k">...</span><span class="pl-v">args</span><span class="pl-k">:</span> <span class="pl-c1">any</span>) <span class="pl-k">=></span> <span class="pl-c1">any</span>> <span class="pl-k">=</span> <span class="pl-en">T</span> <span class="pl-k">extends</span> (<span class="pl-k">...</span><span class="pl-v">args</span><span class="pl-k">:</span> <span class="pl-c1">any</span>) <span class="pl-k">=></span> <span class="pl-k">infer</span> <span class="pl-en">R</span> <span class="pl-k">?</span> <span class="pl-en">R</span> <span class="pl-k">:</span> <span class="pl-c1">any</span>;</pre></div>
<p><kbd>F12</kbd> 一看,果然发现了点什么,这里使用了 <code>infer</code> 关键字。</p>
<h2>条件类型及 <code>infer</code></h2>
<p>上面 <code>T extends U ? X : Y</code> 的形式为条件类型(Conditional Types),即,如果类型 <code>T</code> 能够赋值给类型 <code>U</code>,那么该表达式返回类型 <code>X</code>,否则返回类型 <code>Y</code>。</p>
<p>所以,考察 <code>ReturnType</code>的定义,</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-k">type</span> <span class="pl-en">ReturnType</span><<span class="pl-en">T</span> <span class="pl-k">extends</span> (<span class="pl-k">...</span><span class="pl-v">args</span><span class="pl-k">:</span> <span class="pl-c1">any</span>) <span class="pl-k">=></span> <span class="pl-c1">any</span>> <span class="pl-k">=</span> <span class="pl-en">T</span> <span class="pl-k">extends</span> (<span class="pl-k">...</span><span class="pl-v">args</span><span class="pl-k">:</span> <span class="pl-c1">any</span>) <span class="pl-k">=></span> <span class="pl-k">infer</span> <span class="pl-en">R</span> <span class="pl-k">?</span> <span class="pl-en">R</span> <span class="pl-k">:</span> <span class="pl-c1">any</span>;</pre></div>
<p>如果传入的类型 <code>T</code> 能够赋值给 <code>(...args: any) => R</code> 则返回类型 <code>R</code>。</p>
<p>但是这里类型 <code>R</code> 从何而来?讲道理,泛型中的变量需要外部指定,即 <code>RetrunType<T,R></code>,但我们不是要得到 R 么,所以不能声明在这其中。这里 <code>infer</code> 便解决了这个问题。表达式右边的类型中,加上 <code>infer</code> 前缀我们便得到了反解出的类型变量 <code>R</code>,配合 <code>extends</code> 条件类型,可得到这个反解出的类型 <code>R</code>。这里 <code>R</code> 即为函数 <code>(...args: any) => R</code> 的返回类型。</p>
<h2>反解 Promise</h2>
<p>有了上面的基础,推而广之就很好反解 <code>Promise<T></code> 中的 <code>T</code> 了。</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-k">type</span> <span class="pl-en">PromiseType</span><<span class="pl-en">T</span>> <span class="pl-k">=</span> (<span class="pl-v">args</span><span class="pl-k">:</span> <span class="pl-c1">any</span>[]) <span class="pl-k">=></span> <span class="pl-en">Promise</span><<span class="pl-en">T</span>>;
<p><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>;</p></pre></div><p></p>
<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>;
}
<p><span class="pl-k">async</span> <span class="pl-k">function</span> numberPromise() {<br>
<span class="pl-k">return</span> <span class="pl-c1">1</span>;<br>
}</p>
<p><span class="pl-k">interface</span> <span class="pl-en">Person</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">async</span> <span class="pl-k">function</span> personPromise() {<br>
<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>;<br>
}</p>
<p><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></p>
<p><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></p>
<p><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></p></pre></div><p></p>
<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>;
<p><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>;<br>
<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> </span></p></pre></div><p></p>
<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>
>;
<p><span class="pl-k">type</span> <span class="pl-en">AnchorButtonProps</span> <span class="pl-k">=</span> {<br>
color<span class="pl-k">:</span> <span class="pl-c1">string</span>;<br>
disabled<span class="pl-k">:</span> <span class="pl-c1">boolean</span>;<br>
children<span class="pl-k">:</span> <span class="pl-en">React</span>.<span class="pl-en">ReactChildren</span>;<br>
} <span class="pl-k">&</span> <span class="pl-en">React</span>.<span class="pl-en">DetailedHTMLProps</span><<br>
<span class="pl-en">React</span>.<span class="pl-en">AnchorHTMLAttributes</span><<span class="pl-en">HTMLAnchorElement</span>>,<br>
<span class="pl-en">HTMLAnchorElement</span><br>
>;</p>
<p><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>) {<br>
<span class="pl-c"><span class="pl-c">//</span>...</span><br>
<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>>;<br>
}</p>
<p><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>) {<br>
<span class="pl-c"><span class="pl-c">//</span>...</span><br>
<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>>;<br>
}</p></pre></div><p></p>
<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>
>;
<p><span class="pl-k">type</span> <span class="pl-en">AnchorButtonProps</span> <span class="pl-k">=</span> {<br>
color<span class="pl-k">:</span> <span class="pl-c1">string</span>;<br>
disabled<span class="pl-k">:</span> <span class="pl-c1">boolean</span>;<br>
children<span class="pl-k">:</span> <span class="pl-en">React</span>.<span class="pl-en">ReactChildren</span>;<br>
} <span class="pl-k">&</span> <span class="pl-en">React</span>.<span class="pl-en">DetailedHTMLProps</span><<br>
<span class="pl-en">React</span>.<span class="pl-en">AnchorHTMLAttributes</span><<span class="pl-en">HTMLAnchorElement</span>>,<br>
<span class="pl-en">HTMLAnchorElement</span><br>
>;</p></pre></div><p></p>
<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>>
>;
<p><span class="pl-k">type</span> <span class="pl-en">AnchorButtonProps</span> <span class="pl-k">=</span> <span class="pl-en">ExtendHTMLAttributes</span><<br>
{<br>
color<span class="pl-k">:</span> <span class="pl-c1">string</span>;<br>
disabled<span class="pl-k">:</span> <span class="pl-c1">boolean</span>;<br>
children<span class="pl-k">:</span> <span class="pl-en">React</span>.<span class="pl-en">ReactChildren</span>;<br>
},<br>
<span class="pl-en">React</span>.<span class="pl-en">AnchorHTMLAttributes</span><<span class="pl-en">HTMLAnchorElement</span>><br>
>;</p></pre></div><p></p>
<p>去掉了两者重叠的部分,看起来简洁了一些。关键后续编写其他组件时,如果想支持原生 HTML 属性,直接复用这里的 <code>ExtendHTMLAttributes</code> 类型即可。</p>
<h2>相关资源</h2>
<ul>
<li>TypeScript 2.8 - Conditional Types</li>
<li>Unwrapping composite types in Typescript</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/typescript_infer.html
頁:
[1]