漫谈受控与非受控组件

在大多数状况下,咱们推荐使用 受控组件 来处理表单数据。在一个受控组件中,表单数据是由 React 组件来管理的。另外一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。

以上是 React 官网对受控组件与非受控组件的一次解释,大学刚刚毕业时候,看到这一段, 实在有些难以接受,在我看来,既然已经选择使用了 React ,就应该彻底完全的使用受控组件,为何开发者会有直接使用 DOM 节点开发的的非受控组件。当时在 vue 中,并无这种设定。同时我当时在开发 Sass 网站,由于在开发 pc 端网站老是须要即时验证(即时给予用户交互,不让用户在填写完整的数据后再提示错误以至于过度沮丧)。html

不过如今来看,非受控组件的确是 React 很是好的设计。前端

非受控与受控组件的区别与选择

非受控的输入就像传统的HTML表单输入同样:vue

class Form extends Component {
  /** 提交时候获取数据 */  
  handleSubmitClick = () => {
    const name = this._name.value;
    // 检测数据提示而后
  }
  render() {
    return  (
      <div>
        <input type="text" ref={input => this._name = input} />
        <button onClick={this.handleSubmitClick}>Sign up</button>
      </div>
    );
  }
}

咱们只有在触发其余事件(例如点击提交按钮时),才能够获取 DOM 数据中的值。react

受控的输入接受当前的值做为参数,而且在值发生改变的时候执行回调函数。git

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('提交的名字: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          名字:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}

当用户一旦进行了数据的修改,程序马上就能够知道哪些状态发生了改变,这时候咱们就能够基于状态修改来构建更好的表单交互与用户体验。github

受控与非受控组件能够类比服务器和客户端的 push 和 pull 模型,push 是服务器主动发送数据给客户端,一旦有“数据改变”就当即推送,因此用户能够基于最新的消息作处理,而 pull 模型无法知道数据的细节变化,即便遇到了错误输出也只能间接检查通知用户。gulp

若是你的表单在交互的过程当中足够简单,仅仅只须要提交时候验证,而且没有级联数据,强制格式输入等复杂用户交互。那么能够选择非受控组件。固然选择并非一次性的,咱们能够在开发过程当中迁移非受控组件到受控组件(应该没有小伙伴反向操做吧)。小程序

全量与增量问题

事实上,咱们在生活与开发过程当中,老是会遇到相似 React 中受控与非受控组件等同的问题。其实也就是全量与增量问题。基于不一样的领域,不一样的设计目标,不一样的用户,不一样的团队,当前受限的资源以及当下要完成的目标设定,都会影响问题的最终决策。这里我列出最近我遇到的几个问题。供你们参考阅读。api

老程序库 ts 升级

从去年开始,新项目都使用 TypeScript 进行开发,面对复杂的大型项目来讲,TypeScript 利好不言而喻。尤为在业务不稳定,面临大量改动时候。动态类型总会让人担惊受怕。对于老项目,咱们也想从中获得 TypeScript 的利好。安全

前端的 Vue 项目已经开发 3 年多了,当前的项目也从一个简单的单页面应用程序变成了基于业务的多页面应用。同时也拆分出来一系列的基础库与 widget。

咱们都知道修改必然是从基础升级,目前咱们还面临着繁重的开发任务,因此咱们没有时间资源进行全量升级,同时基础依赖中也有 Vue 业务组件。Vue 3 此时的开发进度也让咱们“进退两难”。因此咱们只能想去改动非组件的辅助代码。

这是咱们先增长了 tsc 编译配置,可让新模块使用 TypeScript,同时能够在空闲时候修改部分老代码。

可是,做为依赖库,仅仅只在内部 TypeScript 代码是不够的,进一步来讲,老大还想要定义文件(.d.ts)辅助其余项目开发。因此咱们使用 tsc 编译出当前代码的全部 TypeScript 定义文件,而后使用 gulp-insert 对定义文件进行修改后投入使用。

小程序组件开发

面对移动端 Sass 开发,咱们仍要提供复杂的表单,同时移动端不须要太多的交互。结合小程序 setData 很是耗费性能,同时小程序组件不像 Vue,React 有单向数据流的说法。因此数据放在各个组件内部反而更好,最后在提交时候,把各个组件的数据组合起来进行验证与对比提交,不管是性能仍是开发都具备更好的体验。因此小程序表单提交反而使用“非受控组件”更好。

固然,移动端表单的侧重点是减小用户的输入,开发的精力应该投入在表单的默认数据上,从而减小用户的输入操做。

查询非法字符

在用户创造价值的时代中,审核用户提供的数据必定是重中之重。而咱们目前小程序能够提供分享功能。而小程序自己也有查询文本安全的增值服务 api security.msgSecCheck

这时候咱们有两种选择,咱们能够每一次提交可分享数据时候检查并提示含有非法字符,这样用户能够清楚的知道该次的数据提交中有非法字符。固然也能够在最终分享的时候提示有非法信息,可是此时面对如此大的数据量分享,用户恐怕很难查询出到底是哪条信息出了问题。到底是那种方式更好?这取决于小程序的用户量以及投入的资源。

同时还有不少例子,例如 js 文件修改,到底是增量(字符串级别)仍是全量(单个 js 文件) ? 你们须要根据公司和项目来判断。因此究竟使用什么方式,取决于你的业务,所拥有的资源,甚至是你面对的客户量级。

鼓励一下

若是你以为这篇文章不错,但愿能够给与我一些鼓励,在个人 github 博客下帮忙 star 一下。
博客地址

参考资料

controlled-vs-uncontrolled-inputs-react