state のリフトアップ
公式サイトのURLは https://ja.reactjs.org/docs/lifting-state-up.html
複数のコンポーネントが同一の変化するデータを反映する必要がある場合は、最も近い共通の祖先コンポーネントへ共有されている state
をリフトアップすること。
えられた温度で水が沸騰するかどうかを計算する温度計算ソフトの作成を通して、この手法を学ぶ。
BoilingVerdict
コンポーネントを作成- 温度
celsius
をprops
から受け取る - 水が沸騰するのに十分な温度か判定表示する
function BoilingVerdict(props) { if (props.celsius >= 100) { return <p>The water would boil.</p>; } return <p>The water would not boil.</p>; }
Calculator
コンポーネントを作成- 温度を入力する
<input>
要素を定義 - 入力された値を
this.state.temperature
に保持 - 現在の入力値を判定する
Boilingdict
をレンダー
class Calculator extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { temperature: '' }; } handleChange(e) { this.setState({ temperature: e.target.value }); } render() { const temperature = this.state.temperature; return ( <fieldset> <legend>Enter temperature in Celsius:</legend> <input value={temperature} onChange={this.handleChange} /> <BoilingVerdict celsius={parseFloat(temperature)} /> </fieldset> ); } }
2 つ目の入力を追加する
- 温度として華氏を受け取れるようにする
Calculator
クラスからTemperatureInput
コンポーネントを抽出props
としてc
もしくはf
の値をとるscale
を追加
const scaleNames = { c: 'Celsius', f: 'Fahrenheit' }; class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = {temperature: ''}; } handleChange(e) { this.setState({temperature: e.target.value}); } render() { const temperature = this.state.temperature; const scale = this.props.scale; return ( <fieldset> <legend>Enter temperature in {scaleNames[scale]}:</legend> <input value={temperature} onChange={this.handleChange} /> </fieldset> ); } }
2つの別個の温度入力フィールドをレンダーするように変更する。
class Calculator extends React.Component { render() { return ( <div> <TemperatureInput scale="c" /> <TemperatureInput scale="f" /> </div> ); } }
2つの入力フィールドが用意できた。これあの入力は連動していません。 Calculator
クラスは、 TemperatureInput
の温度を知らないからです。
変換関数の作成
- 摂氏から華氏に変換する関数を作成
- 華氏から摂氏に変換する関数を作成
function toCelsius(fahrenheit) { return (fahrenheit - 32) * 5 / 9; } function toFahrenheit(celsius) { return (celsius * 9 / 5) + 32; }
-文字列 temperature
と変換関数を引数に取り文字列を返す関数を作成。小数第 3 位までで四捨五入し、無効な temperature
は空の文字列を返す。
function tryConvert(temperature, convert) { const input = parseFloat(temperature); if (Number.isNaN(input)) { return ''; } const output = convert(input); const rounded = Math.round(output * 1000) / 1000; return rounded.toString(); }
公式サイトの例をデベロッパーツールの Console タブに入力する。
tryConvert('10.22', toFahrenheit) "50.396" tryConvert('abc', toCelsius) ""
stateのリフトアップ
すごく丁寧に解説されている。丁寧なあまりまわりくどくて要点をつかむのに苦労するのは、日本語力も低い私だけだろな。
React は単方向データバインディング。では、複数のコンポーネントを連携して、一方の状態の変化に応じて、他方の状態を変更できないのか?
いや、相互に連携して状態を変更する方法はある。複数のコンポーネントが所属する親要素から制御すればよいのです。その方法が述べられている。
reder()
に props.temperature
を渡します。 state
から props
に書き換えたことで何が変わるのか?
state
: そのコンポーネントが持っている状態
props
: 親コンポーネントから渡されたプロパティ
render() { const temperature = this.props.temperature; // const temperature = this.state.temperature;
まさにリフトアップしたのです。これで親要素から制御する準備が整いました。しかし、 props
は読み取り専用です。新たな問題が発生しました。
TemperatureInput
は、DOM の <input>
が value
と onChange
プロパティの両方を受け取るように、 親コンポーネントから値とイベントを受け取れる。
render() { handleChange(e) { this.props.onTemperatureChange(e.target.value); // this.setState({ temperature: e.target.value }); }
TemperatureInput
はここまでの変更を反映し次のようになっています。
class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); // this.state = { temperature: '' }; } handleChange(e) { this.props.onTemperatureChange(e.target.value); // this.setState({ temperature: e.target.value }); } render() { const temperature = this.props.temperature; // const temperature = this.state.temperature; const scale = this.props.scale; return ( <fieldset> <legend>Enter temperature in {scaleNames[scale]}</ legend> <input value={temperature} onChange={this.handleChange} /> </fieldset> ); } }