9
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【React】React 初心者でも「ステート管理」を完全に理解できる(ライブラリは使わないよ)

Last updated at Posted at 2022-10-25

1.はじめに

私は
「React ってファイルを行ったり来たりして、わけわからん!」
「 React めちゃむずい」
って思っていました。

この React の行ったり来たりの流れを整理するために本記事を書きました。

同じように思われている方のお役に立てれば幸いです。

本記事は、「ステート管理」について見ていきます。

ステート の 基礎的な内容となっています。

※ ステート管理には、useState 以外の フック や ライブラリ 等は使用しておりません。

簡単な内容でどのように state の値が渡っていくのかをみていきたいと思います。

もし間違って認識しているところ等ありましたらご指摘いただけますと幸いです。

2.目次

1.はじめに
2.目次
3.この記事でわかること
4.環境
5.ステート管理
 5.1.上から下にステート値を渡す
 5.2.下から上にステート値を渡す
6.おわりに
7.参考

3.この記事でわかること

ステートの値が、

  • 親コンポーネント → 子コンポーネント → 孫コンポーネント(上から下へステート値を渡す)
  • 孫コンポーネント → 子コンポーネント → 親コンポーネント(下から上へステート値を渡す)

という流れを見ていきます。

ルートコンポーネントでステート値を受け取って親・子・孫コンポーネントに値を渡して、ユーザー情報を表示させ、
孫コンポーネントの削除ボタンをクリックして、子・親コンポーネントへ変更したステート値を渡す流れとなります。

↓ こんな感じの簡単なアプリです。

ステート管理.gif

最終的なソースコードは下記です。

型定義の src/user.tssrc/userList.ts、ユーザー情報の src/user-data.json については、本記事では振れません。
あと、src/index.tsx はコンポーネントを表示するためのファイルなので、こちらも説明は省略します。
上記のソースコードについては、こんな風に定義してるんだな程度で見ていただけますと幸いです。
(下記をコピーすると動くようにはなっています。)

src/user.ts(型定義)
export type User = {
  id: string;
  name: string;
  age: number;
  onRemoveUser?: (id: string) => void;
  onRemove?: (id: string) => void;
};
src/userList.ts(型定義)
import type { User } from "./user";

export type UserList = {
  users: User[];
  onRemoveUser?: (id: string) => void;
};
src/user-data.json(ユーザー情報)
[
  {
    "id": "dahis1",
    "name": "daishi",
    "age": 10
  },
  {
    "id": "manju2",
    "name": "manju",
    "age": 20
  },
  {
    "id": "daishiman3",
    "name": "daishiman",
    "age": 30
  }
]
src/index.tsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);
src/App.tsx(親コンポーネント)
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>
  );
}
src/UserList.tsx(子コンポーネント)
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>
  );
};
src/User.tsx(孫コンポーネント)
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. コンポーネント → コンポーネント → コンポーネント
    (上から下へステート値を渡す)
  2. コンポーネント → コンポーネント → コンポーネント
    (下から上へステート値を渡す)

という流れで見ていきます。

1 . コンポーネント → コンポーネント → コンポーネント(上から下へステート値を渡す)

では、src/user-data.json のユーザー情報を、親(App)コンポーネントから孫(User)コンポーネントまでステート値を渡していき、ユーザー情報を表示します。

2 . コンポーネント → コンポーネント → コンポーネント(下から上へステート値を渡す)

では、孫(User)コンポーネントの削除ボタンをクリックすることで、表示するステート値のユーザー情報を減らしていきます。

これらの流れを見ながら ステートの値の行ったり来たり を確認したいと思います。

5.1.上から下にステート値を渡す

では、まずはステートの値を上(親)から下(孫)に渡す流れを見ていきます。

今回使用するソースコードは下記のような構成となっています。

  • 親コンポーネント: src/App.tsx
  • 子コンポーネント: src/UserList.tsx
  • 孫コンポーネント: src/User.tsx

5.1.1.親コンポーネントから子コンポーネントにステート値を渡す

まず、親(App)コンポーネントを見ていきます。

src/App.tsx(親コンポーネント)
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 コンポーネントの構成は

  1. ステート管理を useState で行う
  2. ステート管理している値(users)を UserList コンポーネントに渡す
  3. UserLIst を表示する

となっています。

では細かく見ていきましょう。

1. ステート管理を useState で行う

ステート値を useState フックを用いて管理します。

( ↓ useState の参考記事)

下記の部分になります。

const [users] = useState<User[]>(userData);

まだ値を更新しないので、更新用の関数は記述していません。

src/user-data.jsonuserData を初期値として 変数 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 コンポーネントを使用することができます。

これが、コンポーネントが便利な所以ですね。

話を戻して、ソースコードを見てきましょう。

src/UserList.tsx(子コンポーネント)
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 コンポーネントのは下記の構成でできています。

  1. UserList はアロー関数で記述(アロー関数の参考記事
  2. 引数の users = []users: users = [] を省略
  3. users.length が false (0)のときの返り値
  4. 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.nameuser.age を一つにまとめて 孫(User)コンポーネントに渡しています。

5.1.3.孫コンポーネントでステート値を受け取る

最後に、孫(User)コンポーネントを見ていきます。

親(App)から子(UserList)そして孫(User)にステート値が渡ってきて、
最後に渡ってきた値をどのように表示するかのコンポーネントとなっています。

src/User.tsx(孫コンポーネント)
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 コンポーネントの構成は

  1. UserList はアロー関数で記述(アロー関数の参考記事
  2. 引数の nameagename: nameage: age を省略
  3. 名前と年齢を表示

となっています。

1. UserList はアロー関数で記述

先ほどと同じ内容なので詳細は省略しますが、

const User: FC<UserType> = () => {};

ということですね。

2. 引数の nameagename: nameage: age を省略

こちらも同じなので、説明は省略しますが、

  • namename: name の省略系
  • yerayera: age の省略系

3. 名前と年齢を表示

親コンポーネントから順番に渡ってきたステート値は、ここでやっと表示する形になります。

  return (
    <section>
      <p>
        {name}(年齢:{age}歳)
      </p>
    </section>
  );

長い道のりでしたが、
親(App)コンポーネントから孫(User)コンポーネントでまでステートの値を渡し、表示する流れをつかめたかと思います。

次は、孫(User)コンポーネントに削除バタンを定義して、削除するステート値を親(App)コンポーネントまで渡す流れを見ていきます。

5.2.下から上にステート値を渡す

今回の実装する概要は、下記の通りです。

  • 孫コンポーネントに削除ボタンを定義
  • 削除したステート値を親コンポーネントまで伝える
  • 親コンポーネントでステートを更新
  • 以降は、上からステート値を渡すと同じ流れ

では早速、孫コンポーネントに追記してきます。

5.2.1.孫コンポーネントに削除機能を追加

孫(User)コンポーネントに削除機能を追加します。

ここでやることは、
クリックしてどのユーザーのイベントが発生したか(削除するか)を 子(UserList)コンポーネント に伝えることです。

src/User.tsx(孫コンポーネント)
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>
  );
};

流れを見てみましょう。

  1. 削除ボタンをクリックイベントが発生する
  2. 削除するための onRemove 関数を呼び出す
  3. onRemove 関数は、 User コンポーネントに新たに追加されたプロパティ
  4. onRemove 関数は、引数に id 値を受け取る(どのユーザーかを認識する)
  5. onRemove 関数は、id を受け取り、id を返す
  6. 孫(User)コンポーネントを使う子(UserList)コンポーネントに id を返す

このようにして、孫(User)コンポーネントから子(UserList)コンポーネントにステート値を渡します。

5.2.2.子コンポーネントに削除機能を追加

次に、孫(User)コンポーネントから伝わった id を親(App)コンポーネントに伝えるロジックを追加します。

孫(User)コンポーネントでやった id の受け渡しをここでもやります。

src/UserList.tsx(子コンポーネント)
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>
  );
};

では、流れを見ていきましょう。

  1. 孫(User)コンポーネントから onRemove 属性の値として id を受け取る
  2. 1.で受け取った id は、onRemoveUser 関数に代入される
  3. onRemoveUser 関数は、 UserList コンポーネントに新たに追加されたプロパティ
  4. onRemoveUser 関数に代入された id は、そのまま id を返す
  5. 子(UserList)コンポーネントを使う親(App)コンポーネントに id を返す

流れは、孫(User)コンポーネントでやった内容とほぼ同じですね。

5.2.3.親コンポーネントに削除機能を追加

最後に、親(App)コンポーネントに削除機能を追加します。

ここでは、子(UserList)から親(App)に渡ってきたステート値を更新するためのロジックの追加が必要になります。

src/App.tsx(親コンポーネント)
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>
  );
}

では、最後の流れを見ていきましょう。

  1. useState フックに users を更新する setUser 変数を追加する
  2. 子(UserList)コンポーネントから onRemoveUser 属性の値として id を受け取る
  3. 1.で受け取った id は、removeUser 関数に代入される
  4. removeUser 関数は、
    4.1. users 配列から filter 関数を用いて user.idid と一致しないユーザーを抽出する
    4.2. 4.1.で抽出したユーザーを newUsers 変数に代入する
    4.3. ステート値を更新する setUsersnewUsers を渡す
  5. users が更新され、上から下に再度ステート値が渡される
  6. 以下「上から下にステート値を渡す」を繰り返す

となります。

最後の親(App)が少し複雑に感じられるかもしれませんが、このようにして useState フックを用いてステート値を管理します。

ただ、ステート値をバケツリレーのようにして渡していくので、コンポーネントの階層が多くなると複雑になっていきます。

これらを解消するフックやライブラリについては、また記事にしたいと思います。

6.おわりに

ステートの値を

  • 親コンポーネント → 子コンポーネント → 孫コンポーネント(上から下へステート値を渡す)
  • 孫コンポーネント → 子コンポーネント → 親コンポーネント(下から上へステート値を渡す)

と渡していく流れを見ることで、React 重要なステート管理を掴むことができました。

ただ、一つ一つステートを受け渡していくのは、めんどくさいなと感じました。

今回は、親から孫まででしたが、これがもっと階層が増えると管理しきれなくなりますね。

そのために、便利なフックやライブラリができたんだなと…

今後ステート管理のフックやライブラリについてもキャッチアップして React マスターに近づきたいなと思います。

最後まで読んでいただき、ありがとうございました😊

併せて他の記事も読んでいただけると嬉しいです🙇‍♂️

7.参考

9
16
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
9
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?