React.jsでFormを扱う

  • 182
    いいね
  • 5
    コメント
この記事は最終更新日から1年以上が経過しています。

今回はReact.jsでのFormの扱いについて書きたいと思います。

例えばReact.jsでは↓のようなことをすると変更出来ないテキストフィールドになってしまいます。どうしてなのかという話ですね。

// ダメ
<input type="text" value="initial value" />

// ダメ
<input type="text" value={this.state.textValue} />

http://jsfiddle.net/koba04/kb3gN/8198/

Controlled Component

Controlled ComponentとはStateによって値を管理するComponentのことでテキストフィールドの場合はこのようになります。

var Text = React.createClass({
  getInitialState() {
    return {
      textValue: "initial value"
    };
  },
  changeText(e) {
    this.setState({textValue: e.target.value});
  },
  render() {
    return (
      <div>
        <p>{this.state.textValue}</p>
        <input type="text" value={this.state.textValue} onChange={this.changeText} />
      </div>
    );
  }
});

value値をStateで管理しつつ、onChangeで明示的にsetStateして更新してあげる必要があります。

UnControlled Component

UnControlled Componentとは逆に値を管理しないComponentで、初期値を設定した場合はdefaultValueに設置する必要があります。

この場合は↑のようにonChangeで常にStateに反映してもいいですし、反映したい時にDOMからvalueを取得することも出来ます。

var LiveText = React.createClass({
  getInitialState() {
    return {
      textValue: "initial value"
    };
  },
  changeText(e) {
    this.setState({textValue: this.refs.inputText.getDOMNode().value });
  },
  render() {
    return (
      <div>
        <p>{this.state.textValue}</p>
        <input type="text" ref="inputText" defaultValue="initial value" />
        <button onClick={this.changeText}>change</button>
      </div>
    );
  }
});

textarea

テキストエリアの場合は、テキストフィールドと同じようにvalueに値を指定します。
HTMLの時と同じように<textarea>xxxx</textarea>とするとそれはdefaultValueとして扱われます。

var OreTextArea = React.createClass({
  getInitialState() {
    return {
      textAreaValue: "initial value"
    };
  },
  onChangeText(e) {
    this.setState({textAreaValue: e.target.value});
  },
  onClick() {
    this.setState({textAreaValue: this.refs.textArea.getDOMNode().value}); 
  },
  render() {
    return (
      <div>
        <div>{this.state.textAreaValue}</div>
        <div>
          <textarea value={this.state.textAreaValue} onChange={this.onChangeText} />
        </div>
        <div>
          <textarea ref="textArea">this is default value</textarea>
          <button onClick={this.onClick}>change</button>
        </div>
      </div>
    );
  }
});

http://jsfiddle.net/koba04/wkkvrh4m/2/

select

セレクトボックスの場合もvalueに値を設定します。multiple={true}のPropを指定するとvalueに配列を指定できるようになり、複数要素を選択することができるようになります。

var OreSelectBox = React.createClass({
  getDefaultProps() {
    return {
      answers: [1, 10, 100, 1000]
    };
  },
  getInitialState() {
    return {
      selectValue: 1,
      selectValues: [1,100]
    };
  },
  onChangeSelectValue(e) {
    this.setState({selectValue: e.target.value});
  },
  // 他に良い方法があるかもい...
  onChangeSelectValues(e) {
    var values = _.chain(e.target.options)
      .filter(function(option) { return option.selected })
      .map(function(option) { return +option.value })
      .value()
    ;
    this.setState({selectValues: values});
  },
  render() {
    var options = this.props.answers.map(function(answer) {
      return <option value={answer} key={answer}>{answer}</option>;
    });
    return (
      <div>
        <div>selectValue: {this.state.selectValue}</div>
        <div>
          <select value={this.state.selectValue} onChange={this.onChangeSelectValue}>
            {options}
          </select>
        </div>
        <div>selectValues: {this.state.selectValues.join(",")}</div> 
        <div>
          <select multiple={true} defaultValue={this.state.selectValues} onChange={this.onChangeSelectValues}>
            {options}
          </select>
        </div>     
      </div>
    );
  }
});

http://jsfiddle.net/koba04/khdftsuu/

LinkedStateMixin

LinkedStateMixinというaddonを使うとこんな感じでonChangeを実装しなくてもstateに反映することも出来ます。
CheckBoxに対して使用する場合は、checkedLinkとなります。

var React = require('react/addons');
var LinkedStateMixin = React.createClass({
  mixins: [React.addons.LinkedStateMixin],
  getInitialState() {
    return {
      textValue: "initial value"
    }
  },
  render() {
    return (
      <div>
        <div>value: {this.state.textValue}</div>
        <input type="text" valueLink={this.linkState('textValue')} />
      </div>
    );
  }
});

このMixinがやっていることは簡単ですし、中を見るきっかけとして追ってみるのも面白いかと思います。

蛇足(LinkedStateMixinの挙動がどうなってるのか確認してみる)

  • まずMixinされるlinkStateを見てみると、ReactLinkというオブジェクトをvalueと何か作成されたSetterが渡されて作成しているのがわかります。
  linkState: function(key) {
    return new ReactLink(
      this.state[key],
      ReactStateSetters.createStateKeySetter(this, key)
    );
  }

https://github.com/facebook/react/blob/3aa56039c60add45eb30f1edbaf40ddf195c54ce/src/addons/link/LinkedStateMixin.js#L31-L36

  • ReactStateSetters.createStateKeySetterの中を見てみると、渡したStateのkeyに対してsetStateする関数を作成しています。
  createStateKeySetter: function(component, key) {
    // Memoize the setters.
    var cache = component.__keySetters || (component.__keySetters = {});
    return cache[key] || (cache[key] = createStateKeySetter(component, key));
  }
};

function createStateKeySetter(component, key) {
  // Partial state is allocated outside of the function closure so it can be
  // reused with every call, avoiding memory allocation when this function
  // is called.
  var partialState = {};
  return function stateKeySetter(value) {
    partialState[key] = value;
    component.setState(partialState);
  };
}

https://github.com/facebook/react/blob/3aa56039c60add45eb30f1edbaf40ddf195c54ce/src/core/ReactStateSetters.js#L45-L61

  • ReactLinkのConstructorでは値とrequestChangeとしてcreateStateKeySetterで作成した関数を保持されています。
function ReactLink(value, requestChange) {
  this.value = value;
  this.requestChange = requestChange;
}

https://github.com/facebook/react/blob/3aa56039c60add45eb30f1edbaf40ddf195c54ce/src/addons/link/ReactLink.js#L44-L47

  • ここで、ReactLinkを渡しているvalueLinkのPropに注目してみると、requestChangee.target.valueが渡されて呼ばれることがわかります。
function _handleLinkedValueChange(e) {
  /*jshint validthis:true */
  this.props.valueLink.requestChange(e.target.value);
}

/**
  * @param {SyntheticEvent} e change event to handle
  */
function _handleLinkedCheckChange(e) {
  /*jshint validthis:true */
  this.props.checkedLink.requestChange(e.target.checked);
}

https://github.com/facebook/react/blob/3aa56039c60add45eb30f1edbaf40ddf195c54ce/src/browser/ui/dom/components/LinkedValueUtils.js#L58-L69

  • InputのComponentを見てみると、onChangeイベントでvalueLinkがあれば、_handleLinkedValueChangeを呼ばれて結果setStateされることがわかるかと思います。
  getOnChange: function(input) {
    if (input.props.valueLink) {
      _assertValueLink(input);
      return _handleLinkedValueChange;
    } else if (input.props.checkedLink) {
      _assertCheckedLink(input);
      return _handleLinkedCheckChange;
    }
    return input.props.onChange;
  }

https://github.com/facebook/react/blob/3aa56039c60add45eb30f1edbaf40ddf195c54ce/src/browser/ui/dom/components/LinkedValueUtils.js#L140-L149

  _handleChange: function(event) {
    var returnValue;
    var onChange = LinkedValueUtils.getOnChange(this);
    if (onChange) {
      returnValue = onChange.call(this, event);
    }

https://github.com/facebook/react/blob/3aa56039c60add45eb30f1edbaf40ddf195c54ce/src/browser/ui/dom/components/ReactDOMInput.js#L114-L119


最後はちょっと脱線しましたが、今回はFormの操作について書きました。

明日はReact.jsのVirtualDOM実装で重要な役割を担っているkey属性について書きたいと思います。

この投稿は 一人React.js Advent Calendar 201410日目の記事です。