はじめに
- Reactを体系的に学習し、そのアウトプットの記事。
- Vueとの対比で理解を深めていく。
- React18、Vue3を使用。
- 本記事では、一番重要なstateを学習。
概要
- stateは値の変更をリアクティブに反映するための仕組み
- stateを更新することで、DOMの再レンダリングが行われ値が更新される。
- stateのスコープはコンポーネント内で閉じるため、再レンダリングの範囲もコンポーネント単位となる。
- そのため、stateの宣言はコンポーネント直下で行う(コンポーネントのトップレベル)
- 部分的な再レンダリングを可能とするため、仮想DOMを使用している。
- 子コンポーネントへstateを受け渡す場合は、propsの仕組みを使う。
基本的な使い方
- stateは値を保持する変数と、state値を更新する関数が分かれている。
- useState関数を用いて、state変数と、更新関数を配列形式で取得する。
// stateの利用にはuseStateを使用する
import { useState } from 'react';
const Sample = () => {
// useStateの引数には初期値を設定
// 戻り値には、state変数と、更新関数が配列で返却される。
let [val, setVal] = useState('test');
return (
<>
<input
type="text"
onChange={(e) => {
// stateの更新は更新関数を使用する。→仮想DOM上でコンポーネントの再レンダリングが走る
setVal(e.target.value);
}}
/>
// 再レンダリングが行われ、値がリアクティブに反映される
= {val}
</>
);
};
現在のstate値を使用した更新
-
stateの更新は非同期であることに注意する
-
stateの更新関数を実行すると、state値の更新と、DOMの再レンダリングを依頼する。
- state値の更新はDOMの再レンダリング後となり、この再レンダリング処理は非同期で行われる。
-
そのため、現在のstate値を元にした更新を行う際、以下の例だと上手くいかない。
const [count, setCount] = useState(0);
setCount(count + 1); // count === 0 → 0 + 1
setCount(count + 1); // count === 0 → 0 + 1
// 再レンダリングは非同期であるためまだ実施されていない。
// 従って2回目のsetCont時のcountは0のまま。
// 再レンダリング後の結果は1となる。
- 現在のstate値を元にした更新は、以下の例のように、引数に関数を使用する。
const [count, setCount] = useState(0);
setCount((prev) => prev + 1); // count === 0 → 0+1
setCount((prev) => prev + 1); // count === 1 → 1+1
// 更新関数の引数を利用すると、再レンダリング前でも直前のstate値が取得できる。
// 再レンダリング後の結果は2となる。
オブジェクト・配列のstate更新
- オブジェクトや配列は、元のstate値のプロパティを変更しても反映されない。
- オブジェクトの更新は新しいオブジェクト作成して更新関数に設定する必要がある。(配列も同様)
- 一部のプロパティのみを変更する場合などはスプレッド演算子でコピーオブジェクトを作るとよい。
const [human, setHuman] = useState({ name: 'jiro', age: 18 });
// 以下はNG:既存のオブジェクトの変更では反映されない。
//person.age = 20
//setPerson(person)
// 以下はOK:新規に作成したオブジェクトを更新関数に設定する
setHuman({ ...human, age: 20 });
// 配列も同様
const [nums, setNums] = useState([1, 2, 3, 4, 5]);
const newNUms = [...nums]; // スプレッド演算子でコピーした配列を新規に作成
newNums[1] = 10;
setNums(newNUms); // 新規に作成した配列を更新関数に設定
親子コンポーネント間でのstate共有
- stateのスコープはコンポーネント内であるため、コンポーネント間で共有する場合はpropsで受渡してやる。
- 子コンポーネント側でstateの更新も行う場合は更新関数も渡す。
- ※親子間以外のstate値の共有は、useContextやRedux(状態管理ライブラリ)を利用するがここでは割愛。
const Parent = () => {
// 親コンポーネントでstateを定義する
const [count, setCount] = useState(0);
return (
<>
// state値は、子コンポーネント側と共有される。
<div>parent: {count}</div>
// 子コンポーネントへprops経由で state値と更新関数を受け渡す。
<Children count={count} setCount={setCount} />
</>
);
};
// 子コンポーネント:state値と更新関数をprops経由で受け取る。
const Children = ({ count, setCount }) => {
const add = () => {
// 親からの更新関数を使用し、stateを更新する。
setCount((prev) => prev + 1);
};
return (
<>
// state値の表示(親コンポーネントと同期される)
{count}:<button onClick={add}>+</button>
</>
);
};
vueの場合
現在のstate値を使用した更新、オブジェクト・配列のstate更新
- vueにおけるstateの更新は
ref
関数を使用する。-
reactive
もあるが、個人的にref
推奨)
-
- ref関数を実行するとstate変数が返却される。script内では
.value
でアクセスする。- template内では
.value
は不要
- template内では
- 現在のstate値を用いた更新や、オブジェクト、配列の更新などは、
.value
経由になるだけで、普通の変数と同じように更新できる。(上述したReactの考慮は不要)
// 再レンダリング前の値を取得できる。
const count = ref(0);
count.value = count.value + 1; // count === 0 → 0 +1
count.value = count.value + 1; // count === 1 → 1 +1
// 結果は2となる。
// オブジェクトの更新は直接プロパティを変更するだけでよい
const human = ref({ name: 'taro', age: 20 });
human.value.age = 15;
// 配列も同様
const numArray = ref([1, 2, 3, 4, 5]);
numArray.value[1] = 10;
親子コンポーネント間でのstate共有
- 親子間でのstateの共有は、props/emitで行う。
- vueでは、親で定義したstateは子コンポーネント側での更新を禁止する。(一応更新できるのだが、警告が出るので非推奨)
- そのため、更新する際は、子コンポーネント側で親処理を呼び出し(emitの発火)、親コンポーネント側でstateの更新を行う。。
- ※React同様に親子間以外の共有は、privide/injectやPinia(状態管理ライブラリ)を利用するが、ここでは割愛。
Parent.vue
<script setup>
import { ref } from 'vue';
import Children from './Children.vue';
const count = ref(0);
const add = (cnt) => (count.value = cnt + 1);
</script>
<template>
<div>parente:{{ count }}</div>
// propsでstateを渡し、子からのemitで更新処理を行う。
<Children :count="count" @clicked="add"></Children>
</template>
Children.vue
<script setup>
const props = defineProps(['count']);
const emits = defineEmits(['clicked']);
</script>
<template>
// ボタンクリック時、propsのstate値を渡して、emitを発火する。
{{ props.count }}: <button @click="emits('clicked', props.count)">+</button>
</template>
おわりに
- 「vueはeasy、reactはsimple」と言うが、ことstate周りについては、「vueがeasyかつ、simple」に感じた。
- state周りに関しては総じて、Vue3のほうが使いやすいと思う。
- vue3でstate(リアクティブ)周りがかなり使いやすく進化している!
- vueの経験の方が長いので、vueよりな意見になってるかもだが。。
- ただし、子コンポーネントのstate共有は、Reactの方がよい。
- vueのprops/emitのバケツリレーは単純に面倒。
- Reactのように更新関数を渡したほうがわかりやすい。
- どこでstateが更新されたのかわからなくなる可能性もあるが、
useReducer
を使えばある程度把握できる。 - 深いコンポーネント階層へのstate受け渡しは、vue/reactどちらも、状態管理ライブラリ等の別の手段がある。
- どこでstateが更新されたのかわからなくなる可能性もあるが、