React 高级指引的学习笔记 4

静态类型检查

像 Flow 和 TypeScript 等这些静态类型检查器,可以在运行前识别某些类型的问题。他们还可以通过增加自动补全等功能来改善开发者的工作流程。出于这个原因,我们建议在大型代码库中使用 FlowTypeScript 来代替 PropTypes。(引自官网)

在这一小节中,主要讲的就是如何使用 FlowTypeScript 来实现对静态类型的检测,如果项目是通过 create-react-app 构建的,那么这两个方案都是已经能够开箱即用的,只需要新增比较简单的配置即可。如 Flow

// 1、在项目中新增Flow
yarn add --dev flow-bin

// 2、在package.json中的scripts里加入
"scripts":{
    ...,
    "flow": "flow"
}

// 1、运行flow
yarn flow

以上就是简单的 Flow 的配置, 只要在项目中使用了 //@flow 注释的页面, Flow 就会对该页面进行检测,最后返回是否有异常。随后说的是 TypeScript 的配置:

TypeScript 是一种由微软开发的编程语言。它是 JavaScript 的一个类型超集,包含独立的编译器。作为一种类型语言,TypeScript 可以在构建时发现 bug 和错误,这样程序运行时就可以避免此类错误。(引自官网)

都知道,在使用TS进行编程的时候是必须要进行类型的定义的,这自然就能在使用的时候就进行验证。create-react-app 已经配置好了TS的运行环境,可以通过 npx create-react-app my-app --template typescript 进行创建项目,如果是在现有的项目中新增,需要先安装: yarn add typescript @types/node @types/react @types/react-dom @types/jest 。随后将所有js类文件重命名,如(src/index.js 替换成 src/index.tsx

PS: 对于自己通过 webpack 进行配置的用户来说, Flow 需要在编译的时候去除其语法,不然会报错:具体配置见 flow-remove-types

严格模式

严格模式有点类似于前文的 Profiler 的用法,不过严格模式是通过 StrictMode 检测内部组件的合法性,目前主要用于一下几项:

识别不安全的生命周期 关于使用过时字符串 ref API 的警告 关于使用废弃的 findDOMNode 方法的警告 检测意外的副作用 检测过时的 context API

(引自官网)

所谓不安全的生命周期主要是针对用于更新的几个生命周期在调用时出现的异常,在异步渲染之更新这里面进行了描述,在下面的引用中,react 特意对 componentWillMountcomponentWillReceivePropscomponentWillUpdate 设置了以 UNSAFE_ 开头的别名,可见这些更新在 react 看来是不安全的。此外,在 React17.0 之后将新增生命周期来完善现有的更新机制: getDerivedStateFromPropsgetSnapshotBeforeUpdate

从概念上讲,React 分两个阶段工作:

渲染 阶段会确定需要进行哪些更改,比如 DOM。在此阶段,React 调用 render,然后将结果与上次渲染的结果进行比较。 提交 阶段发生在当 React 应用变化时。(对于 React DOM 来说,会发生在 React 插入,更新及删除 DOM 节点的时候。)在此阶段,React 还会调用 componentDidMountcomponentDidUpdate 之类的生命周期方法。

提交阶段通常会很快,但渲染过程可能很慢。因此,即将推出的 concurrent 模式 (默认情况下未启用) 将渲染工作分解为多个部分,对任务进行暂停和恢复操作以避免阻塞浏览器。这意味着 React 可以在提交之前多次调用渲染阶段生命周期的方法,或者在不提交的情况下调用它们(由于出现错误或更高优先级的任务使其中断)。

渲染阶段的生命周期包括以下 class 组件方法:

constructor componentWillMount (or UNSAFE_componentWillMount) componentWillReceiveProps (or UNSAFE_componentWillReceiveProps) componentWillUpdate (or UNSAFE_componentWillUpdate) getDerivedStateFromProps shouldComponentUpdate render setState 更新函数(第一个参数)

因为上述方法可能会被多次调用,所以不要在它们内部编写副作用相关的代码,这点非常重要。忽略此规则可能会导致各种问题的产生,包括内存泄漏和或出现无效的应用程序状态。不幸的是,这些问题很难被发现,因为它们通常具有非确定性。

严格模式不能自动检测到你的副作用,但它可以帮助你发现它们,使它们更具确定性。通过故意重复调用以下函数来实现的该操作:

class 组件的 constructorrender 以及 shouldComponentUpdate 方法 class 组件的生命周期方法 getDerivedStateFromProps 函数组件体 状态更新函数 (即 setState 的第一个参数) 函数组件通过使用 useStateuseMemo 或者 useReducer

(以上引自官网)

使用 PropTypes 类型检查

这一个模块就是说了 PropTypes 怎么在 react 中使用,以及大部分的判定函数是如何书写的,由于类型太多我就不复制黏贴了,后面如果想要知道就去官网自己看就行,上面的标题是直达链接

非受控组件

对于一个接受参数并返回相应结果的组件来说,分为非受控组件和受控组件,其参数也分为受控参数和非受控参数。以 antd4.0 为例来看, defaultValue 这一参数基本上都是非受控的,而 value 则是受控的,你在外层可以控制内部的函数渲染,外层的更改会传递到内部进行修改。
通常来说,使用 setState 还是使用 ref 来获得数据,主要还是根据当前的需求来决定的,如果你不需要动态的更新,则使用 ref 就很方便,反之亦然,下面写了一个感觉是假的非受控组件的 form 表单,模仿 antdForm,用到了高级指引中学到的几种方法以及 React.Children:

/**
 * @desc 用于测试NotContral的页面
 * @name NotContral
 * @author Cyearn
 * @date 2020/11/21
 */

import React, { Component } from "react";
import styles from "./styles.module.less";

class NotContral extends Component {
  constructor(props) {
    super(props);
    this.state = {};
    this.ref = React.createRef();
  }
  componentDidMount() {}
  submit = () => {
    const { setValue, getValue } = this.ref;
    console.log(getValue());
  };
  render() {
    return (
      <div className={styles.contain}>
        <Form refForm={(el) => (this.ref = el)}>
          <Item label={"账号"} name={"username"}>
            <Input />
          </Item>
          <Item label={"密码"} name={"password"}>
            <Input />
          </Item>
        </Form>
        <button onClick={this.submit}>提交</button>
      </div>
    );
  }
}
const Form = function (props) {
  const { children, refForm } = props;

  const ReForm = withForm(children, refForm);
  return (
    <div>
      test
      <ReForm ref={refForm} />
    </div>
  );
};

function withForm(children, refForm) {
  if (children) {
    return class extends Component {
      constructor(props) {
        super(props);
        this.state = {};
      }

      componentDidMount() {
        let param = {};
        // 向子组件注入默认值,onChange事件
        this.Children = React.Children.map(children, (child) => {
          param[child.props.name] = "";
          let key = child.props.name;
          return React.cloneElement(child, {
            onChange: this.changeInput,
            defaultValue: this.state[key],
            value: this.state[key],
          }); //这次我们通过React.cloneElement添加属性
        });
        this.setState({
          ...param,
        });
      }
      // 定义设置值得函数
      setValue = (obj = {}) => {
        this.setState({
          ...obj,
        });
      };
      // 定义获取值得函数
      getValue = () => {
        const all = this.state;
        return { ...all };
      };
      // 当表单内数据发生变化的函数
      changeInput = (e, id) => {
        let param = {};
        param[id] = e;
        this.setState({
          ...param,
        });
      };

      onValueChange = () => {};

      render() {
        return <form onValueChange={this.onValueChange}>{this.Children}</form>;
      }
    };
  }
}

// HOC
function WithItem(Inject, props) {
  class Test extends Component {
    constructor(props) {
      super(props);
      this.state = { value: "" };
    }
    render() {
      const { value } = props;
      console.log(value);
      return (
        <Inject
          {...props}
          onChange={(e) => props.onChange(e, props.name)}
          value={value}
        />
      );
    }
  }
  return Test;
}

// 包裹子节点
class Item extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    const { label, children, ...other } = this.props;
    let type = children && children.type;
    if (type) {
      const Children = WithItem(this.props.children.type, { ...other });
      return (
        <section style={{ marginBottom: 10 }}>
          <span>{label}:</span>
          <Children />
        </section>
      );
    } else {
      return null;
    }
  }
}

// 最小节点
function Input(props) {
  const { onChange, value } = props;
  return (
    <input
      type="text"
      onChange={(e) => onChange(e.target.value)}
      value={value}
    />
  );
}

export default NotContral;