Edited at

React.jsでFormを扱う

More than 3 years have passed since last update.

今回は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属性について書きたいと思います。