# 进阶

# 属性默认值 和 类型检查

# 属性默认值

通过一个静态属性defaultProps告知 react 属性默认值

  • 函数组件
import React from "react";

export default function FuncDefault(props) {
  console.log(props); //已经完成了混合
  return (
    <div>
      a:{props.a},b:{props.b},c:{props.c}
    </div>
  );
}
// 属性默认值
FuncDefault.defaultProps = {
  a: 1,
  b: 2,
  c: 3,
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • 类组件
import React from "react";
export default class ClassDefault extends React.Component {
  static defaultProps = {
    a: 1,
    b: 2,
    c: 3,
  };

  constructor(props) {
    super(props);
    console.log(props);
  }

  render() {
    return (
      <div>
        a:{this.props.a},b:{this.props.b},c:{this.props.c}
      </div>
    );
  }
}
// // 属性默认值
// ClassDefault.defaultProps = {
//     a: 1,
//     b: 2,
//     c: 3
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 属性类型检查

使用库:prop-types

对组件使用静态属性propTypes告知 react 如何检查属性


PropTypes.any://任意类型
PropTypes.array://数组类型
PropTypes.bool://布尔类型
PropTypes.func://函数类型
PropTypes.number://数字类型
PropTypes.object://对象类型
PropTypes.string://字符串类型
PropTypes.symbol://符号类型

PropTypes.node://任何可以被渲染的内容,字符串、数字、React元素
PropTypes.element://react元素
PropTypes.elementType://react元素类型
PropTypes.instanceOf(构造函数)//必须是指定构造函数的实例
PropTypes.oneOf([xxx, xxx])//枚举
PropTypes.oneOfType([xxx, xxx]);  //属性类型必须是数组中的其中一个
PropTypes.arrayOf(PropTypes.XXX)//必须是某一类型组成的数组
PropTypes.objectOf(PropTypes.XXX)//对象由某一类型的值组成
PropTypes.shape(对象): //属性必须是对象,并且满足指定的对象要求
PropTypes.exact({...})//对象必须精确匹配传递的数据

//自定义属性检查,如果有错误,返回错误对象即可
属性: function(props, propName, componentName) {
   //...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React, { Component } from "react";
import PropTypes from "prop-types";

export class A {}

export class B extends A {}

export default class ValidationComp extends Component {
  //先混合属性
  static defaultProps = {
    b: false,
  };

  //再调用相应的函数进行验证
  static propTypes = {
    a: PropTypes.number.isRequired, //a属性必须是一个数字类型,并且必填
    b: PropTypes.bool.isRequired, //b必须是一个bool属性,并且必填
    onClick: PropTypes.func, //onClick必须是一个函数
    c: PropTypes.any, //1. 可以设置必填   2. 阵型保持整齐(所有属性都在该对象中)
    d: PropTypes.node.isRequired, //d必填,而且必须是一个可以渲染的内容,字符串、数字、React元素
    e: PropTypes.element, //e必须是一个React元素
    F: PropTypes.elementType, //F必须是一个组件类型
    g: PropTypes.instanceOf(A), //g必须是A的实例
    sex: PropTypes.oneOf(["男", "女"]), //属性必须是数组当中的一个
    h: PropTypes.arrayOf(PropTypes.number), //数组的每一项必须满足类型要求
    i: PropTypes.objectOf(PropTypes.number), //每一个属性必须满足类型要求
    j: PropTypes.shape({
      //属性必须满足该对象的要求
      name: PropTypes.string.isRequired, //name必须是一个字符串,必填
      age: PropTypes.number, //age必须是一个数字
      address: PropTypes.shape({
        province: PropTypes.string,
        city: PropTypes.string,
      }),
    }),
    k: PropTypes.arrayOf(
      PropTypes.shape({
        name: PropTypes.string.isRequired,
        age: PropTypes.number.isRequired,
      })
    ),
    m: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    score: function (props, propName, componentName) {
      console.log(props, propName, componentName);
      const val = props[propName];
      //必填
      if (val === undefined || val === null) {
        return new Error(`invalid prop ${propName} in ${componentName}${propName} is Required`);
      }
      //该属性必须是一个数字
      if (typeof val !== "number") {
        return new Error(`invalid prop ${propName} in ${componentName}${propName} is not a number`);
      }
      const err = PropTypes.number.isRequired(props, propName, componentName);
      if (err) {
        return err;
      }
      //并且取值范围是0~100
      if (val < 0 || val > 100) {
        return new Error(`invalid prop ${propName} in ${componentName}${propName} must is between 0 and 100`);
      }
    },
  };

  render() {
    const F = this.props.F;
    return (
      <div>
        {this.props.a}
        <div>
          {this.props.d}
          <F />
        </div>
      </div>
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

# HOC 高阶组件

HOF:Higher-Order Function, 高阶函数,以函数作为参数,并返回一个函数 HOC: Higher-Order Component, 高阶组件,以组件作为参数,并返回一个组件

通常,可以利用 HOC 实现横切关注点。

举例:20 个组件,每个组件在创建组件和销毁组件时,需要作日志记录 20 个组件,它们需要显示一些内容,得到的数据结构完全一致

注意

  1. 不要在 render 中使用高阶组件
  2. 不要在高阶组件内部更改传入的组件
import React from "react";

/**
 * 高阶组件,命名通常以with开头
 * @param {*} comp 组件
 */
export default function withLog(Comp, str) {
  return class LogWrapper extends React.Component {
    componentDidMount() {
      console.log(`日志:组件${Comp.name}被创建了!${Date.now()}`);
    }

    componentWillUnmount() {
      console.log(`日志:组件${Comp.name}被销毁了!${Date.now()}`);
    }

    render() {
      return (
        <>
          <h1>{str}</h1>
          <Comp {...this.props} />
        </>
      );
    }
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# ref

reference: 引用

场景:希望直接使用 dom 元素中的某个方法,或者希望直接使用自定义组件中的某个方法

  1. ref 作用于内置的 html 组件,得到的将是真实的 dom 对象
  2. ref 作用于类组件,得到的将是类的实例
  3. ref 不能作用于函数组件
import React, { Component } from "react";

class A extends Component {
  method() {
    console.log("调用了组件A的方法");
  }

  render() {
    return <h1>组件A</h1>;
  }
}

// function B(){
//     return <h1>组件B</h1>
// }

export default class Comp extends Component {
  handleClick = () => {
    console.log(this);
    this.refs.txt.focus();
    this.refs.compA.method();
  };

  render() {
    return (
      <div>
        <input ref="txt" type="text" />
        <A ref="compA" />
        {/* <B ref="compB" /> */}
        <button onClick={this.handleClick}>聚焦</button>
      </div>
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

ref 不再推荐使用字符串赋值,字符串赋值的方式将来可能会被移出

目前,ref 推荐使用对象或者是函数

# 对象

通过 React.createRef 函数创建

import React, { Component } from "react";

export default class Comp extends Component {
  constructor(props) {
    super(props);
    this.txt = React.createRef();
  }

  handleClick = () => {
    this.txt.current.focus();
  };

  render() {
    return (
      <div>
        <input ref={this.txt} type="text" />
        <button onClick={this.handleClick}>聚焦</button>
      </div>
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 函数

import React, { Component } from "react";

export default class Comp extends Component {
  state = {
    show: true,
  };

  handleClick = () => {
    // this.txt.focus();
    this.setState({
      show: !this.state.show,
    });
  };

  componentDidMount() {
    console.log("didMount", this.txt);
  }

  getRef = (el) => {
    console.log("函数被调用了", el);
    this.txt = el;
  };

  render() {
    return (
      <div>
        {this.state.show && <input ref={this.getRef} type="text" />}
        <button onClick={this.handleClick}>显示/隐藏</button>
      </div>
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

函数的调用时间:

  1. componentDidMount 的时候会调用该函数
    1. 在 componentDidMount 事件中可以使用 ref
  2. 如果 ref 的值发生了变动(旧的函数被新的函数替代),分别调用旧的函数以及新的函数,时间点出现在 componentDidUpdate 之前
    1. 旧的函数被调用时,传递 null
    2. 新的函数被调用时,传递对象
  3. 如果 ref 所在的组件被卸载,会调用函数

谨慎使用 ref

能够使用属性和状态进行控制,就不要使用 ref。

  1. 调用真实的 DOM 对象中的方法
  2. 某个时候需要调用类组件的方法

# Ref 转发

forwardRef

forwardRef 方法:

  1. 参数,传递的是函数组件,不能是类组件,并且,函数组件需要有第二个参数来得到 ref
  2. 返回值,返回一个新的组件
import React from "react";

function A(props, ref) {
  return (
    <h1 ref={ref}>
      组件A
      <span>{props.words}</span>
    </h1>
  );
}

//传递函数组件A,得到一个新组件NewA
const NewA = React.forwardRef(A);

export default class App extends React.Component {
  ARef = React.createRef();

  componentDidMount() {
    console.log(this.ARef);
  }

  render() {
    return (
      <div>
        <NewA ref={this.ARef} words="asfsafasfasfs" />
        {/* this.ARef.current:  h1 */}
      </div>
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React from "react";

class A extends React.Component {
  render() {
    return (
      <h1 ref={this.props.abc}>
        组件A
        <span>{this.props.words}</span>
      </h1>
    );
  }
}

const NewA = React.forwardRef((props, ref) => {
  return <A {...props} abc={ref} />;
});

export default class App extends React.Component {
  ARef = React.createRef();

  componentDidMount() {
    console.log(this.ARef);
  }

  render() {
    return (
      <div>
        <NewA ref={this.ARef} words="asfsafasfasfs" />
        {/* this.ARef.current:  h1 */}
      </div>
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# Context

上下文:Context,表示做某一些事情的环境

React 中的上下文特点:

  1. 当某个组件创建了上下文后,上下文中的数据,会被所有后代组件共享
  2. 如果某个组件依赖了上下文,会导致该组件不再纯粹(外部数据仅来源于属性 props)
  3. 一般情况下,用于第三方组件(通用组件)

# 旧的 API

创建上下文

只有类组件才可以创建上下文

  1. 给类组件书写静态属性 childContextTypes,使用该属性对上下文中的数据类型进行约束
  2. 添加实例方法 getChildContext,该方法返回的对象,即为上下文中的数据,该数据必须满足类型约束,该方法会在每次 render 之后运行。

使用上下文中的数据

要求:如果要使用上下文中的数据,组件必须有一个静态属性 contextTypes,该属性描述了需要获取的上下文中的数据类型

  1. 可以在组件的构造函数中,通过第二个参数,获取上下文数据
  2. 从组件的 context 属性中获取
  3. 在函数组件中,通过第二个参数,获取上下文数据

上下文的数据变化

上下文中的数据不可以直接变化,最终都是通过状态改变

在上下文中加入一个处理函数,可以用于后代组件更改上下文的数据

# 新版 API

旧版 API 存在严重的效率问题,并且容易导致滥用

创建上下文

上下文是一个独立于组件的对象,该对象通过 React.createContext(默认值)创建

返回的是一个包含两个属性的对象

  1. Provider 属性:生产者。一个组件,该组件会创建一个上下文,该组件有一个 value 属性,通过该属性,可以为其数据赋值
    1. 同一个 Provider,不要用到多个组件中,如果需要在其他组件中使用该数据,应该考虑将数据提升到更高的层次
  2. Consumer 属性:后续讲解

使用上下文中的数据

  1. 在类组件中,直接使用 this.context 获取上下文数据
    1. 要求:必须拥有静态属性 contextType , 应赋值为创建的上下文对象
  2. 在函数组件中,需要使用 Consumer 来获取上下文数据
    1. Consumer 是一个组件
    2. 它的子节点,是一个函数(它的 props.children 需要传递一个函数)

注意细节

如果,上下文提供者(Context.Provider)中的 value 属性发生变化(Object.is 比较),会导致该上下文提供的所有后代元素全部重新渲染,无论该子元素是否有优化(无论 shouldComponentUpdate 函数返回什么结果)

import React, { Component } from "react";

const ctx = React.createContext();

function ChildA(props) {
  return (
    <div>
      <h1>ChildA</h1>
      <h2>
        <ctx.Consumer>
          {(value) => (
            <>
              {value.a}{value.b}
            </>
          )}
        </ctx.Consumer>
      </h2>
      <ChildB />
    </div>
  );
}

class ChildB extends React.Component {
  static contextType = ctx;

  render() {
    return (
      <p>
        ChildB,来自于上下文的数据:a: {this.context.a}, b:{this.context.b}
        <button
          onClick={() => {
            this.context.changeA(this.context.a + 2);
          }}
        >
          后代组件的按钮,点击a+2
        </button>
      </p>
    );
  }
}

export default class NewContext extends Component {
  state = {
    a: 0,
    b: "abc",
    changeA: (newA) => {
      this.setState({
        a: newA,
      });
    },
  };

  render() {
    return (
      <ctx.Provider value={this.state}>
        <div>
          <ChildA />

          <button
            onClick={() => {
              this.setState({
                a: this.state.a + 1,
              });
            }}
          >
            父组件的按钮,a加1
          </button>
        </div>
      </ctx.Provider>
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

# PureComponent

纯组件,用于避免不必要的渲染(运行 render 函数),从而提升效率

优化:如果一个组件的属性和状态,都没有发生变化,重新渲染组件是没有必要的

PureComponent 是一个组件,如果某个组件继承自该组件,则该组件的 shouldComponentUpdate 会进行优化,对属性和状态进行浅比较,如果相等则不会重新渲染

注意

  1. PureComponent 进行浅比较
    1. 为了效率, 应该尽量使用 PureComponent
    2. 要求不要改动之前的状态,永远是创建新的状态来覆盖之前的状态(Immutable,不可变对象)
    3. 有一个第三方 JS 库,Immutable.js, 它专门用于制作不可变对象
  2. 函数组件,使用 React.memo 函数制作纯组件

# render props

有时候,某些组件的各种功能及其处理逻辑几乎完全相同,只是显示的界面不一样,建议下面的方式认选其一来解决重复代码的问题(横切关注点)

  1. render props
    1. 某个组件,需要某个属性
    2. 该属性是一个函数,函数的返回值用于渲染
    3. 函数的参数会传递为需要的数据
    4. 注意纯组件的属性(尽量避免每次传递的 render props 的地址不一致)
    5. 通常该属性的名字叫做 render
import React, { PureComponent } from "react";
import "./style.css";

/**
 * 该组件用于监听鼠标的变化
 */
export default class MouseListener extends PureComponent {
  state = {
    x: 0,
    y: 0,
  };

  divRef = React.createRef();

  handleMouseMove = (e) => {
    //更新x和y的值
    const { left, top } = this.divRef.current.getBoundingClientRect();
    const x = e.clientX - left;
    const y = e.clientY - top;
    this.setState({
      x,
      y,
    });
  };

  render() {
    return (
      <div ref={this.divRef} className="point" onMouseMove={this.handleMouseMove}>
        {this.props.render ? this.props.render(this.state) : "默认值"}
      </div>
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import MouseListener from "./MouseListener";
import React from "react";

const renderPoint = (mouse) => (
  <>
    横坐标:{mouse.x},纵坐标:{mouse.y}
  </>
);
const renderDiv = (mouse) => (
  <>
    <div
      style={{
        width: 100,
        height: 100,
        background: "#008c8c",
        position: "absolute",
        left: mouse.x - 50,
        top: mouse.y - 50,
      }}
    ></div>
  </>
);

export default function Test() {
  return (
    <div>
      <MouseListener render={renderPoint} />
      <MouseListener render={renderDiv} />
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  1. HOC
import React, { PureComponent } from "react";
import "./style.css";
export default function withMouseListener(Comp) {
  return class MouseListener extends PureComponent {
    state = {
      x: 0,
      y: 0,
    };

    divRef = React.createRef();

    handleMouseMove = (e) => {
      //更新x和y的值
      const { left, top } = this.divRef.current.getBoundingClientRect();
      const x = e.clientX - left;
      const y = e.clientY - top;
      this.setState({
        x,
        y,
      });
    };

    render() {
      return (
        <div ref={this.divRef} className="point" onMouseMove={this.handleMouseMove}>
          <Comp {...this.props} x={this.state.x} y={this.state.y} />
        </div>
      );
    }
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import withMouseListener from "./withMouseListener";
import React from "react";

function Point(props) {
  return (
    <>
      横坐标:{props.x},纵坐标:{props.y}
    </>
  );
}

function MoveDiv(props) {
  return (
    <div
      style={{
        width: 100,
        height: 100,
        background: "#008c8c",
        position: "absolute",
        left: props.x - 50,
        top: props.y - 50,
      }}
    ></div>
  );
}

const MousePoint = withMouseListener(Point);
const MouseDiv = withMouseListener(MoveDiv);

export default function Test() {
  return (
    <div>
      <MousePoint />
      <MouseDiv />
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# Portals

插槽:将一个 React 元素渲染到指定的 DOM 容器中

ReactDOM.createPortal(React 元素, 真实的 DOM 容器),该函数返回一个 React 元素

注意事件冒泡

  1. React 中的事件是包装过的
  2. 它的事件冒泡是根据虚拟 DOM 树来冒泡的,与真实的 DOM 树无关。
import React from "react";
import ReactDOM from "react-dom";

function ChildA() {
  return ReactDOM.createPortal(
    <div
      className="child-a"
      style={{
        marginTop: 200,
      }}
    >
      <h1>ChildA</h1>
      <ChildB />
    </div>,
    document.querySelector(".modal")
  );
}

function ChildB() {
  return (
    <div className="child-b">
      <h1>ChildB</h1>
    </div>
  );
}

export default function App() {
  return (
    <div
      className="app"
      onClick={(e) => {
        console.log("App被点击了", e.target);
      }}
    >
      <h1>App</h1>
      <ChildA />
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 错误边界

默认情况下,若一个组件在渲染期间(render)发生错误,会导致整个组件树全部被卸载

错误边界:是一个组件,该组件会捕获到渲染期间(render)子组件发生的错误,并有能力阻止错误继续传播

让某个组件捕获错误

  1. 编写生命周期函数 getDerivedStateFromError
    1. 静态函数
    2. 运行时间点:渲染子组件的过程中,发生错误之后,在更新页面之前
    3. 注意:只有子组件发生错误,才会运行该函数
    4. 该函数返回一个对象,React 会将该对象的属性覆盖掉当前组件的 state
    5. 参数:错误对象
    6. 通常,该函数用于改变状态
  2. 编写生命周期函数 componentDidCatch
    1. 实例方法
    2. 运行时间点:渲染子组件的过程中,发生错误,更新页面之后,由于其运行时间点比较靠后,因此不太会在该函数中改变状态
    3. 通常,该函数用于记录错误消息

细节

某些错误,错误边界组件无法捕获

  1. 自身的错误
  2. 异步的错误
  3. 事件中的错误

总结:仅处理渲染子组件期间的同步错误

// ErrorBound.js
import React, { PureComponent } from "react";

export default class ErrorBound extends PureComponent {
  state = {
    hasError: false,
  };

  static getDerivedStateFromError(error) {
    console.log("发生错误了");
    return {
      hasError: true,
    };
  }

  componentDidCatch(error, info) {
    console.log("记录错误信息");
  }

  render() {
    // setTimeout(() => {
    //     throw new Error("asfasdfasf");
    // }, 1000);
    if (this.state.hasError) {
      return <h1>出现错误了!</h1>;
    }
    return this.props.children;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// app.js
import React from "react";
import ErrorBound from "./components/common/ErrorBound";

function Comp1() {
  return (
    <div
      style={{
        width: "90%",
        height: 500,
        border: "2px solid",
      }}
    >
      <h1>Comp1</h1>
      <Comp2 />
    </div>
  );
}

function Comp2() {
  return (
    <div
      style={{
        width: "70%",
        height: "70%",
        border: "2px solid",
      }}
    >
      <h1
        onClick={() => {
          throw new Error("点击时发生的错误");
        }}
      >
        Comp2
      </h1>
    </div>
  );
}

function Comp3() {
  return (
    <div
      style={{
        width: "90%",
        height: 500,
        border: "2px solid",
      }}
    >
      <h1>Comp3</h1>
    </div>
  );
}

export default function App() {
  return (
    <div>
      <ErrorBound>
        <Comp1 />
      </ErrorBound>
      <Comp3 />
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

# React 中的事件

这里的事件:React 内置的 DOM 组件中的事件

  1. 给 document 注册事件
  2. 几乎所有的元素的事件处理,均在 document 的事件中处理
    1. 一些不冒泡的事件,是直接在元素上监听
    2. 一些 document 上面没有的事件,直接在元素上监听
  3. 在 document 的事件处理,React 会根据虚拟 DOM 树的完成事件函数的调用
  4. React 的事件参数,并非真实的 DOM 事件参数,是 React 合成的一个对象,该对象类似于真实 DOM 的事件参数
    1. stopPropagation,阻止事件在虚拟 DOM 树中冒泡
    2. nativeEvent,可以得到真实的 DOM 事件对象
    3. 为了提高执行效率,React 使用事件对象池来处理事件对象

注意事项

  1. 如果给真实的 DOM 注册事件,阻止了事件冒泡,则会导致 react 的相应事件无法触发
  2. 如果给真实的 DOM 注册事件,事件会先于 React 事件运行
  3. 通过 React 的事件中阻止事件冒泡,无法阻止真实的 DOM 事件冒泡
  4. 可以通过 nativeEvent.stopImmediatePropagation(),阻止 document 上剩余事件的执行
  5. 在事件处理程序中,不要异步的使用事件对象,如果一定要使用,需要调用 persist 函数

# 工具

# 严格模式

StrictMode(React.StrictMode),本质是一个组件,该组件不进行 UI 渲染(React.Fragment <> </>),它的作用是,在渲染内部组件时,发现不合适的代码。

  • 识别不安全的生命周期
  • 关于使用过时字符串 ref API 的警告
  • 关于使用废弃的 findDOMNode 方法的警告
  • 检测意外的副作用
    • React 要求,副作用代码仅出现在以下生命周期函数中
      1. ComponentDidMount
      1. ComponentDidUpdate
      1. ComponentWillUnMount

副作用:一个函数中,做了一些会影响函数外部数据的事情,例如:

  1. 异步处理
  2. 改变参数值
  3. setState
  4. 本地存储
  5. 改变函数外部的变量

相反的,如果一个函数没有副作用,则可以认为该函数是一个纯函数

在严格模式下,虽然不能监控到具体的副作用代码,但它会将不能具有副作用的函数调用两遍,以便发现问题。(这种情况,仅在开发模式下有效)

  • 检测过时的 context API

# Profiler

性能分析工具

分析某一次或多次提交(更新),涉及到的组件的渲染时间

火焰图:得到某一次提交,每个组件总的渲染时间以及自身的渲染时间

排序图:得到某一次提交,每个组件自身渲染时间的排序

组件图:某一个组件,在多次提交中,自身渲染花费的时间