最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • React & Typescript:组件的入门实例

    正文概述 掘金(荷包蛋卷)   2021-04-30   568

    React 组件的演化

    组件复用方式优势劣势状态
    类组件(Class发展时间长,接受度广泛只能继承父类作为一种传统开发模式,会长期存在Mixin可以复制任意对象的任意多个方法,实现组件间的复用组件间相互依赖、耦合,可能产生冲突,不利于维护被官方抛弃高阶组件(HOC)利用装饰器模式,在不改变组件的基础上,动态地为其添加新的能力嵌套过多调试困难,需要遵循某些约定(不改变原始组件,透传 props 等)能力强大,广泛引用Hooks替代类组件,多个 Hooks 间互不影响,避免嵌套地狱,开发效率高切换新思维需要成本React 的未来(官方主推)

    函数组件

    普通函数组件

    上一篇 中,我们创建了一个无状态组件(没有状态影响,作为纯静态展示) <Hello />,同时它也是一个函数组件。

    import React from "react";
    import { Button } from "antd";
    
    interface Greeting {
      name: string;
      firstName: string;
      lastName: string;
    }
    
    const Hello = (props: Greeting) => <Button>hello {props.name}</Button>;
    
    Hello.defaultProps = {
      firstName: "",
      lastName: "",
    };
    
    export default Hello;
    

    React.FC

    在 react 的声明文件中,对函数组件单独定义了一个类型 -- React.FC:

    type React.FC<P = {}> = React.FunctionComponent<P>
    

    现在使用 React.FC 重新定义一下 <Hello />

    const Hello: React.FC<Greeting> = ({ name, firstName, lastName }) => (
      <Button>hello {name}</Button>
    );
    

    使用 React.FC 后的区别

    FCFunctionComponent 的简写,这个类型定义了默认的 props (如 children)。

    const Hello: React.FC<Greeting> = ({ name, firstName, lastName, children }) => (
      <Button>hello {name}</Button>
    );
    

    在使用 React.FC后定义 defaultProps 时,默认属性必须是可选的(这和普通函数组件不同):

    interface Greeting {
      name: string;
      firstName?: string;
      lastName?: string;
    }
    
    const Hello: React.FC<Greeting> = ({ name, firstName, lastName, children }) => (
      <div>
        <Button>hello {name}</Button>
      </div>
    );
    
    Hello.defaultProps = {
      firstName: "",
      lastName: "",
    };
    

    小结

    TypeScript 中,函数组件需要为 props 定义类型。

    类组件

    Component

    类组件需要继承 Components 组件,在 react 的声明文件中,Component 被定义为泛型类:

    // P: 属性的类型,默认{}
    // S: 状态的类型,默认{}
    // SS: snapshot
    (alias) class Component<P = {}, S = {}, SS = any>
    

    实现

    将上面的函数组件改造成类组件:

    // src/componets/demo/HellpClass.tsx
    import React, { Component } from "react";
    import { Button } from "antd";
    
    interface Greeting {
      name: string;
      firstName: string;
      lastName: string;
    }
    
    interface State {
      count: number;
    }
    
    class HelloClass extends Component<Greeting, State> {
      // 初始化 state
      state: State = { count: 0 };
      // 默认属性值
      static defaultProps = {
        firstName: "",
        lastName: "",
      };
      render() {
        return <Button>hello {this.props.name}</Button>;
      }
    }
    
    export default HelloClass;
    

    通过 setState<HelloClass /> 添加一个点击计数功能。

    class HelloClass extends Component<Greeting, State> {
      state: State = { count: 0 };
      static defaultProps = {
        firstName: "",
        lastName: "",
      };
      render() {
        return (
          <>
            <div>您点击了 {this.state.count} 次</div>
            <Button
              onClick={() => {
                this.setState({ count: this.state.count + 1 });
              }}
            >
              hello {this.props.name}
            </Button>
          </>
        );
      }
    }
    

    小结

    TypeScript 中,类组件需要为 propsstate 定义类型。

    高阶组件

    我们现在要利用高阶组件包装一下 <HelloClass />,包装后的组件有一个新属性 loading,通过该属性控制被包装组件的 显示/隐藏。

    React.ComponentType

    指定被包装组件的类型为 React.ComponentType(一种 React 预定义类型),既可以是类组件,也可以是函数组件:

    type React.ComponentType<P = {}> = React.ComponentClass<P, any> | React.FunctionComponent<P>
    

    实现

    添加 <HelloHOC /> 组件:

    import React, { Component } from "react";
    import HelloClass from "./HelloClass";
    
    interface Loading {
      loading: boolean;
    }
    
    /*
     ** WrapperComponetn: 需要被包装的组件
     */
    function HelloHOC<P>(WrapperComponetn: React.ComponentType<P>) {
      // 定义 props 为 P 和 Loading 的交叉类型
      return class extends Component<P & Loading> {
        render() {
          // 解构 props,拆分出 loading
          const { loading, ...props } = this.props;
          // {...props}:属性透传
          return loading ? (
            <div>Loading...</div>
          ) : (
            <WrapperComponetn {...(props as P)} />
          );
        }
      };
    }
    
    // 导出经过高阶组件包装后的组件
    export default HelloHOC(HelloClass);
    

    有个报错

    我们在 index.tsx 中引入这个组件,这时会有一个报错:

    import React from "react";
    import ReactDOM from "react-dom";
    import HelloHOC from "./components/demo/HelloHOC";
    
    ReactDOM.render(
      <HelloHOC name="typescript" loading={true} />,
      document.querySelectorAll(".app")[0]
    );
    
    // ERROR! 因为 HelloClass 的静态属性 defaultProps 传不出来。
    

    解决方案:将 defaultProps 设置为可选属性。

    interface Greeting {
      name: string;
      firstName?: string;
      lastName?: string;
    }
    

    小结

    TypeScript 中,高阶组件的使用会遇到很多类型问题,还有可能遇到一些已知的 bug,但这并不是高阶组件本身的问题,而是因为 react 声明文件没有很好的兼容。其实官方最推荐的是使用 Hooks,下面就再用 Hooks 实现一下吧

    hooks

    hooks 也是一种函数组件,对比类组件,明显简化了许多:

    import React, { useEffect, useState } from "react";
    import { Button } from "antd";
    
    interface Greeting {
      name: string;
      firstName: string;
      lastName: string;
    }
    
    const HelloHooks = (props: Greeting) => {
      // 定义 [组件的状态,设置状态的方法],给定状态的初始值:不需要再定义类型
      const [count, setCount] = useState(0);
      const [text, setText] = useState<string | null>(null);
      return (
        <>
          <div>您点击了 {count} 次</div>
          <Button
            onClick={() => {
              setCount(count + 1);
            }}
          >
            hello {props.name}
          </Button>
        </>
      );
    };
    
    HelloHooks.defaultProps = {
      firstName: "",
      lastName: "",
    };
    
    export default HelloHooks;
    

    利用 useEffect 新增一个功能: 点击超过 5 次给出提示。

    const HelloHooks = (props: Greeting) => {
      const [count, setCount] = useState(0);
      const [text, setText] = useState<string | null>(null);
    
      // 只有当 count 改变时,渲染逻辑才会执行。
      useEffect(() => {
        if (count > 5) {
          setText("休息一下");
        }
      }, [count]);
    
      return (
        <>
          <div>
            您点击了 {count} 次,{text}
          </div>
          <Button
            onClick={() => {
              setCount(count + 1);
            }}
          >
            hello {props.name}
          </Button>
        </>
      );
    };
    

    为什么要定义为泛型?

    不用泛型变量,this.props 的类型无法确定,在内部只能使用类型断言来访问属性:

    (this.props as Greeting).name;
    

    这样很麻烦,而 React 声明文件把这些约束关系都用泛型定义好了。


    起源地下载网 » React & Typescript:组件的入门实例

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元