真理之光必将照亮东方大地 發表於 2021-6-30 13:39:00

typescript之defaultProps

<h2 id="react-之-default-prop-values">React 之 Default Prop Values</h2>
<p>React 官方文档 - Default Prop Values</p>
<h3 id="方式一-class-类名属性名">方式一: Class 类名.属性名</h3>
<p>通过组件的 defaultProps 属性可为其 Props 指定默认值。</p>
<pre><code class="language-js">class Greeting extends React.Component {
render() {
    return &lt;h1&gt;Hello, {this.props.name}&lt;/h1&gt;;
}
}

// Specifies the default values for props:
Greeting.defaultProps = {
name: "Stranger",
};

// Renders "Hello, Stranger":
ReactDOM.render(&lt;Greeting /&gt;, document.getElementById("example"));
</code></pre>
<h3 id="方式二-static-属性名">方式二: static 属性名</h3>
<p>如果编译过程使用了 Babel 的 transform-class-properties 插件,你可以在 React 类组建中定义一个静态变量 defaultProps</p>
<pre><code class="language-js">class Greeting extends React.Component {
static defaultProps = {
    name: "stranger",
};

render() {
    return &lt;div&gt;Hello, {this.props.name}&lt;/div&gt;;
}
}
</code></pre>
<h2 id="加入-typescript">加入 TypeScript</h2>
<h3 id="方式一-class-类名属性名报错">方式一: Class 类名.属性名(报错)</h3>
<pre><code class="language-js">interface IProps {
name?: string;
}

class Greeting extends React.Component&lt;IProps, {}&gt; {
render() {
    return &lt;h1&gt;Hello, {this.props.name}&lt;/h1&gt;;
}
}

Greeting.defaultProps = {
name: "Stranger",
};
</code></pre>
<p>此时不支持直接通过类访问 defaultProps 来赋值以设置默认属性,因为 React.Component 类型上并没有该属性。</p>
<pre><code class="language-js">// ?Property 'defualtProps' does not exist on type 'typeof Greeting'.ts(2339)
Greeting.defualtProps = {
name: "stranger",
};
</code></pre>
<h3 id="方式二-static-属性名-1">方式二: static 属性名</h3>
<p>上面虽然实现了通过 defaultProps 来指定属性的默认值,但 <code>defaultProps 的类型是不受约束的</code>,和 Props 没有关联上。以至于我们可以在 defaultProps 里面放任何值,显然这是不科学的。</p>
<pre><code class="language-js">class Greeting extends React.Component&lt;IProps, {}&gt; {
static defaultProps = {
    name: "stranger",
    // 并不会报错
    foo: 1, // +
    bar: {}, // +
};
// ...
}
</code></pre>
<p>同时对于同一字段,我们不得不书写两次代码。一次是定义组件的 Props,另一次是在 defaultProps 里。如果属性有增删或名称有变更,两个地方都需要改。</p>
<h3 id="partialtypeof-defaultprops">✨<code>Partial&lt;typeof defaultProps&gt;</code></h3>
<p>为了后面演示方便,现在给组件新增一个必填属性 age:number。</p>
<pre><code class="language-js">interface IProps {
age: number;
name?: string;
}

class Greeting extends React.Component&lt;IProps, {}&gt; {
static defaultProps = {
    name: "Stranger",
};

render() {
    const { name, age } = this.props;
    return (
      &lt;h1&gt;
      Hello, {name}, my age is {age}
      &lt;/h1&gt;
    );
}
}
</code></pre>
<p>通过可选属性抽取出来,利用 <code>typeof 获取其类型和必传属性</code>结合来形成组件的 Props 可解决上面提到的两个问题。</p>
<p>所以优化后的代码成了:</p>
<pre><code class="language-js">const defaultProps = {
    name: 'Stranger'
};

type IProps {
    age: number;
} &amp; Partial&lt;typeof defaultProps&gt;;


class Greeting extends React.Component&lt;IProps, {}&gt; {
    static defaultProps = defaultProps;

    render() {
      const { name, age } = this.props;
      return (
      &lt;h1&gt;Hello, {name}, my age is {age}&lt;/h1&gt;
      );
    }
}
</code></pre>
<p>注意我们的 <code>Props 是通过和 typeof defaultProps 组合</code>而形成的,可选<code>属性中的 name 字段在整个代码中只书写了一次</code>。</p>
<p>当我们更新了 defaultProps 时整个组件的 Props 也同步更新,所以 <code>defaultProps 中的字段一定是组件所需要的字段</code>。</p>
<h2 id="默认值的判空检查优化">默认值的判空检查优化</h2>
<p>如果属性提供了默认值,在使用时,可不再需要判空,因为其一定是有值的。但 TypeScript 在编译时并不知道,因为有默认值的属性是被定义成可选的 ?。</p>
<p>比如我们尝试访问 name 属性的长度,</p>
<pre><code class="language-js">class Greeting extends React.Component&lt;IProps, {}&gt; {
static defaultProps = defaultProps;

render() {
    const { name } = this.props;
    return (
      &lt;div&gt;
      {/* ?Object is possibly 'undefined'.ts(2532) */}
      name length is {name.length}
      &lt;/div&gt;
    );
}
}
</code></pre>
<p>因为此时我们的 Props 实际上是:</p>
<pre><code class="language-js">type Props = {
age: number,
} &amp; Partial&lt;typeof defaultProps&gt;;
// 相当于:
type Props = {
age: number,
name?: string,
};
</code></pre>
<h3 id="非空判定符">非空判定符</h3>
<pre><code class="language-js">- name length is {name.length}

* name length is {name?.length}
</code></pre>
<p>这意味着每一处使用的地方都需要做类似的操作,当程序复杂起来时不太可控。但多数情况下应付日常使用,这样已经够了。</p>
<h3 id="-类型转换">✨ 类型转换</h3>
<p>因为组件内部有默认值的保证,所以字段不可能为空,因此,可对组件内部使用非空的属性类型来定义组件,而对外仍暴露原来的版本。</p>
<pre><code class="language-js">const Greeting = class extends React.Component&lt;
-IProps,
+IProps &amp; typeof defaultProps,
{}
&gt; {
static defaultProps = defaultProps;

render() {
    const { name } = this.props;
    return (
      &lt;div&gt;
-      name length is {name!.length}
+      name length is {name.length}
      &lt;/div&gt;
    );
}
-};
+} as React.ComponentClass&lt;IProps&gt;;
</code></pre>
<p>通过 <code>as React.ComponentClass&lt;Props&gt;</code> 的类型转换,对外使用 Greeting 时属性中 name 还是可选的,但组件内部实际使用的是 <code>Props &amp; typeof defaultProps</code>,而不是 <code>Partial&lt;T&gt;</code> 版本的,所以规避了字段可能为空的报错。</p>
<h3 id="通过高阶组件的方式封装默认属性的处理">通过高阶组件的方式封装默认属性的处理</h3>
<p>通过定义一个高阶组件比如 withDefaultProps 将需要默认属性的组件包裹,将默认值的处理放到高阶组件中,同样可解决上述问题。</p>
<pre><code class="language-js">function withDefaultProps&lt;P extends object, DP extends Partial&lt;P&gt;&gt;(
dp: DP,
component: React.ComponentType&lt;P&gt;,
) {
component.defaultProps = dp;
type RequiredProps = Omit&lt;P, keyof DP&gt;;
return (component as React.ComponentType&lt;any&gt;) as React.ComponentType&lt;
    RequiredProps &amp; DP
&gt;;
}
</code></pre>
<p>然后我们的组件则可以这样来写:</p>
<pre><code class="language-js">const defaultProps = {
name: "stranger",
};

interface Props {
name: string;
age: number;
}

const _Greeting = class extends React.Component&lt;Props, {}&gt; {
public render() {
    const { name } = this.props;
    return &lt;div&gt;name length is {name.length}&lt;/div&gt;;
}
};

export const Greeting = withDefaultProps(defaultProps, _Greeting);
</code></pre>
<p>这种方式就比较通用一些,将 withDefaultProps 抽取成一个公共组件,后续其他组件都可使用。但此种情况下就没有很好地利用已经定义好的默认值 defaultProps 中的字段,书写 Props 时还需要重复写一遍字段名。</p>
<h2 id="方法组建-defaultprops">方法组建 defaultProps</h2>
<pre><code class="language-js">interface IProps {
name?: string;
}

function Greeting(props: IProps) { {
   return &lt;h1&gt;Hello, {props.name}&lt;/h1&gt;;
}

Greeting.defaultProps = {
name: "Stranger",
};
</code></pre>
<p>或者</p>
<pre><code class="language-js">interface IProps {
name?: string;
}

function Greeting({ name = 'Stranger' }: IProps) { {
   return &lt;h1&gt;Hello, {name}&lt;/h1&gt;;
}
</code></pre>
<h2 id="理解-partial">理解 Partial</h2>
<pre><code class="language-js">type Partial&lt;T&gt; = {
    ?: T;
};
</code></pre>
<p>假设我们有一个定义 user 的接口,如下</p>
<pre><code class="language-js">interface IUser {
name: string
age: number
department: string
}
</code></pre>
<p>经过 Partial 类型转化后得到</p>
<pre><code class="language-js">type optional = Partial&lt;IUser&gt;;

// optional的结果如下
type optional = {
name?: string,
age?: number,
department?: string,
};
</code></pre>
<h3 id="那么-partialt-是如何实现类型转化的呢">那么 <code>Partial&lt;T&gt;</code> 是如何实现类型转化的呢?</h3>
<p>1.遍历入参 <code>T</code> ,获得每一对 <code>key, value</code></p>
<p>2.将每一对中的 <code>key</code> 变为可选,即添加 <code>?</code></p>
<p>3.希望得到的是由 <code>key, value</code> 组成的新类型</p>
<p>以上对应到 TypeScript 中是如何实现的呢?</p>
<h3 id="keyofintp">keyof、in、T</h3>
<p>对照最开始 Partial 的类型定义,能够捕捉到以下重要信息</p>
<ul>
<li>
<p><code>keyof</code> 是干什么的?</p>
</li>
<li>
<p><code>in</code> 是干什么的?</p>
</li>
<li>
<p><code></code>中括号是干什么的?</p>
</li>
<li>
<p><code>?</code> 是将该属性变为可选属性</p>
</li>
<li>
<p><code>T</code> 是干什么的?</p>
</li>
</ul>
<h4 id="keyof">keyof</h4>
<p><code>keyof</code>,即 <code>索引类型查询操作符</code>,我们可以将 <code>keyof</code> 作用于<code>泛型 T</code>上来获取泛型 T 上的<code>所有 public 属性名</code>构成的 <code>联合类型</code></p>
<blockquote>
<p>注意:"public、protected、private"修饰符不可出现在类型成员上<br>
例如:</p>
</blockquote>
<pre><code class="language-js">type unionKey = keyof IUser

// unionKey 结果如下,其获得了接口类型 IUser 中的所有属性名组成的联合类型
type unionKey = "name" | "age" | "department"
</code></pre>
<h4 id="in">in</h4>
<p>我们需要遍历 IUser ,这时候 <code>映射类型</code>就可以用上了,其语法为 <code></code></p>
<ul>
<li>P:类型变量,依次绑定到每个属性上,对应每个属性名的类型</li>
<li>Keys:字符串字面量构成的<code>联合类型</code>,表示一组属性名(的类型),可以联想到上文 keyof 的作用<br>
上述问题中 中括号是干什么的?这里也就很清晰了。</li>
</ul>
<h4 id="tp">T</h4>
<p>我们可以通过 keyof 查询索引类型的属性名,那么如何获取属性名对应的属性值类型呢?</p>
<p>这里就用到了 <code>索引访问操作符</code>,与 JavaScript 种访问属性值的操作类似,访问类型的操作符也是通过 [] 来访问的,即 T,其中”中括号“中的<code>P</code> 与 <code></code> 中的 P 相对应。</p>
<p>例如</p>
<pre><code class="language-js">type unionKey = keyof IUser // "name" | "age" | "department"

type values = IUser // string | number 属性值类型组成的联合类型
</code></pre>
<p>最后我们希望得到的是由多个 <code>key, value</code> 组成的新类型,故而在 <code>?: T</code>; 外部包上一层大括号。</p>
<p>到此我们解决遇到的所有问题,只需要逐步代入到 Partial 类型定义中即可。</p>
<h2 id="相关资源">相关资源</h2>
<ul>
<li>React docs - Default Prop Values</li>
<li>Default property value in React component using TypeScript</li>
<li>React, TypeScript and defaultProps dilemma</li>
</ul>


</div>
<div id="MySignature" role="contentinfo">
    本博客所记录的文章,主要是从网络收集的,有一些因为经过多次转载,所以出处已经不知,若是侵权,请通知我,我及时修改。本博客主要是用来记录我对所写文章的理解,若有错误,请大家指点,相互学习!<br><br>
来源:https://www.cnblogs.com/qiqi715/p/14954081.html
頁: [1]
查看完整版本: typescript之defaultProps