296
264

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

一人React.jsAdvent Calendar 2014

Day 10

React.jsでFormを扱う

Last updated at Posted at 2014-12-10

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

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

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

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

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>
    );
  }
});

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>
    );
  }
});

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)
    );
  }

  • 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);
  };
}

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

  • ここで、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);
}

  • 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;
  }

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


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

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

296
264
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
296
264

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?