1.はじめに
私は
「React ってファイルを行ったり来たりして、わけわからん!」
「 React めちゃむずい」
って思っていました。
この React の行ったり来たりの流れを整理するために本記事を書きました。
同じように思われている方のお役に立てれば幸いです。
本記事は、「ステート管理」について見ていきます。
ステート の 超 基礎的な内容となっています。
※ ステート管理には、useState 以外の フック や ライブラリ 等は使用しておりません。
超 簡単な内容でどのように state の値が渡っていくのかをみていきたいと思います。
もし間違って認識しているところ等ありましたらご指摘いただけますと幸いです。
2.目次
1.はじめに
2.目次
3.この記事でわかること
4.環境
5.ステート管理
5.1.上から下にステート値を渡す
5.2.下から上にステート値を渡す
6.おわりに
7.参考
3.この記事でわかること
ステートの値が、
- 親コンポーネント → 子コンポーネント → 孫コンポーネント(上から下へステート値を渡す)
- 孫コンポーネント → 子コンポーネント → 親コンポーネント(下から上へステート値を渡す)
という流れを見ていきます。
ルートコンポーネントでステート値を受け取って親・子・孫コンポーネントに値を渡して、ユーザー情報を表示させ、
孫コンポーネントの削除ボタンをクリックして、子・親コンポーネントへ変更したステート値を渡す流れとなります。
↓ こんな感じの簡単なアプリです。
最終的なソースコードは下記です。
型定義の src/user.ts や src/userList.ts、ユーザー情報の src/user-data.json については、本記事では振れません。
あと、src/index.tsx はコンポーネントを表示するためのファイルなので、こちらも説明は省略します。
上記のソースコードについては、こんな風に定義してるんだな程度で見ていただけますと幸いです。
(下記をコピーすると動くようにはなっています。)
export type User = {
id: string;
name: string;
age: number;
onRemoveUser?: (id: string) => void;
onRemove?: (id: string) => void;
};
import type { User } from "./user";
export type UserList = {
users: User[];
onRemoveUser?: (id: string) => void;
};
[
{
"id": "dahis1",
"name": "daishi",
"age": 10
},
{
"id": "manju2",
"name": "manju",
"age": 20
},
{
"id": "daishiman3",
"name": "daishiman",
"age": 30
}
]
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
import { useState } from "react";
import userData from "./user-data.json";
import { UserList } from "./UserList";
import type { User } from "./user";
export default function App() {
const [users, setUsers] = useState<User[]>(userData);
const removeUser = (id: string) => {
const newUsers = users.filter((user) => user.id !== id);
setUsers(newUsers);
};
return (
<div>
<UserList users={users} onRemoveUser={removeUser} />
</div>
);
}
import { FC } from "react";
import { User } from "./User";
import type { UserList as UserListType } from "./userList";
export const UserList: FC<UserListType> = ({
users = [],
onRemoveUser = (f) => f
}) => {
if (!users.length) return <div>表示できるユーザーはいません</div>;
return (
<div>
{users.map((user) => (
<User key={user.id} {...user} onRemove={onRemoveUser} />
))}
</div>
);
};
import { FC } from "react";
import type { User as UserType } from "./user";
export const User: FC<UserType> = ({
id,
name,
age,
onRemove = (f) => f
}) => {
return (
<section>
<p>
{name}(年齢:{age}歳)
</p>
<button onClick={() => onRemove(id)}>削除</button>
</section>
);
};
4.環境
- react: 17.0.2
- typescript: 4.4.2
5.ステート管理
ステートの値の管理をユーザー情報を表示したり、削除する簡単な流れを見ていきます。
-
親コンポーネント → 子コンポーネント → 孫コンポーネント
(上から下へステート値を渡す) -
孫コンポーネント → 子コンポーネント → 親コンポーネント
(下から上へステート値を渡す)
という流れで見ていきます。
1 . 親コンポーネント → 子コンポーネント → 孫コンポーネント(上から下へステート値を渡す)
では、src/user-data.json のユーザー情報を、親(App)コンポーネントから孫(User)コンポーネントまでステート値を渡していき、ユーザー情報を表示します。
2 . 孫コンポーネント → 子コンポーネント → 親コンポーネント(下から上へステート値を渡す)
では、孫(User)コンポーネントの削除ボタンをクリックすることで、表示するステート値のユーザー情報を減らしていきます。
これらの流れを見ながら ステートの値の行ったり来たり を確認したいと思います。
5.1.上から下にステート値を渡す
では、まずはステートの値を上(親)から下(孫)に渡す流れを見ていきます。
今回使用するソースコードは下記のような構成となっています。
- 親コンポーネント:
src/App.tsx - 子コンポーネント:
src/UserList.tsx - 孫コンポーネント:
src/User.tsx
5.1.1.親コンポーネントから子コンポーネントにステート値を渡す
まず、親(App)コンポーネントを見ていきます。
import { useState } from "react";
import userData from "./user-data.json";
import { UserList } from "./UserList";
import type { User } from "./user";
export default function App() {
const [users] = useState<User[]>(userData);
return (
<div>
<UserList users={users} />
</div>
);
}
App コンポーネントの構成は
- ステート管理を
useStateで行う - ステート管理している値(
users)をUserListコンポーネントに渡す -
UserLIstを表示する
となっています。
では細かく見ていきましょう。
1. ステート管理を useState で行う
ステート値を useState フックを用いて管理します。
( ↓ useState の参考記事)
下記の部分になります。
const [users] = useState<User[]>(userData);
まだ値を更新しないので、更新用の関数は記述していません。
src/user-data.json の userData を初期値として 変数 users に代入しています。
2. ステート管理している値(users)を子コンポーネントに渡す
子(UserList)コンポーネントの users 属性に変数 users の値を渡しています。
<UserList users={users} />
上記の UserList コンポーネントの定義は、次の子(UserList)コンポーネントで行っています。
3. UserLIst を表示する
src/App.tsx では、UserList コンポーネントを表示しています。
ここまでが、親(App)コンポーネントのステート値を渡す流れです。
5.1.2.子コンポーネントから孫コンポーネントにステート値を渡す
次に、親(App)コンポーネントから受け取ったステート値を孫(User)コンポーネントに渡す 子(UserList)コンポーネント について見ていきます。
今回は、UserList コンポーネントを使用しているのは App.tsx ですが、
コンポーネント(部品)という言葉の通り、いろいろなところで UserList コンポーネントを使用することができます。
これが、コンポーネントが便利な所以ですね。
話を戻して、ソースコードを見てきましょう。
import { FC } from "react";
import { User } from "./User";
import type { UserList as UserListType } from "./userList";
export const UserList: FC<UserListType> = ({
users = [],
}) => {
if (!users.length) return <div>表示できるユーザーはいません</div>;
return (
<div>
{users.map((user) => (
<User key={user.id} {...user} />
))}
</div>
);
};
UserList コンポーネントのは下記の構成でできています。
-
UserListはアロー関数で記述(アロー関数の参考記事) - 引数の
users = []はusers: users = []を省略 -
users.lengthが false (0)のときの返り値 -
users.lengthが false (0)でないときの返り値
1. UserList はアロー関数で記述
最初はややこしく見えるかもしれませんが、噛み砕いて見てみるとアロー関数を使っています。
const const UserList: FC<UserListType> = () => {}
UserLIstコンポーネントは、これ ↑ を使っています。
2. 引数の users = [] は users: users = [] を省略
UserList のアロー関数の引数の users というのは、 users: users を省略しています。(参考記事)
なので、 users 属性に users の値を代入していることになります。
そして、users の値が 空 のときは [] がデフォルトで代入されます。
つまりこれらをまとめると、
users = [] は users: users = [] と同じで省略記法を用いて記述しています。
3. users.length が false (0)のときの返り値
if (!users.length) return <div>表示できるユーザーはいません</div>;
の通り、users.length が false(0) のときは、
<div>表示できるユーザーはいません</div>;
を表示します。
4. users.length が false (0)でないときの返り値
users.length が false(0) でないときは、
<div>
{users.map((user) => (
<User key={user.id} {...user} />
))}
</div>
の処理が動きます。
users の配列を map 関数により User コンポーネントの配列に変換して表示します。
{...user} は、user.id 以外の user.name、user.age を一つにまとめて 孫(User)コンポーネントに渡しています。
5.1.3.孫コンポーネントでステート値を受け取る
最後に、孫(User)コンポーネントを見ていきます。
親(App)から子(UserList)そして孫(User)にステート値が渡ってきて、
最後に渡ってきた値をどのように表示するかのコンポーネントとなっています。
import { FC } from "react";
import type { User as UserType } from "./user";
export const User: FC<UserType> = ({
name,
age,
}) => {
return (
<section>
<p>
{name}(年齢:{age}歳)
</p>
</section>
);
};
User コンポーネントの構成は
-
UserListはアロー関数で記述(アロー関数の参考記事) - 引数の
nameやageはname: nameやage: ageを省略 - 名前と年齢を表示
となっています。
1. UserList はアロー関数で記述
先ほどと同じ内容なので詳細は省略しますが、
const User: FC<UserType> = () => {};
ということですね。
2. 引数の name や age は name: name や age: age を省略
こちらも同じなので、説明は省略しますが、
-
nameはname: nameの省略系 -
yeraはyera: ageの省略系
3. 名前と年齢を表示
親コンポーネントから順番に渡ってきたステート値は、ここでやっと表示する形になります。
return (
<section>
<p>
{name}(年齢:{age}歳)
</p>
</section>
);
長い道のりでしたが、
親(App)コンポーネントから孫(User)コンポーネントでまでステートの値を渡し、表示する流れをつかめたかと思います。
次は、孫(User)コンポーネントに削除バタンを定義して、削除するステート値を親(App)コンポーネントまで渡す流れを見ていきます。
5.2.下から上にステート値を渡す
今回の実装する概要は、下記の通りです。
- 孫コンポーネントに削除ボタンを定義
- 削除したステート値を親コンポーネントまで伝える
- 親コンポーネントでステートを更新
- 以降は、上からステート値を渡すと同じ流れ
では早速、孫コンポーネントに追記してきます。
5.2.1.孫コンポーネントに削除機能を追加
孫(User)コンポーネントに削除機能を追加します。
ここでやることは、
クリックしてどのユーザーのイベントが発生したか(削除するか)を 子(UserList)コンポーネント に伝えることです。
import { FC } from "react";
import type { User as UserType } from "./user";
export const User: FC<UserType> = ({
+ id,
name,
age,
+ onRemove = (f) => f
}) => {
return (
<section>
<p>
{name}(年齢:{age}歳)
</p>
+ <button onClick={() => onRemove(id)}>削除</button>
</section>
);
};
流れを見てみましょう。
- 削除ボタンをクリックイベントが発生する
- 削除するための
onRemove関数を呼び出す -
onRemove関数は、Userコンポーネントに新たに追加されたプロパティ -
onRemove関数は、引数にid値を受け取る(どのユーザーかを認識する) -
onRemove関数は、idを受け取り、idを返す - 孫(
User)コンポーネントを使う子(UserList)コンポーネントにidを返す
このようにして、孫(User)コンポーネントから子(UserList)コンポーネントにステート値を渡します。
5.2.2.子コンポーネントに削除機能を追加
次に、孫(User)コンポーネントから伝わった id を親(App)コンポーネントに伝えるロジックを追加します。
孫(User)コンポーネントでやった id の受け渡しをここでもやります。
import { FC } from "react";
import { User } from "./User";
import type { UserList as UserListType } from "./userList";
export const UserList: FC<UserListType> = ({
users = [],
+ onRemoveUser = (f) => f
}) => {
if (!users.length) return <div>表示できるユーザーはいません</div>;
return (
<div>
{users.map((user) => (
- <User key={user.id} {...user} />
+ <User key={user.id} {...user} onRemove={onRemoveUser} />
))}
</div>
);
};
では、流れを見ていきましょう。
- 孫(
User)コンポーネントからonRemove属性の値としてidを受け取る - 1.で受け取った
idは、onRemoveUser関数に代入される -
onRemoveUser関数は、UserListコンポーネントに新たに追加されたプロパティ -
onRemoveUser関数に代入されたidは、そのままidを返す - 子(
UserList)コンポーネントを使う親(App)コンポーネントにidを返す
流れは、孫(User)コンポーネントでやった内容とほぼ同じですね。
5.2.3.親コンポーネントに削除機能を追加
最後に、親(App)コンポーネントに削除機能を追加します。
ここでは、子(UserList)から親(App)に渡ってきたステート値を更新するためのロジックの追加が必要になります。
import { useState } from "react";
import userData from "./user-data.json";
import { UserList } from "./UserList";
import type { User } from "./user";
export default function App() {
- const [users] = useState<User[]>(userData);
+ const [users, setUsers] = useState<User[]>(userData);
+ const removeUser = (id: string) => {
+ const newUsers = users.filter((user) => user.id !== id);
+ setUsers(newUsers);
+ };
return (
<div>
- <UserList users={users} />
+ <UserList users={users} onRemoveUser={removeUser} />
</div>
);
}
では、最後の流れを見ていきましょう。
-
useStateフックにusersを更新するsetUser変数を追加する - 子(
UserList)コンポーネントからonRemoveUser属性の値としてidを受け取る - 1.で受け取った
idは、removeUser関数に代入される -
removeUser関数は、
4.1.users配列からfilter関数を用いてuser.idがidと一致しないユーザーを抽出する
4.2. 4.1.で抽出したユーザーをnewUsers変数に代入する
4.3. ステート値を更新するsetUsersにnewUsersを渡す -
usersが更新され、上から下に再度ステート値が渡される - 以下「上から下にステート値を渡す」を繰り返す
となります。
最後の親(App)が少し複雑に感じられるかもしれませんが、このようにして useState フックを用いてステート値を管理します。
ただ、ステート値をバケツリレーのようにして渡していくので、コンポーネントの階層が多くなると複雑になっていきます。
これらを解消するフックやライブラリについては、また記事にしたいと思います。
6.おわりに
ステートの値を
- 親コンポーネント → 子コンポーネント → 孫コンポーネント(上から下へステート値を渡す)
- 孫コンポーネント → 子コンポーネント → 親コンポーネント(下から上へステート値を渡す)
と渡していく流れを見ることで、React 重要なステート管理を掴むことができました。
ただ、一つ一つステートを受け渡していくのは、めんどくさいなと感じました。
今回は、親から孫まででしたが、これがもっと階層が増えると管理しきれなくなりますね。
そのために、便利なフックやライブラリができたんだなと…
今後ステート管理のフックやライブラリについてもキャッチアップして React マスターに近づきたいなと思います。
最後まで読んでいただき、ありがとうございました😊
併せて他の記事も読んでいただけると嬉しいです🙇♂️
7.参考
- React ハンズオンラーニング web アプリケーション開発のベストプラクティス
著: Alex Banks, Eve Porcello
訳: 宮崎 空 - 【JavaScript】オブジェクトリテラルの省略記法
