[React 基础系列] 受控表单 vs 不受控表单

Posted GoldenaArcher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[React 基础系列] 受控表单 vs 不受控表单相关的知识,希望对你有一定的参考价值。

[React 基础系列] 受控表单 vs 不受控表单

之前的学习部分带了一些表单内容的使用——之前的案例基本上是用的都是 input,接下来就学习一下受控表单和不受控表单之间的区别,并且通过非受控表单了解一下非受控组件的知识点。

受控表单指的是状态由 React 内部进行管理的表单,自然,不受控表单值得就是状态不由 React 内部进行管理的表单。

学习案例下载资源在:受控表单 vs 不受控表单-案例

前复习过的内容:

受控表单

受控表单指的是所有的表单状态由 state 进行管理的表单,通过 setState 触发状态更新,如下面的这个受控组件:

class ControlledForm extends Component {
  constructor() {
    super();

    this.state = {
      name: '',
      phone: '',
    };

    this.onSubmit = this.onSubmit.bind(this);
  }

  updateInputHandler = (e, label) => {
    this.setState((prevState) => {
      return { ...prevState, [label]: e.target.value };
    });
  };

  onSubmit = (e) => {
    for (const [key, val] of Object.entries(this.state)) {
      console.log(key, val);
    }
    // e.preventDefault(); 放置页面的重定向
    e.preventDefault();
  };

  render() {
    return (
      <form onSubmit={this.onSubmit}>
        <div>
          <label>name: </label>
          <input
            type="text"
            value={this.state.name}
            onChange={(e) => this.updateInputHandler(e, 'name')}
          />
        </div>
        <div>
          <label>phone: </label>
          <input
            type="text"
            value={this.state.phone}
            onChange={(e) => this.updateInputHandler(e, 'phone')}
          />
        </div>
        <div>
          <input type="submit" value="submit" />
        </div>
      </form>
    );
  }
}

实现效果为:

这里的事件处理通过接收另外一个参数,可以做到简化状态更新的方法。这是利用了 ES6 中的计算属性名的特殊特性去实现的。通过使用 obj[variable] 这种语法可以通过传入参数(variable),做到动态更新参数(variable)的值。

更懂 ES6 的新特性可以参考: 都 2021 年了还不会连 ES6/ES2015 更新了什么都不知道吧 这篇笔记。

另外,注意到这里的 setState 的写法与之前的案例不一样,这是因为 React 更新状态并不能保证是同步地情况。为了节省效率,它内部会判断哪些 setState 是可以合并,放在一个 batch job 里面去更新。所以直接在 setState 调用 this.state 去传值是一个非常危险的行为。而通过 this.setState(prevState => {//...}) 回调函数,能够保证在 setState 内获取的状态,是更新时的状态。

未执行
未执行
未执行
未执行
执行,顺序不确定
执行,顺序不确定
执行,顺序不确定
执行,顺序不确定
setState1
setState1
setState2
setState2
setState3
setState3
setState4
setState4
batch Job

按照上面的图示所说,在 setState2 中调用的this.state,极大可能会受到 setState4 的影响,从而造成更新的状态出错。这种合并更新的情况在大型应用中经常会发生,所以必须要注意这点。

确实受控组件写起来会有点麻烦,尤其是之后如果需要加其他的类型检查,那么 updateInputHandler 里面的内容会变得非常的多,这也是下一步会实现优化/封装的内容题材。

refs

refs 其实是一种可以在 React 中更直接访问 DOM 节点,或在 render 方法中创建 React 元素的方法。它的出现是针对于在组件中可能需要在非常规操作的 React 时所实用的方法。

回顾一下,常规方法即组件内通过 state 管理,父子组件通过 props 传递信息。自然,非常规方法就是不通过 state 也不通过 props 去对组件 和/或 元素进行操作。

React 官方其实是不太推荐使用 refs 去进行 DOM 的操作,因为这会放弃 Virtual DOM 带来的好处——即通过对比虚拟 DOM 树和真实 DOM 树,只重新渲染更新过的 DOM 节点,这样可以省去大量无意义的重复渲染。

React 团队列举出了 3 种使用 refs 的情景:

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。

一般来说,在常规的业务情况下都不太需要用到 refs……至少我到现在没用过。现在创建和使用 refs 的方法也很简单:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    // 通过 createRef 创建ref
    this.myRef = React.createRef();
  }
  render() {
    // 通过 ref={this.{创建的ref实例}} 就能够关联ref
    return <div className="exposeRef" ref={this.myRef} />;
  }
}

这样,类名为 exposeRef 的 div 元素就可以通过操作 this.myRef 的方法去修改。毕竟,refs 也可以通过 props 传递给其他的组件,这样就能够在其他的组件——通常情况下是 MyComponent 的子组件——中直接操作 exposeRef

不受控表单

不受控表单的内容即然无法由 state 控制,那么取值就无法通过 state 去获取了。这种情况下,也只能交给 refs 去处理了,如:

class Form extends Component {
  constructor() {
    super();
    this.name = React.createRef();
    this.phone = React.createRef();
    this.onSubmit = this.onSubmit.bind(this);
  }

  onSubmit(e) {
    console.log('submit form');
    console.log(this.name.current.value, this.phone.current.value);
    // e.preventDefault(); 放置页面的重定向
    e.preventDefault();
  }
  render() {
    return (
      <form onSubmit={this.onSubmit}>
        <div>
          <input type="text" ref={this.name} />
        </div>
        <div>
          <input type="text" ref={this.phone} />
        </div>
        <div>
          <input type="submit" value="submit" />
        </div>
      </form>
    );
  }
}

使用效果为:

可以看到第一行的 submit form,以及第二行也成功输出了两个 input 输入框中的值。

如果只需要表单的值,并不需要对表单的内容进行验证,使用不受控表单会方便一些,通过对比同样的代码也能看到,非受控组件的代码量少一些,实现也更简单一些。

相对而言,这种业务的应用范围也相对较少。

注意,还记得 状态 & 状态更新 & 生命周期方法 中提到的报错信息吗:

其中将 input 中的 value 改成 defaultValue 这个解决方案,主要针对的就是非受控组件而进行的提议。在非受控组件中,可以通过添加 defaultValue 这个属性去为其设置默认值。

不受控组件

不受控表单是一种不受控组件,但是不受控组件的应用范围更广一些。

相较于受控组间,不受控组件的使用情况比较少,大多数情况下都会使用到第三方库去对 div 进行管理,如使用一些可视化处理的库,这种情况下也需要搭配 componentWillUnmount 方法去清理由第三方库管理的元素。

例如说 How to properly use componentWillUnmount() in ReactJs 中被采纳的答案就使用了一个案例:

import React from 'react';
import { Component } from 'react';
import ReactDom from 'react-dom';
import d3Chart from './d3charts';

export default class Chart extends Component {
  static propTypes = {
    data: React.PropTypes.array,
    domain: React.PropTypes.object,
  };

  constructor(props) {
    super(props);
  }

  componentDidMount() {
    let el = ReactDom.findDOMNode(this);
    d3Chart.create(
      el,
      {
        width: '100%',
        height: '300px',
      },
      this.getChartState()
    );
  }

  componentDidUpdate() {
    let el = ReactDom.findDOMNode(this);
    d3Chart.update(el, this.getChartState());
  }

  getChartState() {
    return {
      data: this.props.data,
      domain: this.props.domain,
    };
  }

  componentWillUnmount() {
    let el = ReactDom.findDOMNode(this);
    d3Chart.destroy(el);
  }

  render() {
    return <div className="Chart"></div>;
  }
}

这个案例中的 Chart 组件就不包含任何的状态,对图像进行的任何操作,都是由 d3Chart 本身去进行的状态管理。

另外,findDOMNode 这个函数在严格模式下,也就是 <React.StrictMode><其他组件 /></React.StrictMode> 下已经被废弃使用,并且官方建议使用 refs 代替 findDOMNode。

在使用第三方组件的时候一定要记得,如果在 componentDidMount 中有通过绑定 ref/findDOMNode 去创建新对象的情况,那么在 componentWillUnmount 一定要注意去销毁创建的对象,以防内存泄露的情况。

总结

这里主要就表单的内容进行了一定程度的深入了解,并且就非受控表单学习了 refs 的使用,以及在 React 中集成第三方库管理非受控组件需要注意的要点。

总结知识点如下:

  • 受控表单通过 state 去进行状态管理

    受控表单中的知识点包括:

    • ES6 的计算属性

      通过计算属性可以快速更新对象给定参数的值

    • 正确的使用 setState

      这里指代的内容,失去去更新有多个键值对的状态。

      为了性能的提升,React 存在压缩 setState,然后使用 batch update 去更新多个 setState,从而避免频繁的 UI 更新。这个时候,就要采取另外的写法去更新 etState,而不能通过直接调用 this.state 的方法去取值

  • 非受控表单,让页面自己去处理值更新的问题

    通过 refs 去更新和获取值

  • 非受控组件,包含非受控表单,应用范围更广

    采用了 StackOverflow 的一个案例,去学习非受控组件的应用

以上是关于[React 基础系列] 受控表单 vs 不受控表单的主要内容,如果未能解决你的问题,请参考以下文章

react 表单(受控组件和非受控组件)

React学习第四步:受控组件相关学习

React --- 收集表单数据(非受控组件和受控组件)

反应:HTML 表单应该是受控组件还是不受控组件?

react-生命周期

受控组件和不受控组件的区别