TypeScript `infer` 关键字

<table class="d-block"> <tbody class="d-block"> <tr class="d-block"> <td class="d-block comment-body markdown-body js-comment-body"> <p>考察如下类型:</p> <div class="highlight highlight-source-ts"><pre><span class="pl-k">type</span> <span class="pl-en">PromiseType</span>&lt;<span class="pl-en">T</span>&gt; <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">=&gt;</span> <span class="pl-en">Promise</span>&lt;<span class="pl-en">T</span>&gt;;</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>&lt;<span class="pl-k">typeof</span> <span class="pl-en">stringPromise</span>&gt;; <span class="pl-c"><span class="pl-c">//</span> Promise&lt;string&gt;</span></pre></div> <p>确实拿到了方法的返回类型,不过是 <code>Promise&lt;string&gt;</code>。但其实是想要返回里面的 <code>string</code>,所以和我们想要的还差点意思。</p> <p>既然都能从一个方法反解其返回类型,肯定还能从 <code>Promsie&lt;T&gt;</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>&lt;<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">=&gt;</span> <span class="pl-c1">any</span>&gt; <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">=&gt;</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>&lt;<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">=&gt;</span> <span class="pl-c1">any</span>&gt; <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">=&gt;</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) =&gt; R</code> 则返回类型 <code>R</code>。</p> <p>但是这里类型 <code>R</code> 从何而来?讲道理,泛型中的变量需要外部指定,即 <code>RetrunType&lt;T,R&gt;</code>,但我们不是要得到 R 么,所以不能声明在这其中。这里 <code>infer</code> 便解决了这个问题。表达式右边的类型中,加上 <code>infer</code> 前缀我们便得到了反解出的类型变量 <code>R</code>,配合 <code>extends</code> 条件类型,可得到这个反解出的类型 <code>R</code>。这里 <code>R</code> 即为函数 <code>(...args: any) =&gt; R</code> 的返回类型。</p> <h2>反解 Promise</h2> <p>有了上面的基础,推而广之就很好反解 <code>Promise&lt;T&gt;</code> 中的 <code>T</code> 了。</p> <div class="highlight highlight-source-ts"><pre><span class="pl-k">type</span> <span class="pl-en">PromiseType</span>&lt;<span class="pl-en">T</span>&gt; <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">=&gt;</span> <span class="pl-en">Promise</span>&lt;<span class="pl-en">T</span>&gt;;

<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&lt;T&gt;</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>&lt;<span class="pl-en">A</span> <span class="pl-k">extends</span> <span class="pl-c1">any</span>\[\]&gt; <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">=&gt;</span> <span class="pl-c1">any</span>; <span class="pl-k">type</span> <span class="pl-en">ArgsType</span>&lt;<span class="pl-en">T</span>&gt; <span class="pl-k">=</span> <span class="pl-en">T</span> <span class="pl-k">extends</span> <span class="pl-en">VariadicFn</span>&lt;<span class="pl-k">infer</span> <span class="pl-en">A</span>&gt; <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">&amp;</span> <span class="pl-en">React</span>.<span class="pl-en">DetailedHTMLProps</span>&lt; <span class="pl-en">React</span>.<span class="pl-en">ButtonHTMLAttributes</span>&lt;<span class="pl-en">HTMLButtonElement</span>&gt;, <span class="pl-en">HTMLButtonElement</span> &gt;;

<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">&amp;</span> <span class="pl-en">React</span>.<span class="pl-en">DetailedHTMLProps</span>&lt; <span class="pl-en">React</span>.<span class="pl-en">ButtonHTMLAttributes</span>&lt;<span class="pl-en">HTMLButtonElement</span>&gt;, <span class="pl-en">HTMLButtonElement</span> &gt;;

<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>&lt;<span class="pl-en">P</span>, <span class="pl-en">T</span>, <span class="pl-en">K</span>&gt; <span class="pl-k">=</span> <span class="pl-en">P</span> <span class="pl-k">&amp;</span> <span class="pl-en">React</span>.<span class="pl-en">DetailedHTMLProps</span>&lt;<span class="pl-en">T</span>, <span class="pl-en">K</span>&gt;;</pre></div> <p>其中 <code>T</code> 呢又是 <code>T&lt;K&gt;</code> 形式,即 <code>T</code> 中包含或有使用了 K。因此对使用者来说,如果传递了 <code>T&lt;K&gt;</code> 形式,就没必要单独再传递一次 <code>K</code>,我们应该是能利用 <code>infer</code> 从 <code>T&lt;K&gt;</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">&lt;</span><span class="pl-smi">infer</span> <span class="pl-c1">K</span><span class="pl-k">&gt;</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>&lt; <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>&lt;<span class="pl-en">HTMLElement</span>&gt; &gt; <span class="pl-k">=</span> <span class="pl-en">P</span> <span class="pl-k">&amp;</span> <span class="pl-en">React</span>.<span class="pl-en">DetailedHTMLProps</span>&lt; <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>&lt;<span class="pl-k">infer</span> <span class="pl-en">K</span>&gt; <span class="pl-k">?</span> <span class="pl-en">K</span> <span class="pl-k">:</span> <span class="pl-en">HTMLElement</span> &gt;;</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>&lt; { 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>&lt;<span class="pl-en">HTMLButtonElement</span>&gt; &gt;;

<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>
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )