zoukankan      html  css  js  c++  java
  • typescript之defaultProps

    React 之 Default Prop Values

    React 官方文档 - Default Prop Values

    方式一: Class 类名.属性名

    通过组件的 defaultProps 属性可为其 Props 指定默认值。

    class Greeting extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    
    // Specifies the default values for props:
    Greeting.defaultProps = {
      name: "Stranger",
    };
    
    // Renders "Hello, Stranger":
    ReactDOM.render(<Greeting />, document.getElementById("example"));
    

    方式二: static 属性名

    如果编译过程使用了 Babel 的 transform-class-properties 插件,你可以在 React 类组建中定义一个静态变量 defaultProps

    class Greeting extends React.Component {
      static defaultProps = {
        name: "stranger",
      };
    
      render() {
        return <div>Hello, {this.props.name}</div>;
      }
    }
    

    加入 TypeScript

    方式一: Class 类名.属性名(报错)

    interface IProps {
      name?: string;
    }
    
    class Greeting extends React.Component<IProps, {}> {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    
    Greeting.defaultProps = {
      name: "Stranger",
    };
    

    此时不支持直接通过类访问 defaultProps 来赋值以设置默认属性,因为 React.Component 类型上并没有该属性。

    // ?Property 'defualtProps' does not exist on type 'typeof Greeting'.ts(2339)
    Greeting.defualtProps = {
      name: "stranger",
    };
    

    方式二: static 属性名

    上面虽然实现了通过 defaultProps 来指定属性的默认值,但 defaultProps 的类型是不受约束的,和 Props 没有关联上。以至于我们可以在 defaultProps 里面放任何值,显然这是不科学的。

    class Greeting extends React.Component<IProps, {}> {
      static defaultProps = {
        name: "stranger",
        // 并不会报错
        foo: 1, // +
        bar: {}, // +
      };
      // ...
    }
    

    同时对于同一字段,我们不得不书写两次代码。一次是定义组件的 Props,另一次是在 defaultProps 里。如果属性有增删或名称有变更,两个地方都需要改。

    Partial<typeof defaultProps>

    为了后面演示方便,现在给组件新增一个必填属性 age:number。

    interface IProps {
      age: number;
      name?: string;
    }
    
    class Greeting extends React.Component<IProps, {}> {
      static defaultProps = {
        name: "Stranger",
      };
    
      render() {
        const { name, age } = this.props;
        return (
          <h1>
            Hello, {name}, my age is {age}
          </h1>
        );
      }
    }
    

    通过可选属性抽取出来,利用 typeof 获取其类型和必传属性结合来形成组件的 Props 可解决上面提到的两个问题。

    所以优化后的代码成了:

    const defaultProps = {
        name: 'Stranger'
    };
    
    type IProps {
        age: number;
    } & Partial<typeof defaultProps>;
    
    
    class Greeting extends React.Component<IProps, {}> {
        static defaultProps = defaultProps;
    
        render() {
            const { name, age } = this.props;
            return (
            <h1>Hello, {name}, my age is {age}</h1>
            );
        }
    }
    

    注意我们的 Props 是通过和 typeof defaultProps 组合而形成的,可选属性中的 name 字段在整个代码中只书写了一次

    当我们更新了 defaultProps 时整个组件的 Props 也同步更新,所以 defaultProps 中的字段一定是组件所需要的字段

    默认值的判空检查优化

    如果属性提供了默认值,在使用时,可不再需要判空,因为其一定是有值的。但 TypeScript 在编译时并不知道,因为有默认值的属性是被定义成可选的 ?。

    比如我们尝试访问 name 属性的长度,

    class Greeting extends React.Component<IProps, {}> {
      static defaultProps = defaultProps;
    
      render() {
        const { name } = this.props;
        return (
          <div>
            {/* ?Object is possibly 'undefined'.ts(2532) */}
            name length is {name.length}
          </div>
        );
      }
    }
    

    因为此时我们的 Props 实际上是:

    type Props = {
      age: number,
    } & Partial<typeof defaultProps>;
    // 相当于:
    type Props = {
      age: number,
      name?: string,
    };
    

    非空判定符

    - name length is {name.length}
    
    * name length is {name?.length}
    

    这意味着每一处使用的地方都需要做类似的操作,当程序复杂起来时不太可控。但多数情况下应付日常使用,这样已经够了。

    ✨ 类型转换

    因为组件内部有默认值的保证,所以字段不可能为空,因此,可对组件内部使用非空的属性类型来定义组件,而对外仍暴露原来的版本。

    const Greeting = class extends React.Component<
    -  IProps,
    +  IProps & typeof defaultProps,
      {}
    > {
      static defaultProps = defaultProps;
    
      render() {
        const { name } = this.props;
        return (
          <div>
    -        name length is {name!.length}
    +        name length is {name.length}
          </div>
        );
      }
    -};
    +} as React.ComponentClass<IProps>;
    

    通过 as React.ComponentClass<Props> 的类型转换,对外使用 Greeting 时属性中 name 还是可选的,但组件内部实际使用的是 Props & typeof defaultProps,而不是 Partial<T> 版本的,所以规避了字段可能为空的报错。

    通过高阶组件的方式封装默认属性的处理

    通过定义一个高阶组件比如 withDefaultProps 将需要默认属性的组件包裹,将默认值的处理放到高阶组件中,同样可解决上述问题。

    function withDefaultProps<P extends object, DP extends Partial<P>>(
      dp: DP,
      component: React.ComponentType<P>,
    ) {
      component.defaultProps = dp;
      type RequiredProps = Omit<P, keyof DP>;
      return (component as React.ComponentType<any>) as React.ComponentType<
        RequiredProps & DP
      >;
    }
    

    然后我们的组件则可以这样来写:

    const defaultProps = {
      name: "stranger",
    };
    
    interface Props {
      name: string;
      age: number;
    }
    
    const _Greeting = class extends React.Component<Props, {}> {
      public render() {
        const { name } = this.props;
        return <div>name length is {name.length}</div>;
      }
    };
    
    export const Greeting = withDefaultProps(defaultProps, _Greeting);
    

    这种方式就比较通用一些,将 withDefaultProps 抽取成一个公共组件,后续其他组件都可使用。但此种情况下就没有很好地利用已经定义好的默认值 defaultProps 中的字段,书写 Props 时还需要重复写一遍字段名。

    方法组建 defaultProps

    interface IProps {
      name?: string;
    }
    
    function Greeting(props: IProps) { {
       return <h1>Hello, {props.name}</h1>;
    }
    
    Greeting.defaultProps = {
      name: "Stranger",
    };
    

    或者

    interface IProps {
      name?: string;
    }
    
    function Greeting({ name = 'Stranger' }: IProps) { {
       return <h1>Hello, {name}</h1>;
    }
    

    理解 Partial

    type Partial<T> = {
        [P in keyof T]?: T[P];
    };
    

    假设我们有一个定义 user 的接口,如下

    interface IUser {
      name: string
      age: number
      department: string
    }
    

    经过 Partial 类型转化后得到

    type optional = Partial<IUser>;
    
    // optional的结果如下
    type optional = {
      name?: string,
      age?: number,
      department?: string,
    };
    

    那么 Partial<T> 是如何实现类型转化的呢?

    1.遍历入参 T ,获得每一对 key, value

    2.将每一对中的 key 变为可选,即添加 ?

    3.希望得到的是由 key, value 组成的新类型

    以上对应到 TypeScript 中是如何实现的呢?

    keyof、in、T[P]

    对照最开始 Partial 的类型定义,能够捕捉到以下重要信息

    • keyof 是干什么的?

    • in 是干什么的?

    • [P in keyof T]中括号是干什么的?

    • ? 是将该属性变为可选属性

    • T[P] 是干什么的?

    keyof

    keyof,即 索引类型查询操作符,我们可以将 keyof 作用于泛型 T上来获取泛型 T 上的所有 public 属性名构成的 联合类型

    注意:"public、protected、private"修饰符不可出现在类型成员上
    例如:

    type unionKey = keyof IUser
    
    // unionKey 结果如下,其获得了接口类型 IUser 中的所有属性名组成的联合类型
    type unionKey = "name" | "age" | "department"
    

    in

    我们需要遍历 IUser ,这时候 映射类型就可以用上了,其语法为 [P in Keys]

    • P:类型变量,依次绑定到每个属性上,对应每个属性名的类型
    • Keys:字符串字面量构成的联合类型,表示一组属性名(的类型),可以联想到上文 keyof 的作用
      上述问题中 [P in keyof T] 中括号是干什么的?这里也就很清晰了。

    T[P]

    我们可以通过 keyof 查询索引类型的属性名,那么如何获取属性名对应的属性值类型呢?

    这里就用到了 索引访问操作符,与 JavaScript 种访问属性值的操作类似,访问类型的操作符也是通过 [] 来访问的,即 T[P],其中”中括号“中的P[P in keyof T] 中的 P 相对应。

    例如

    type unionKey = keyof IUser // "name" | "age" | "department"
    
    type values = IUser[unionKey] // string | number 属性值类型组成的联合类型
    

    最后我们希望得到的是由多个 key, value 组成的新类型,故而在 [P in keyof T]?: T[P]; 外部包上一层大括号。

    到此我们解决遇到的所有问题,只需要逐步代入到 Partial 类型定义中即可。

    相关资源

    本博客所记录的文章,主要是从网络收集的,有一些因为经过多次转载,所以出处已经不知,若是侵权,请通知我,我及时修改。本博客主要是用来记录我对所写文章的理解,若有错误,请大家指点,相互学习!
  • 相关阅读:
    AGC 018E.Sightseeing Plan(组合 DP)
    BZOJ.4767.两双手(组合 容斥 DP)
    AGC 001E.BBQ Hard(组合 DP)
    洛谷.3960.列队(线段树/树状数组)
    Codeforces Round #514 (Div. 2)
    10.4 正睿国庆集训测试 青岛
    Codeforces.264E.Roadside Trees(线段树 DP LIS)
    BZOJ.4653.[NOI2016]区间(线段树)
    Ansible安装部署以及常用模块详解
    Linux系统诊断必备技能之二:tcpdump抓包工具详解
  • 原文地址:https://www.cnblogs.com/qiqi715/p/14954081.html
Copyright © 2011-2022 走看看