Open34

📗 React実践の教科書

ピン留めされたアイテム
きたじーきたじー

はじめに
React学習のためのアウトプットとして書き綴る試みです。

自分の理解のために出力しているので、初学者にとってわかりやすくないことはご了承ください。

きたじーきたじー

モダンJavaScriptの機能

const, letの変数宣言

ほとんどconst
処理の中で値を上書きする必要があればlet

テンプレート文字列

文字列の中で変数を使うための記法
バッククォートで文字列を囲む

const message = `私の名前は${name}です`;

アロー関数

=>が矢印に見えるのでアロー関数

const func2 = (value) => {
    return value;
}

// 引数が1つの場合は()の省略可能
const func2 = value => {
    return value;
}

// 処理を1行で返す場合は{}とreturnの省略可能
const func3 = (num1, num2) => num1 + num2;

// 処理が複数行の場合は()でまとめ可能
const func4 = (val1, val2) => (
    {
        name: val1,
        age: val2,
    }
)

分割代入

オブジェクトに対して分割代入

// 分割代入なし
const myProfile = {
    name: "田中",
    age: 30,
};

const message = `私の名前は${myProfile.name}です。年齢は${myProfile.age}歳です。`;

// 分割代入あり
const myProfile = {
    name: "田中",
    age: 30,
};

const { name, age } = myProfile;

const message = `私の名前は${name}です。年齢は${age}歳です。`;

配列に対して分割代入

const myProfile = ["田中", 30];

const [ name, age ] = myProfile;

const message = `私の名前は${name}です。年齢は${age}です。`

デフォルト値

変数に値がない場合に備えてデフォルト値(初期値)を設定できる

// デフォルト値なし
const sayHello = (name) => console.log(`こんにちは${name}さん`);

// デフォルト値あり
const sayHello = (name = "ゲスト") => console.log(`こんにちは${name}さん`);

スプレッド構文 ...

要素の展開
配列の内部の値を順番に展開してくれるので簡潔にコードが書ける

const array1 = [1, 2];

const sumFunc = (num1, num2) => console.log(num1 + num2);

// スプレッド構文なし
sumFunc(array1[0], array1[1]);

// スプレッド構文あり
sumFunc(...array1);

要素をまとめる

const array2 = [1, 2, 3, 4, 5];

const [num1, num2, ...array3] = array2;

console.log(num1);     // 1
console.log(array3);   // [3, 4, 5]

要素のコピー、結合

const array4 = [1, 2];
const array5 = [3, 4];

// コピー
const array6 = [...array4];

// 結合
const array7 = [...array4, ...array5];

console.log(array6);  // [1, 2]
console.log(array7);  // [1, 2, 3, 4]

オブジェクトの省略記法

「プロパティ名」と「変数名」が同一ならば、:(コロン)以降を省略できる

const name = "田中";
const age = 30;

// 省略なし
const user = {
    name: name,
    age: age,
};

// 省略あり
const user = {
    name,
    age,
};

map関数

配列を処理して結果を配列で受け取れる

// for文の場合
const nameArray = ["田中", "山田", "西村"];

for (let index = 0; index < nameArray.length; index++) {
    console.log(nameArray[index]);
}

// map関数の場合
const nameArray = ["田中", "山田", "西村"];

nameArray.map((name) => console.log(name));

// どちらも同じ結果がコンソールに表示される

filter関数

map関数とほとんど同じ使い方
returnの後に条件式、一致した値だけ配列から取り出す

const numArray = [1, 2, 3, 4, 5];

const newNumArray = numArray.filter((num) => {
    return num % 2 === 1;
});

console.log(newNumArray);  // [1, 3, 5]
きたじーきたじー

JSXのルール

return以降が複数行になる場合は()で囲む
return以降は1つのタグ<div> <Fragment> <>で囲われている必要がある

const App = () => {
    return (
        <>
            <h1>こんにちは!</h1>
            <p>お元気ですか?</p>
        </>
    );
};
きたじーきたじー

拡張子

Reactは.jsでも動くが、
コンポーネントであることをわかりやすくするために、
コンポーネントは.jsxが良い

きたじーきたじー

イベントの扱い方

キャメルケース記法
HTMLのようなタグの中(return以降など)では{}で囲んでJSを扱う

export const App = () => {
    const onClickButton = () => {
        alert();
    };

    return (
        <>
            <h1>こんにちは</h1>
            <p>お元気ですか?</p>
            <button onClick={onClickButton}>ボタン</button>
        </>
    );
};
きたじーきたじー

スタイルの扱い方

キャメルケース
CSSはJSのオブジェクト{}として記述
そのため、値は文字列
さらにJSのコードを扱うために{}でくくるので{{}}となる

export const App = () => {
    const contentStyle = {
        color: "blue",
        fontSize: "20px"
    };

    return (
        <>
            <h1 style={{ color: "red" }}>こんにちは</h1>
            <p style={contentStyle}>お元気ですか?</p>
        </>
    );
};
きたじーきたじー

Props

コンポーネントのスタイルや内容を変化させる
親コンポーネントから子コンポーネントへpropsを渡し、
子コンポーネントで処理した結果を親コンポーネントへ返す

// 親コンポーネント
    return (
        <>
            <ColoredMessage color="blue" message="お元気ですか?" />
        </>
// 子コンポーネント
export const ColoredMessage = (props) => {
    const contentStyle = {
        color: props.color,
        fontSize: "20px"
    };

    return <p style={contentStyle}>{props.message}</p>;
};
きたじーきたじー

children

コンポーネントで囲ったところがchildrenとしてpropsになる
コードがよりわかりやすくなる

// childrenなし
<ColoredMessage />

// childrenとしてtanakaを設定
<ColoredMessage>tanaka</ColoredMessage>

テキスト部分をchildrenで渡す

    return (
        <>
            <ColoredMessage color="blue">お元気ですか?</ColoredMessage>
            <ColoredMessage color="pink">元気です!</ColoredMessage>
        </>
        );
    };
export const ColoredMessage = (props) => {
    const contenstStyle = {
        color: props.color,
        fontSize: "20px"
    };

    return <p style={contentStyle}>{props.children}</p>;
};

文字以外にも要素をタグで囲むことも可能

<SomeComponent>
    <div>
        <span>tanaka</span>
    </div>
</SomeComponent>

// この場合のchildrenは<SomeComponent>で囲まれた部分全て
きたじーきたじー

Propsを扱うテクニック

propsを使うときにprops.が不要になるので簡潔にかける

テクニックなし

export const ColoredMessage = (props) => {
    const contentStyle = {
        color: props.color,
        fontSize: "20px"
    };

    return <p style={contentStyle}>{props.children}</p>;
};

分割代入

export const ColoredMessage = (props) => {
    const { color, children } = props;

    const contentStyle = {
        color: color,  // さらに省略記法を使うとcolor,でよい
        fontSize: "20px"
    };

    return <p style={contentStyle}>{children}</p>;
};

引数の段階で展開パターン

export const ColoredMessage = ({ color, children }) => {
    const contentStyle = {
        color,
        fontSize: "20px"
    };

    return <p style={contentStyle}>{children}</p>;
};

なお共同開発では
あえてprops.を残してpropsから渡ってきた値であることをわかりやすくするやり方もある模様
(Propsをdestructureするかどうか問題)

きたじーきたじー

State

コンポーネントの状態を表す
React HooksのuseState関数が主流

useStateの返却値は配列
1つ目にState変数、2つ目にそのStateを更新するための関数

const [num, setNum] = useState();

初期値を0に設定

const [num, setNum] = useState(0);

カウントアップ機能の実装

import { useState } from "react";

export const App = () => {
    // Stateの定義
    const [num, setNum] = useState(0);

    // ボタンを押した時にStateをカウントアップ
    const onClickButton = () => {
        setNum(num + 1);
    };

    return (
        <>
            <button onClick={onClickButton}>ボタン</button>
            <p>{num}</p>
        </>
    );
};

厳密にはsetNum(num + 1)の書き方は正しくない
正解はこちら

setNum((prev) => prev + 1);

これは非同期では複数回の更新がうまくいかないから

きたじーきたじー

再レンダリング

コンポーネントが再レンダリングされるから画面が変わる
再レンダリング「Stateの変更を検知してコンポーネントを再処理すること」

「State更新時にコンポーネントが再レンダリングされ、関数コンポーネントが再度頭から実行される」

きたじーきたじー

副作用とuseEffect

useEffect
コンポーネントの副作用を制御する
「再レンダリングするたびにこの処理を走らせるのは無駄だからこの値が変わった時だけ走らせたい」

「ある値が変わったときに限り、ある処理を実行する」

useEffect( 実行する関数, [依存する値]);
export const App = () => {

    useEffect(() => {
        alert();
    }, [num]);

    return (
        {/* 省略 */}
    );
};

第1引数 アロー関数
第2引数 配列

useEffectの実行タイミング
・依存配列に指定してる値が変わった時
・コンポーネントマウント時(一番最初)

初回のみ実行する処理の場合、第2引数に[]をとる

きたじーきたじー

CSSの当て方

チームやプロジェクトによって様々

・インラインスタイル
・CSS Modules
・Styled JSX
・styled components (CSS-in-JS)
・Emotion (CSS-in-JS)
・taiwind CSS

・インラインスタイル

// 直接記述
return (
    <div style={{ width: "100%", padding: "16px" }}>
        <p style={{ color: "blue" }}>Hello</p>
    </div>
)

// 事前定義してから指定
const containerStyle = {
    width: "100%",
    padding: "16px",
};
const textStyle = {
    color: "blue",
};

return (
    <div style={containerStyle}>
        <p style={textStyle}>Hello</p>
    </div>
)

・CSS Modules
CSSファイルをimportして当てていく

・Styled JSX
あまり使われない
Next.jsには標準組み込み

・Styled components
スタイルを適用したコンポーネントを定義
HTMLタグを拡張するイメージ

// divタグ
const StyledDiv = styled.div`
    padding: "8px";
`;

// 使用例
<StyledDiv>
    <p>こんにちは</p>
</StyledDiv>

・Emotion
いろんな書き方ができる
故に共同作業の時はルールが必要

きたじーきたじー

再レンダリングが起きるパターン

  1. Stateが更新されたコンポーネント
  2. Propsが変更されたコンポーネント
  3. 再レンダリングされたコンポーネント配下のコンポーネント全て

3の場合、再レンダリングの必要がないコンポーネントまで再レンダリングされるので無駄がある
パフォーマンスの低下につながる

再レンダリングを制御するためにメモ化する

メモ化とは

前回の処理結果を保持しておくことで処理を高速にする技術
コンポーネントをメモ化すると、
親コンポーネントが再レンダリングしても
子コンポーネントの再レンダリングを防ぐことができる

きたじーきたじー

コンポーネントのメモ化

const Component = memo(() => {} );

memo()でProps以下を囲う
これだけ
Propsに変更がない限り再レンダリングされない

きたじーきたじー

関数のメモ化

再レンダリングなどでコードが実行されるたびに新しい関数が再生成されるのを防ぐ

useCallbackを使う

const onClickButton = useCallback(() => {
    alert('ボタンが押されたよ!');
}, []);

useCallback()でprops以下を囲む
useCallbackは第1引数に関数、第2引数に依存配列

この場合、依存配列が空なので、最初に作成された関数が使いまわされる
結果、関数の不要な再生成を防ぐことができる

きたじーきたじー

ContextでのState管理

propsのバケツリレーを避けるためにContextを使う

使い方

① React.createContextでContextの器を作成
② 作成したContextのProviderでグローバルStateを扱いたいコンポーネントを囲む
③ Stateを参照したいコンポーネントでReact.useContextを使う

きたじーきたじー

① React.createContextでContextの器を作成

import { crateContext } from "react";

export const AdiminFlagContext = createContext({});

デフォルト値にからのオブジェクトを設定{}

② 作成したContextのProviderでグローバルStateを扱いたいコンポーネントを囲む

 return (
    <AdminFlagContext.Provider value={sampleObj}>
        {children}
    </AdminFlagContext.Provider>
    );

参照したいコンポーネントを囲む

// index.js
ReactDOM.render(
    <AdminFlagProvider>
        <App />
    </AdminFlagProvider>,
    ...
);

③ Stateを参照したいコンポーネントでReact.useContextを使う

// useContextの引数に参照するContexを指定
const contextValue = useContext(AdminFlagContext);

参照は、使いたいところで引数に使いたいContextを指定するだけ

きたじーきたじー

その他のグローバルStateを扱う方法

Redux
Recoil
Appolo Client

きたじーきたじー

TypeScript

JS + Type(型)
複雑なフロントエンド開発におけるDX向上のため

変数の型の指定の仕方

変数のあとに:(コロン)、続けて型の種類を定義

// string
let str: string = "A";

// number
let num: number = 0;

// boolean
let bool: boolean = true;

// Array
const arr1: Array<number> = [0, 1, 2];
let arr2: number[] = [0, 1, 2];
// null
let null1: null = null;

// undefined
let undefined1: undefined = undefined;

// any
let any1: any;
any1 = false;
any1 = 10;
any = undefined;
きたじーきたじー

関数の型

void型は関数が何も返却しないことを意味する
TypeScriptは型推論があるので関数内で何もreturnしていなければ自動的にvoid型になる
voidを明示しておくと関数内でreturn文を記述するとエラーになる

// (引数: 引数の型名): 返却値の型名 => {}
const funcA = (num: number): void => {
    console.log(num);
};
funcA(10); // OK
funcA("10"); //NG
funcA(); // NG
きたじーきたじー

オブジェクトの型

// : { : 型名, : 型名 ... }
const obj: { str: string, num: number } = {
    str: "A",
    num: 10,
};
obj.str = "B"; // OK
obj.num = 20; // OK
きたじーきたじー

Generics(ジェネリクス)

type CustomType<T> = {
    val: T;
}

// 使用
const strObj = CustomType<string> = { val: "A" };
const numObj = CustomType<number> = { val: 10 };

定義した型を動的に使いまわせるのがメリット

ライブラリやuseStateで使いがち

const [ str, setStr ] = useState<string>("");
きたじーきたじー

コード比較

TSなし
App.js

import { useEffect, useState } from "react";
import { ListItem } from "./components/ListItem";
import axios from "axios";

export const App = () => {
    // 取得したユーザー情報
    const [users, setUsers] = useState([]);

    // 画面表示時にユーザー情報
    useEffect(() = > {
        axios.get("https://example.com/users").then((res) => {
            setUsers(res.data);
        })
    }, []);

    return (
        <div>
            {users.map(user => (
                <ListItem id={user.id} name={user.name} age={user.age} />
            ))}
        </div>
    );
};

ListItem.jsx

export const ListItem = props => {
    const { id, name, age } = props;
    return (
        <p>
            {id}:{name}({age})
        </p>
    );
};

TSあり
App.tsx

import { useEffect, useState } from "react";
import { ListItem } from "./components/ListItem";
import axios from "axios";

// ユーザー情報の型定義
type User = {
    id: number;
    name: string;
    age: number;
    personalColor: string;
};

export const App = () => {
    // 取得したユーザー情報
    const [ users, setUsers ] = useState<User[]>([]);

    // 画面表示時にユーザー情報取得
    useEffect(() => {
        axios.get<User[]>("https://example.com/users").then((res) => {
            setUsers(res.data);
        })
    }, []);

    // ...省略
};

ListItem.tsx

// Propsの型定義
type User = {
    id: number;
    name: string;
    age: number;
};

// propsに型を指定
export const ListItem = ( props: User) => {
    const { id, name, age } = props;
    return (
        <p>
            {id}:{name}({age})
        </p>
    );
};
きたじーきたじー

型の一元管理

同じ型の定義を複数箇所で使う場合、毎回定義するのは手間
そこで1つのファイルに一元管理する
ex) /src/types/user.ts

他のファイルにimportするときは
import type { ~ } fromのようにtypeをつける
型定義のimportを明示化するため

きたじーきたじー

設定される時もあればされない時もある型の場合

省略可能を明示する?をつける

export type User = {
    id: number;
    name: string;
    age: number;
    personalColor?: string; // string || undefined
};
きたじーきたじー

デフォルト値を分割代入で定義

export const ListItem = (props: User) => {
    const { id, name, age, personalColor = "gray" } = props;
    return (
        ...
きたじーきたじー

オプショナルチェイニング

?
オプジェクト内のプロパティの存在に関係なく処理したい場合に使う
プロパティがnullだった場合、その後のメソッドがエラーになるのでその対策

export type User = {
    id: number;
    ...
    hobbies?: string[];
};
    return (
        <p style={{ color: personalColor }}>
            {id}:{name}({age}) {hobbies?.join("/")} // プロパティが存在しない場合はundefinedを返す
        </p>
    );
きたじーきたじー

カスタムフック

useStateやuseContextのように便利機能や特定のロジックを実行するHooksを自作できる
use~と命名が推奨されている

きたじーきたじー

カスタムフックなし
App.js

import { useState } from "react";
import axios from "axios";

export const App = () => {
    const [userList, setUserList] = useState([]);
    const [isLoading, setIsLoading] = useState(false);
    const [isError, setIsError] = useState(false);

    // ユーザー取得ボタン押下アクション
    const onClickFetchUser = () => {
        // ボタン押下時にローディングフラグon、エラーフラグoff
        setIsLoading(true);
        setIsError(false);

        // APIの実行
        axios
            .get("https://example.com/users")
            .then(result => {
                // 苗字と名前を結合するように変換
                const users = result.data.map(user => ({
                    id: user.id,
                    name: `${user.lastname} ${user.firstname}`,
                    age: user.age
                }));
                // ユーザー一覧Stateを更新
                setUserList(users);
            })
            // エラーの場合はエラーフラグをon
            .catch(() => setIsError(true))
            // 処理完了後はローディングフラグをoff
            .finally(() => setIsLoading(false));
        };

        return (
            <div>
                <button onClick={onClickFetchUser}>ユーザ取得</button>
                {/* エラーの場合はエラーメッセージ表示 */}
                {isError && <p style={{ color: "red" }}>エラーが発生しました</p>}
                {*/ ローディング中は表示を切り替える */}
                {isLoading ? (
                    <p>データを取得中です</p>
                ) : (
                    userList.map(user => (
                        <p key={user.id}>{`${user.id}:${user.name}(${user.age}`}</p>
                    ))
                )}
            </div>
        );
};
きたじーきたじー

カスタムフックあり
userFetchUser.js

import { useState } from "react";
import axios from "axios";

// ユーザー一覧を取得するカスタムフック
export const useFetchUsers = () => {}
    const [userList, setUserList] = useState([]);
    const [isLoading, setIsLoading] = useState(false);
    const [isError, setIsError] = useState(false);

    const onClickFetchUser = () => {
        // ボタン押下時にローディングフラグon、エラーフラグoff
        setIsLoading(true);
        setIsError(false);

        // APIの実行
        axios
            .get("https://example.com/users")
            .then(result => {
                // 苗字と名前を結合するように変換
                const users = result.data.map(user => ({
                    id: user.id,
                    name: `${user.lastname} ${user.firstname}`,
                    age: user.age
                }));
                // ユーザー一覧Stateを更新
                setUserList(users);
            })
            // エラーの場合はエラーフラグをon
            .catch(() => setIsError(true))
            // 処理完了後はローディングフラグをoff
            .finally(() => setIsLoading(false));
        };

    // まとめて返却するためオブジェクトに設定
    return ( userList, isLoading, isError, onClickFetchUser };
}

App.jsx

import { useState } from "react";
import { useFetchUsers } from "./hooks/useFetchUsers";

export const App = () => {
    // カスタムフックの使用
    // 関数を実行し返却値を分割代入で受け取る
    const { useList, isLoading, isError, onClickFetchUser } = useFetchUsers();

    return (
        <div>
            <button onClick={onClickFetchUser}>ユーザー取得</button>
            {*/ エラーの場合はエラーメッセージを表示 */}
            {isError && <p style={{ color: "red" }}>エラーが発生しました</p>}
            {*/ ローディング中は表示を切り替える */}
            {isLoading ? (
                <p>データ取得中です</p>
            ) : (
                userList.map(user => (
                    <p key={user.id}>{`${user.id}:${user.name}(${user.age}歳)`}</p>
                ))
            )}
        </div>
    );
};
きたじーきたじー

カスタムフック化することで
ロジックと見た目を分離できる
ロジックの再利用が可能になる