Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 奇技淫巧 - defaultValue 和虚拟 dom diff 算法实现表单重置 #12

Open
sixwinds opened this issue Dec 8, 2016 · 0 comments
Labels

Comments

@sixwinds
Copy link
Owner

sixwinds commented Dec 8, 2016

React 奇技淫巧 - defaultValue 和虚拟 dom diff 算法实现表单重置

我们知道 React 的标准模式是单向数据流,而其表单项通常需要监听 onChange 事件,然后通过改变外部的 value 来回写表单项的 value,譬如如下 input

class App extends React.Component {
  constructor( props ) {
    super( props );
    this.state = {
      inputValue: 'default'
    }

    this.inputChangeHandler = ( e )=>{
      this.setState( {
        inputValue: e.target.value
      } );
    }
  }
  render() {
    return (
      <div>
        <form>
          <input
            value={ this.state.inputValue }
            onChange={ this.inputChangeHandler }
          />
        </form>
      </div>
    )
  }
}

如果表单有很多表单项,那么这种标准的做法需要你写很多个 state 的属性和很多个 onChange 监听函数,这是一个体力活儿。但是一般的表单应用其实不需要实时监控表单项的用户输入,用 defaultValue 足以,在表单项目 onBlur 或者最后提交的时候一次验证获取用户输入即可,譬如:

class App extends React.Component {
  constructor( props ) {
    super( props );

    this.submit = ( e )=>{
      let userInputValue = this.refs.userInput.value;
      // 1. 验证 userInputValue
      // 2. 提交表单
    }
  }
  render() {
    return (
      <div>
        <form>
          <input 
            ref="userInput"
            defaultValue="default"
          />
          <button onClick={ this.submit }>提交</button>
        </form>
      </div>
    )
  }
}

这样就可以少写不少代码,当然你可以写一些工具去批量添加所有的 onChange 事件监听函数和对应的 state 的属性,譬如 redux-form。(回头一想,这种写法在提交时候也需要写很多获取用户输入的代码,如果使用第一种正模式,那么提交时候只需要获取 state 就可以了,不过这里先不讨论这些)

对于一个表单而言,通常还需要重置功能(reset),如果是第一种正模式的写法,我们只要保存一份初始化的默认值,在用户点击到了重置后,通过 setState 设回去就行了。但是如果使用第二种 defaultValue 的写法,那么就没有办法了,因为 defaultValue 只在第一次创建虚拟 dom 的时候有作用,如果 dom 不改变你改变 defaultValue 是没有用的。这个时候该怎么办呢?

嘿嘿!这个时候我们就可以用到这个奇技淫巧了。既然 defaultValue 是在创建虚拟 dom 的时候有用,那么我们在用户点击重置的时候让 React 重新创建这些表单项的虚拟 dom 不就好了么。根据 React 虚拟 dom diff 的算法,只要改变 dom 节点的类型就能促使在 diff 的时候重新创建虚拟 dom。具体的写法我们就用代码来演示下:

class App extends React.Component {
  constructor( props ) {
    super( props );
    // fieldSetWrapperType 是一个标志位属性,render 中会根据这个变量的值的不同,渲染不同的元素
    this.fieldSetWrapperType = 'div';
    this.submit = ( e )=>{
      let userInputValue = this.refs.userInput.value;
      // 1. 验证 userInputValue
      // 2. 提交表单
    }
    this.reset = ()=>{
      // 点击重置,改变标志位
      this.fieldSetWrapperType = this.fieldSetWrapperType === 'div' ? 'section' : 'div';
      // 强制刷新这个组件
      this.forceUpdate();
    }
  }
  // 把表单项的渲染抽象到一个方法中,避免重复编码
  renderFieldSet() {
    return (
      <input 
        ref="userInput"
        defaultValue="default"
      />
    );
  }
  render() {
    return (
      <div>
        <form>
          {
          /* 根据 fieldSetWrapperType 值的不同,渲染不同的元素(表单项的 wrapper 元素) */
          this.fieldSetWrapperType === 'div' ? 
          <div className="wrapper">{ this.renderFieldSet() }</div>
          :
          <section className="wrapper">{ this.renderFieldSet() }</section>
          }
          <button onClick={ this.submit }>提交</button>
          <button onClick={ this.reset }>重置</button>
        </form>
      </div>
    )
  }
}

思路就是在表单项外面包一层元素,每次点击重置后改变一个变量,再强制刷新这个组件,组件根据这个变量不同的值把这个包装元素的 type 改变,那么它下面的所有表单项的虚拟 dom 都会被重新创建,达到了重置的目的。不过这个效果依赖于 React 虚拟dom diff 算法。如果以后算法改变了,那么可能就失效了,而且这个写法是反模式的,我初衷是想在处理巨型表单时候少写点代码偷懒用。如果使用时候出现什么副作用,鄙人概不负责。

此技巧在写文章时 React 正处于 15.4.x 的版本

@sixwinds sixwinds added the react label Dec 8, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant