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 后的区别
FC 是 FunctionComponent 的简写,这个类型定义了默认的 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 中,类组件需要为 props 和 state 定义类型。
高阶组件
我们现在要利用高阶组件包装一下 <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 声明文件把这些约束关系都用泛型定义好了。





















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)