乖乖乖乖 發表於 2021-5-19 23:09:00

TypeScript 中限制对象键名的取值范围

<table class="d-block" data-paste-markdown-skip="">
<tbody class="d-block">
<tr class="d-block">
<td class="d-block comment-body markdown-body js-comment-body rgh-linkified-code">
<p>当我们使用 TypeScript 时,我们想利用它提供的类型系统限制代码的方方面面,对象的键值,也不例外。</p>
<p>譬如我们有个对象存储每个年级的人名,类型大概长这样:</p>
<div class="highlight highlight-source-ts position-relative">
<pre><span class="pl-k">type</span> <span class="pl-smi">Students</span> <span class="pl-c1">=</span> <span class="pl-smi">Record</span><span class="pl-kos">&lt;</span><span class="pl-smi">string</span><span class="pl-kos">,</span> <span class="pl-smi">string</span><span class="pl-kos">[</span><span class="pl-kos">]</span><span class="pl-kos">&gt;</span><span class="pl-kos">;</span></pre>
<div class="zeroclipboard-container position-absolute right-0 top-0">&nbsp;</div>
</div>
<p>理所当然地,数据就是长这样:</p>
<div class="highlight highlight-source-ts position-relative">
<pre><span class="pl-k">const</span> <span class="pl-s1">students</span>: <span class="pl-smi">Students</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span>
<span class="pl-c1">Freshman</span>: <span class="pl-kos">[</span><span class="pl-s">"David"</span><span class="pl-kos">,</span> <span class="pl-s">"John"</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-c1">sophomore</span>: <span class="pl-kos">[</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-c1">Junior</span>: <span class="pl-kos">[</span><span class="pl-s">"Lily"</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-c1">Senior</span>: <span class="pl-kos">[</span><span class="pl-s">"Tom"</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">;</span></pre>
<div class="zeroclipboard-container position-absolute right-0 top-0">&nbsp;</div>
</div>
<h2>限制对象键名为枚举</h2>
<p>上面数据类型的问题是,年级是有限的几种可值取,而该对象上可任意添加属性,这样显得数据不够纯粹。</p>
<p>所以我们新增枚举,列出可取的值:</p>
<div class="highlight highlight-source-ts position-relative">
<pre><span class="pl-k">export</span> <span class="pl-k">enum</span> <span class="pl-smi">Grade</span> <span class="pl-kos">{</span>
<span class="pl-c1">Freshman</span><span class="pl-kos">,</span>
<span class="pl-c1">sophomore</span><span class="pl-kos">,</span>
<span class="pl-c1">Junior</span><span class="pl-kos">,</span>
<span class="pl-c1">Senior</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span></pre>
<div class="zeroclipboard-container position-absolute right-0 top-0">&nbsp;</div>
</div>
<p>现在,把对象的键名限制为上面枚举就行了。</p>
<div class="highlight highlight-source-diff position-relative">
<pre><span class="pl-md"><span class="pl-md">-</span> type Students = Record&lt;string, string[]&gt;;</span>
<span class="pl-mi1"><span class="pl-mi1">+</span> type Students = Record&lt;Grade, string[]&gt;;</span></pre>
<div class="zeroclipboard-container position-absolute right-0 top-0">&nbsp;</div>
</div>
<p>这样我们的数据可写成这样:</p>
<div class="highlight highlight-source-ts position-relative">
<pre><span class="pl-k">const</span> <span class="pl-s1">students</span>: <span class="pl-smi">Students</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span>
<span class="pl-kos">[</span><span class="pl-smi">Grade</span><span class="pl-kos">.</span><span class="pl-c1">Freshman</span><span class="pl-kos">]</span>: <span class="pl-kos">[</span><span class="pl-s">"David"</span><span class="pl-kos">,</span> <span class="pl-s">"John"</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-kos">[</span><span class="pl-smi">Grade</span><span class="pl-kos">.</span><span class="pl-c1">sophomore</span><span class="pl-kos">]</span>: <span class="pl-kos">[</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-kos">[</span><span class="pl-smi">Grade</span><span class="pl-kos">.</span><span class="pl-c1">Junior</span><span class="pl-kos">]</span>: <span class="pl-kos">[</span><span class="pl-s">"Lily"</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-kos">[</span><span class="pl-smi">Grade</span><span class="pl-kos">.</span><span class="pl-c1">Senior</span><span class="pl-kos">]</span>: <span class="pl-kos">[</span><span class="pl-s">"Tom"</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-c">// ❌ Object literal may only specify known properties, and 'blah' does not exist in type 'Students'.ts(2322)</span>
<span class="pl-c1">blah</span>: <span class="pl-kos">[</span><span class="pl-s">"some one"</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">;</span></pre>
<div class="zeroclipboard-container position-absolute right-0 top-0">&nbsp;</div>
</div>
<p>这样,限制住了对象身上键名的范围,可以看到如果添加一个枚举之外的键会报错。</p>
<h2>更加语义化的枚举值</h2>
<p>但上面的做法还是有不妥之处,因为枚举值默认是从 0 开始的数字,这样,作为键值就不够语义了,这点从访问对象的属性时体现了出来:</p>
<p><img src="https://user-images.githubusercontent.com/3783096/118772844-c85c8f80-b8b6-11eb-87b3-0a77485c95f4.png" alt="1" width="899" style="max-width: 100%"></p>
<p>修正我们的枚举,用更加语义的文本作为其值:</p>
<div class="highlight highlight-source-ts position-relative">
<pre><span class="pl-k">export</span> <span class="pl-k">enum</span> <span class="pl-smi">Grade</span> <span class="pl-kos">{</span>
<span class="pl-c1">Freshman</span> <span class="pl-c1">=</span> <span class="pl-s">"Freshman"</span><span class="pl-kos">,</span>
<span class="pl-c1">sophomore</span> <span class="pl-c1">=</span> <span class="pl-s">"sophomore"</span><span class="pl-kos">,</span>
<span class="pl-c1">Junior</span> <span class="pl-c1">=</span> <span class="pl-s">"Junior"</span><span class="pl-kos">,</span>
<span class="pl-c1">Senior</span> <span class="pl-c1">=</span> <span class="pl-s">"Senior"</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span></pre>
<div class="zeroclipboard-container position-absolute right-0 top-0">&nbsp;</div>
</div>
<p>此时再使用该枚举时,得到的就不是无意义的数字了。</p>
<p><img src="https://user-images.githubusercontent.com/3783096/118772877-d1e5f780-b8b6-11eb-9745-b6a46cffed72.png" alt="2" width="899" style="max-width: 100%"></p>
<p>如果你愿意,枚举值也可以是中文,</p>
<div class="highlight highlight-source-ts position-relative">
<pre><span class="pl-k">export</span> <span class="pl-k">enum</span> <span class="pl-smi">Grade</span> <span class="pl-kos">{</span>
<span class="pl-c1">Freshman</span> <span class="pl-c1">=</span> <span class="pl-s">"大一萌新"</span><span class="pl-kos">,</span>
<span class="pl-c1">sophomore</span> <span class="pl-c1">=</span> <span class="pl-s">"大二学弟"</span><span class="pl-kos">,</span>
<span class="pl-c1">Junior</span> <span class="pl-c1">=</span> <span class="pl-s">"大三学妹"</span><span class="pl-kos">,</span>
<span class="pl-c1">Senior</span> <span class="pl-c1">=</span> <span class="pl-s">"大四老司机"</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span></pre>
<div class="zeroclipboard-container position-absolute right-0 top-0">&nbsp;</div>
</div>
<p>使用时也是没任何问题的:</p>
<p><img src="https://user-images.githubusercontent.com/3783096/118772905-d90d0580-b8b6-11eb-9dac-a1eb0db7e24a.png" alt="3" width="899" style="max-width: 100%"></p>
<h2>键值可选</h2>
<p>上面的类型定义还有个问题,即,它要求使用时对象包含枚举中所有值,比如 <code>sophomore</code> 这个年级中并没有人,可以不写,但会报错。</p>
<div class="highlight highlight-source-ts position-relative">
<pre><span class="pl-c">// ❌ Property 'sophomore' is missing in type '{ Freshman: string[]; Junior: string[]; Senior: string[]; }' but required in type 'Students'.ts(2741)</span>
<span class="pl-k">const</span> <span class="pl-s1">students</span>: <span class="pl-smi">Students</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span>
<span class="pl-kos">[</span><span class="pl-smi">Grade</span><span class="pl-kos">.</span><span class="pl-c1">Freshman</span><span class="pl-kos">]</span>: <span class="pl-kos">[</span><span class="pl-s">"David"</span><span class="pl-kos">,</span> <span class="pl-s">"John"</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-c">// : [],</span>
<span class="pl-kos">[</span><span class="pl-smi">Grade</span><span class="pl-kos">.</span><span class="pl-c1">Junior</span><span class="pl-kos">]</span>: <span class="pl-kos">[</span><span class="pl-s">"Lily"</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-kos">[</span><span class="pl-smi">Grade</span><span class="pl-kos">.</span><span class="pl-c1">Senior</span><span class="pl-kos">]</span>: <span class="pl-kos">[</span><span class="pl-s">"Tom"</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">;</span></pre>
<div class="zeroclipboard-container position-absolute right-0 top-0">&nbsp;</div>
</div>
<p>所以,优化类型为可选:</p>
<div class="highlight highlight-source-ts position-relative">
<pre><span class="pl-k">type</span> <span class="pl-smi">Students</span> <span class="pl-c1">=</span> <span class="pl-smi">Partial</span><span class="pl-kos">&lt;</span><span class="pl-smi">Record</span><span class="pl-kos">&lt;</span><span class="pl-smi">Grade</span><span class="pl-kos">,</span> <span class="pl-smi">string</span><span class="pl-kos">[</span><span class="pl-kos">]</span><span class="pl-kos">&gt;</span><span class="pl-kos">&gt;</span><span class="pl-kos">;</span></pre>
<div class="zeroclipboard-container position-absolute right-0 top-0">&nbsp;</div>
</div>
<h2>限制对象的键名为数组中的值</h2>
<p>假若可选的值不是通过枚举定义,而是来自一个数组,</p>
<div class="highlight highlight-source-ts position-relative">
<pre><span class="pl-k">const</span> <span class="pl-s1">grades</span> <span class="pl-c1">=</span> <span class="pl-kos">[</span><span class="pl-s">"Freshman"</span><span class="pl-kos">,</span> <span class="pl-s">"sophomore"</span><span class="pl-kos">,</span> <span class="pl-s">"Junior"</span><span class="pl-kos">,</span> <span class="pl-s">"Senior"</span><span class="pl-kos">]</span><span class="pl-kos">;</span></pre>
<div class="zeroclipboard-container position-absolute right-0 top-0">&nbsp;</div>
</div>
<p>这意味着我们需要提取数组中的值形成一个联合类型。</p>
<p>首先利用const assertions 把数组转元组(Tuple)类型,</p>
<div class="highlight highlight-source-ts position-relative">
<pre><span class="pl-k">const</span> <span class="pl-s1">grades</span> <span class="pl-c1">=</span> <span class="pl-kos">&lt;</span><span class="pl-smi">const</span><span class="pl-kos">&gt;</span><span class="pl-kos">[</span><span class="pl-s">"Freshman"</span><span class="pl-kos">,</span> <span class="pl-s">"sophomore"</span><span class="pl-kos">,</span> <span class="pl-s">"Junior"</span><span class="pl-kos">,</span> <span class="pl-s">"Senior"</span><span class="pl-kos">]</span><span class="pl-kos">;</span></pre>
<div class="zeroclipboard-container position-absolute right-0 top-0">&nbsp;</div>
</div>
<p>再利用 <code>typeof</code> 和 Lookup Types 得到最终的联合类型:</p>
<div class="highlight highlight-source-ts position-relative">
<pre><span class="pl-c">// 实际为 type Keys = "Freshman" | "sophomore" | "Junior" | "Senior"</span>
<span class="pl-k">type</span> <span class="pl-smi">Keys</span> <span class="pl-c1">=</span> <span class="pl-k">typeof</span> <span class="pl-s1">grades</span><span class="pl-kos">[</span><span class="pl-s1">number</span><span class="pl-kos">]</span><span class="pl-kos">;</span></pre>
<div class="zeroclipboard-container position-absolute right-0 top-0">&nbsp;</div>
</div>
<p>最后数据类型和数据可写成:</p>
<div class="highlight highlight-source-ts position-relative">
<pre><span class="pl-k">type</span> <span class="pl-smi">Students</span> <span class="pl-c1">=</span> <span class="pl-smi">Partial</span><span class="pl-kos">&lt;</span><span class="pl-smi">Record</span><span class="pl-kos">&lt;</span><span class="pl-smi">Keys</span><span class="pl-kos">,</span> <span class="pl-smi">string</span><span class="pl-kos">[</span><span class="pl-kos">]</span><span class="pl-kos">&gt;</span><span class="pl-kos">&gt;</span><span class="pl-kos">;</span>

<span class="pl-k">const</span> <span class="pl-s1">students</span>: <span class="pl-smi">Students</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span>
<span class="pl-c1">Freshman</span>: <span class="pl-kos">[</span><span class="pl-s">"David"</span><span class="pl-kos">,</span> <span class="pl-s">"John"</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-c1">Junior</span>: <span class="pl-kos">[</span><span class="pl-s">"Lily"</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-c1">Senior</span>: <span class="pl-kos">[</span><span class="pl-s">"Tom"</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">;</span></pre>
<div class="zeroclipboard-container position-absolute right-0 top-0">&nbsp;</div>
</div>
<p>须知这种形式下,对象的 key 与原数组中元素其实没有语法层面的关联,即,编辑器的「跳转定义」是不可用的。</p>
<p><img src="https://user-images.githubusercontent.com/3783096/118773106-0f4a8500-b8b7-11eb-859d-e9e6a357deb8.png" alt="Screen Shot 2021-05-19 at 3 29 27 PM" width="899" style="max-width: 100%"></p>
<p>尽量还是保持代码之间的关联才能体现出 TypeScript 的作用,所以像这种只有类型约束而无法建立关联的操作是不建议的。</p>
<h1>相关资源</h1>
<ul>
<li>const assertions</li>
<li><code>keyof</code> and Lookup Types</li>
</ul>
</td>
</tr>
<tr class="d-block pl-3 pr-3 pb-3 js-comment-body-error">
<td class="d-block">
<div class="flash flash-warn">
<p class="mb-1"> The text was updated successfully, but these errors were encountered:</p>
</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/14787954.html
頁: [1]
查看完整版本: TypeScript 中限制对象键名的取值范围