React 入門日記 04 (最終回)

前回

blog.takunology.jp

状態

ユーザの入力や何かしらの変更があったときにトリガーとして、何かしらの処理をすることができる。そのためには、データが変更される前の状態を保持しておく必要があるが、useState という関数を使えば状態が変更されたかを検出できるみたい。要は、状況に応じて処理が変わったり値が変わったりする機能を備えているということ。(これをステートフルという。)

const [ recipe, setRecipe ] = useState(initialRecipe);

上記のように記述した場合、setRecipe という関数によって recipe というオブジェクトの中身を変更することができるようになる。このとき、初期状態として useState 関数の引数に initialRecipe というオブジェクト(や変数)を入れておく。ちなみに recipe のように、状態が変更されるようなオブジェクトをステートフルオブジェクトというらしい。

あとは、その更新されたオブジェクトを見たいので、recipe オブジェクトを参照してあげればいい。このとき、値は動的になるので { } を使う。

<RecipeTitle title={recipe.title} feedback={recipe.feedback}/>

f:id:takunology:20220404170800p:plain

とりあえず、何も変化はしないけどステートフルオブジェクトを参照してデータは表示できているみたい。

ユーザイベント

ユーザによるタイミングで値やオブジェクトを変更するには、UI から何か入力されるか、ボタンをクリックされるかの「イベント」が重要になってくる。例えば、ボタンクリックの場合はこのようになる。

class Demo extends React.Component {
    displayMessage() {
        alert('なにクリックしとんねん');
    }

    render() {
        <button onClick={ () => displayMessage() }>クリックしてね</button>
    }
}

何か引数を渡すならこう。

class Demo extends React.Component {
    displayMessage(message) {
        alert(message);
    }

    render() {
        <button onClick={ () => displayMessage('なにクリックしとんねん') }>クリックしてね</button>
    }
}

WPF の場合は Click イベントでコードビハインドにイベントハンドラを定義すれば良いので、構成としては近いかも。UI 側は render() で定義できるし、onClick に呼び出したいメソッドを指定するだけ。特に EventArgs 的な引数を必要としないのがいいところかな。

オブジェクトの書き換え

変数と同じように代入演算子を使ってそのまま書き換えることができる。が、オブジェクトのような構造化されたデータを、例えば属性1つずつ全て書き直すのは骨が折れる。そこでオブジェクトをまるごとコピーできるのが「スプレッド演算子(スプレッド構文)」というやつ。

// コピー元のオブジェクト
let human = {
    name: 'Tanaka Taro',
    age: 20
}

// まるごとコピーする
let humanCopy = { ...human };

これでオブジェクトを複製できる。もし、一部だけ書き換えたい場合はコピーしてからその属性の持つ値を書き換えればいいので、さきにスプレッド構文を使ってあげればいい。

let human = {
    name: 'Tanaka Taro',
    age: 20
}

let humanCopy = { 
    ...human,
    age: 25
 };

クリックイベントの実装

App.js を少し書き換える。

function ingredientClick(index) {
    const updatedRecipe = { ... recipe };
    updatedRecipe.ingredients[index].prepared = !updatedRecipe.ingredients[index].prepared;
    setRecipe(updatedRecipe);
}

こうすると、updatedRecipe は recipe のオブジェクトがコピーされる。各レシピにて ingredients 配列の中にある prepared の値が変更されれば、setRecipe にてオブジェクトが変更される。これで ingredienClick 関数を呼び出せば値が更新されるイベントを発行できるので、トリガーを用意すれば良い。

<IngredientList ingredients={recipe.ingredients} onClick={ingredientClick} />

で、ingredients の props に recipe.ingredients と onClick トリガーを渡しているのでこれらを IngredientList.jsx から受け取って処理を書いてあげる。

return (
    <li key={index} 
        className={ ingredient.prepared ? 'prepared' : '' }
        onClick={() => props.onClick(index)}
    >
        { ingredient.name }
    </li>
);

li 属性に onClick イベントを適用しているので、箇条書きの部分をクリックしてあげれば recipe.ingredents.prepared の値が変更される。つまり、レシビオブジェクト内にある、材料リストの取り消し線をオンオフできるというわけだ。

f:id:takunology:20220404185436g:plain

副作用フック

React 16.8 以上で追加された機能で、class を書かなくても React の機能が使える様になるらしい。要はユーザイベントで書いたような、

class Demo extends React.Component {
    displayMessege () {
    // 処理
    }
    render () {
    // UI の要素
    }
}

みたいなものを書かなくても、こんな風に書けてしまう。

useEffect(() => {
    // 処理
});

ステートフルオブジェクト(依存関係)がある場合はこんな感じ。

useEffect(() => {
    // 処理
}, [ someStatefulObject ]);

と、言われても今の時点では何もわからん。結局、この記述法で何が変わるのか。

フックの追加

今回のアプリの例では材料リストで、準備ができたものから取り消し線をつけていき、全て取り消し線がついたら「準備完了」を表示させるようにするらしい。とりあえずやっていく。まずは App.jsx に状態プロパティを定義。

const [prepared, setPrepared] = useState(false);

この状態では prepared オブジェクトには false で初期化される。これを setPrepared 関数を呼び出すことで prepared の値を変更させるのだろう。

useEffect(() => {
    setPrepared(recipe.ingredients.every(i => i.prepared));
}, [recipe]);

よく見たら useEffect ステートフルオブジェクトは第二引数だった。この引数には、オブジェクトを入れておくことで「変更されているか」を自動的に判定してくれるみたい。なので、特に変更がなければ useEffect は実行されないみたい。

every は「指定した条件に一致する、すべての項目に基づいてブール値を返す」という関数らしい。ラムダ式の中は prepared に一致するもの(つまりは全ての要素)を上書きするようになっている。という認識でいいのかな?

あとは表示用の UI を設定してあげれば良い。

{prepared ? <h2>準備完了!</h2> : <h2>材料が揃っていません</h2>}

動かしてみると、箇条書きの取り消し線の状態によって表示が変化することが確認できた。

f:id:takunology:20220404192355g:plain

これでラーニングパスは完了みたい。とりあえず、useEffect で副作用フックを定義できるし、useState でステートフルオブジェクトを利用できることがわかった。

f:id:takunology:20220404193227p:plain

一通り終えた感想

React のプロジェクト作成方法、コンポーネントの作成と導入、レンダリング方法、状態とイベント、ステートフルオブジェクト、フックなど盛りだくさんの内容だったので全然覚えきれていないというか、経験不足な気がする。なので、もっと React と戯れたほうがいいなとは思った。けど、あくまでも目的は Fluent UI を使うことなので、今度は TypeScript へ入門しないといかんな。

あと、ブログを書きながらラーニングパスやってるとかなり時間がかかることに気づいたので、日記は書くとしても「何やったか」をざっくり書く感じにしたほうが効率がいいかも。全部書いては検証してを繰り返すと版日くらい消費しちゃう。

今回を持って React 入門日記は終了となります~。

イベントのお知らせ

2022年4月24日(日)に MS Tech Camp #14 を開催します。今回は私「たくのろじぃ」主催で、Blazor を使ったポートフォリオサイト製作と Azure Static Web Apps へのデプロイをやります。
興味がある方は(社会人、学生問わず)ぜひご参加ください!
Youtube での配信ですので、休日はごろごろしながらご覧いただければと思います。もちろん、一緒にやっていただいても結構です!

mspjp.connpass.com