この記事について
Reactには__Class Component__と__Funcional Component__の2種類が存在しています。
Reactの公式ドキュメント「state のリフトアップ」はClass Componentで書かれているので、これをFuncional Componentに書き換えていきたいと思います。
この章では、与えられた温度で水が沸騰するかどうかを計算する温度計算ソフトを作成します。
まだまだ手探りで学習中の身ですので、より良い書き方がある場合などあればぜひご教示ください。
実行環境
- react: 17.0.2
- react-dom: 17.0.2
- react-scripts: 4.0.3
開始
BoilingVerdict
とCalculator
のみが存在する状態から開始します。
- function BoilingVerdict(props) {
+ const BoilingVerdict = (props) => {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
BoilingVerdict
は初めからfunctionalなため書き換える必要は無いのですが、アロー関数での記法に統一したいため書き換えを行います。
- class Calculator extends React.Component {
+ const Calculator = () => {
- constructor(props) {
- super(props);
- this.handleChange = this.handleChange.bind(this);
- this.state = { temperature: "" };
- }
- handleChange(e) {
- this.setState({ temperature: e.target.value });
- }
+ const [temperature, setTemperature] = useState("");
+ const handleChange = (e) => setTemperature(() => e.target.temperature);
- render() {
- const temperature = this.state.temperature;
return (
<fieldset>
<legend>Enter temperature in Celsius:</legend>
- <input value={temperature} onChange={this.handleChange} />
+ <input value={temperature} onChange={handleChange} />
<BoilingVerdict celsius={parseFloat(temperature)} />
</fieldset>
);
- }
}
stateはHookを使うことでかなり簡潔に書けるようになります。Fnctionalに書き換えることで参照時にthis
を使用する必要がなくなります。なおHookはconst [state要素名, state更新用関数] = useState(state初期値);
となっています。
handleChange
についてもbind
が不要となりました。
2 つ目の入力を追加する
この章ではCalculator
をCalculator
とTemperatureInput
に分離しています。
+ const scaleNames = {
+ c: "Celsius",
+ f: "Fahrenheit",
+ };
+ const TemperatureInput = (props) => {
+ const [temperature, setTemperature] = useState("");
+ const handleChange = (e) => setTemperature(() => e.target.value);
+ const scale = props.scale;
+ return (
+ <fieldset>
+ <legend>Enter temperature in {scaleNames[scale]}:</legend>
+ <input value={temperature} onChange={handleChange} />
+ </fieldset>
+ );
+ }
const Calculator = () => {
- const [temperature, setTemperature] = useState("");
- const handleChange = (e) => setTemperature(() => e.target.value);
return (
- <fieldset>
- <legend>Enter temperature in Celsius:</legend>
- <input value={temperature} onChange={handleChange} />
- <BoilingVerdict celsius={parseFloat(temperature)} />
- </fieldset>
+ <div>
+ <TemperatureInput scale="c" />
+ <TemperatureInput scale="f" />
<BoilingVerdict celsius={parseFloat(temperature)} />
+ </div>
);
}
const TemperatureInput = (props) => {
はconst TemperatureInput = ({ scale }) => {
でも大丈夫です。
変換関数の作成
3つの関数を追加します。ドキュメントと異なる点はアロー関数に変更した点のみです。
const toCelsius = (fahrenheit) => {
return ((fahrenheit - 32) * 5) / 9;
};
const toFahrenheit = (celsius) => {
return (celsius * 9) / 5 + 32;
};
const 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();
};
state のリフトアップ
ドキュメントでも触れている通り、現在のコードでは2つのTemperatureInput
は独立したtemperature
を保持しており同期されていません。これを同期させます。
const TemperatureInput = (props) => {
- const [temperature, setTemperature] = useState("");
+ const temperature = props.temperature;
- const handleChange = (e) => setTemperature(() => e.target.value);
+ const handleChange = (e) => props.onTemperatureChange(() => e.target.value);
const scale = props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature} onChange={handleChange} />
</fieldset>
);
}
TemperatureInput
でstateを管理するのをやめてCalculator
で管理させるため、temperature
をstateからpropsに変更します。handleChange
もpropsのonTemperatureChange
を使用するよう変更します。
const Calculator = () => {
+ const [temperature, setTemperature] = useState("");
+ const [scale, setScale] = useState("c");
+ const handleCelsiusChange = (temperature) => {
+ setTemperature(temperature);
+ setScale("c");
+ };
+ const handleFahrenheitChange = (temperature) => {
+ setTemperature(temperature);
+ setScale("f");
+ };
+ const celsius = scale === "f" ? tryConvert(temperature, toCelsius) : temperature;
+ const fahrenheit = scale === "c" ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
- <TemperatureInput scale="c" />
+ <TemperatureInput
+ scale="c"
+ temperature={celsius}
+ onTemperatureChange={handleCelsiusChange}
+ />
- <TemperatureInput scale="f" />
+ <TemperatureInput
+ scale="f"
+ temperature={fahrenheit}
+ onTemperatureChange={handleFahrenheitChange}
+ />
<BoilingVerdict celsius={parseFloat(temperature)} />
</div>
);
}
TemperatureInput
のstateを削除したので親要素であるCalculator
にstateを追加します。渡すpropsとしてtemperature
とonTemperatureChange
を追加します。
まとめ
setState
というHookの登場によりFunctional Componentでもstateを管理できるようになったということを、実際に書き換えを行うことで学ぶことが出来ました。
実は今回は途中で妥協してClass Componentでもいいや、と投げ出しそうな予感がしたのでQiita記事を並行で書きながら試行錯誤する作戦で取り組んでみたのですが、効果抜群でコンコルド効果にも捕らわれたおかげで投げ出せなくなったのでオススメです。
完成コード
GitHubに完成コードをプッシュしています。