今回は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に注目してみると、requestChange
はe.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
属性について書きたいと思います。