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】オブジェクトリテラルの省略記法