LoginSignup
2
0

More than 1 year has passed since last update.

React公式ドキュメント「state のリフトアップ」をFunctional Componentに書き換える

Posted at

この記事について

ReactにはClass ComponentFuncional Componentの2種類が存在しています。

Reactの公式ドキュメント「state のリフトアップ」はClass Componentで書かれているので、これをFuncional Componentに書き換えていきたいと思います。

この章では、与えられた温度で水が沸騰するかどうかを計算する温度計算ソフトを作成します。

まだまだ手探りで学習中の身ですので、より良い書き方がある場合などあればぜひご教示ください。

実行環境

  • react: 17.0.2
  • react-dom: 17.0.2
  • react-scripts: 4.0.3

開始

BoilingVerdictCalculatorのみが存在する状態から開始します。

BoilingVerdict
- 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なため書き換える必要は無いのですが、アロー関数での記法に統一したいため書き換えを行います。

Calculatorの宣言
- class Calculator extends React.Component {
+ const Calculator = () => {
Calculatorのconstructor
-   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);
Calculatorのrender
-   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 つ目の入力を追加する

この章ではCalculatorCalculatorTemperatureInputに分離しています。

scaleNames
+  const scaleNames = {
+    c: "Celsius",
+    f: "Fahrenheit",
+  };
TemperatureInput
+ 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>
+   );
+ }
Calculator
  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を保持しており同期されていません。これを同期させます。

TemperatureInput
  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を使用するよう変更します。

Calculator
  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としてtemperatureonTemperatureChangeを追加します。

まとめ

setStateというHookの登場によりFunctional Componentでもstateを管理できるようになったということを、実際に書き換えを行うことで学ぶことが出来ました。

実は今回は途中で妥協してClass Componentでもいいや、と投げ出しそうな予感がしたのでQiita記事を並行で書きながら試行錯誤する作戦で取り組んでみたのですが、効果抜群でコンコルド効果にも捕らわれたおかげで投げ出せなくなったのでオススメです。

完成コード

GitHubに完成コードをプッシュしています。

参考にした記事

2
0
0

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
2
0