🐣

React 基本機能とHooksのおさらい

2023/07/25に公開
2

はじめに

業務上でReactをたまに書いていましたが、動的なコードを書くにあたって基礎知識を学び直しました。
この記事では、基本的なReactの機能の復習について例題とともにまとめます。
本記事での言語はTypeScript、実行環境はCodeSandboxを使用しています。

基本

ここでは基本的なReactの扱い方についてまとめます。
例題:関数コンポーネントを作成し、"Hello, React!"というテキストを表示させてください。
Reactでは以下のように関数コンポーネントHelloを作成し、return内で<Hello />の形で呼び出すことができます。

const Hello = () => {
  return <h2>Hello, React!</h2>;
};

export const App = () => {
  return (
    <div className="App">
      <Hello />
    </div>
  );
};
export default App;

例題:親コンポーネントから渡されたnameプロパティを受け取り、"Hello, {name}!"と表示する関数コンポーネントを作成してください。

受け取るプロパティの名前と型を指定することで、任意の値を受け取ることができます。

type Props = {
  name: string;
};

const Hello: FC<Props> = ({ name }) => {
  return <h2>Hello, {name}!</h2>;
};

export const App = () => {
  return (
    <div className="App">
      <Hello name="minami" />
    </div>
  );
};
export default App;

配列を用いたリストの作成

Reactでは、親コンポーネントからPropsを渡すことで自由に値を扱えるようになります。
これを用いてtodoリストを作成してみます。

例題:親コンポーネントから渡されたtodosプロパティ(配列)を受け取り、それを元に項目のリストを表示する関数コンポーネントを作成してください。

ボタンを押下した際の挙動

ボタンを押した時の挙動を関数として定義しておくことで、コードを簡潔に書くことが可能です。

例題:親コンポーネントから渡されたhandleClick関数を受け取り、ボタンをクリックするとその関数が実行される関数コンポーネントを作成してください。

export const App = () => {
  const handleClick = () => {
    console.log("クリックしました!");
  };
  return (
    <div className="App">
      <button onClick={() => handleClick()}>ボタン</button>
    </div>
  );
};

React Hooks

React Hooksのフック(hook) とは、2019年2月リリースのReact16.8から追加された、クラスを使用せずに Reactを書くことができる機能です。
https://udemy.benesse.co.jp/development/react-hooks.html

ここでは頻度高く利用する、useStateuseEffectuseReduceruseContextについてまとめます。
🌾カスタムフックについては別の記事でまとめる予定です。

useState

useStateは、stateと、stateを管理する関数の2つの要素を持つReactフックです。
例えば、初期値が0のstateを、useStateで作成するとき以下のように記述します。

const [state, setState] = useState<number>(0);

また、この値を更新したいときはsetStateを用いて以下のように記述します。

setState((prevState) => {
 // 更新する処理
return prevState + 1
};
// この処理の後、元のstateに+1された値がsetStateされることでstateの値になる

例題:useStateフックを使用して、カウントアップ機能を持つ関数コンポーネントを作成してください。ボタンをクリックするとカウントが1増えるように実装してください。

ボタンをクリックした際に、handleCountUpという関数を呼び出すようにします。

const CountUp = () => {
  const [count, setCount] = useState<number>(0);
  const handleCountUp = () => {
    setCount((prevCount) => prevCount + 1);
  };
 return (
  // 描画処理
  );
}

useEffect

useEffectは、任意のタイミングで処理を実行させることができるフックです。
普通、Reactではレンダー時に全ての関数が実行されますが、useEffectを使うことで、レンダー後に関数を実行させることができます。

useEffect(() => {
 // 実行させたい処理など
}, [])

例題:useEffectフックを使用して、関数コンポーネントがマウントされた際にブラウザのコンソールに"Component mounted"と表示されるようにしてください。

マウントは、最初にReactコンポーネントがDOMに出力されるときに行われる一連の処理。
レンダリングは、ReactコンポーネントをDOMに出力するために様々な情報が読み込まれること。
https://de-milestones.com/react-mount-rendering/

今回はコンポーネントがマウントされた際にのみ、コンソールに表示すれば良いので以下のようになります。

useContext

複数のコンポーネントが入れ子になっているとき、親コンポーネントが持つプロパティを最下層の子コンポーネントで扱いたい場合、毎度プロパティとして渡す必要が出てきます。

(例)

// 親コンポーネント
const Parent = (color) => {
  return <Child color={color}></Child>
}

const Child = (color) => {
  return <GrandChild color={color}></Child>
}
// 実際にcolorを描画するコンポーネント
const GrandChild = (color) => {
  return <p>{color}</p>
}

useContextを使うと、その必要性がなくなるためコードをより簡潔にわかりやすく書くことが可能になります。

const Parent = (color) => {
  return (
    // ColorProviderで囲われた箇所でContext内の値を扱えるようになる
    <ColorProvider>
      <Child />
    </ColorProvider>
  )
}

例題:useContextフックを使用して、テーマカラーを管理するコンテキストプロバイダを作成し、子コンポーネントでそのカラーを使用してスタイルを適用してください。

const ColorContext = createContext();

const initialColors = [
  {
    id: 1,
    content: "red"
  },
  {
    id: 2,
    content: "yellow"
  },
  {
    id: 3,
    content: "green"
  }
];

export const ColorProvider = ({ children }) => {
  const [colors, setColors] = useState(initialColors);

  return (
    <ColorContext.Provider value={[colors, setColors]}>
      {children}
    </ColorContext.Provider>
  );
};

export const useColorContext = () => useContext(ColorContext);
const Parent = (color) => {
  return (
    // ColorProviderで囲われた箇所でContext内の値を扱えるようになる
    <ColorProvider>
      <ColorList />
    </ColorProvider>
  )
}

useReducer

useReduceruseStateと似たような機能を持ちますが、複数のactionをも取り扱うことができます。
stateにはuseStateと同様にstateを、dispatchにはreducerを呼び出すためのactionが入ります。
基本形は以下になります。

const [state, dispatch] = useReducer(reducer,'初期値')

reducerには各actionで行いたい処理を記述しておきます。

例題:useReducerフックを使用して、ToDoアプリの状態管理を行う関数コンポーネントを作成してください。タスクの追加、削除、完了の機能を実装してください。

この例題では、それぞれの要素の役割は以下のようにします。

  • state...todolistに表示されているタスク
  • action...追加(ADD)、削除(DELETE)、完了(COMPLETE)を呼び出す関数を持たせるようにします。
  • reducer...actionで呼び出す追加、削除、完了の機能の処理について記述します。

以下ではタスクの追加(ADD)の処理について例として挙げています。

state

// todolistの型定義
type State = {
  id: number;
  text: string;
  checked: boolean;
};
// 初期値
const initialState: State[] = [
  {
    id: 0,
    text: "initial todo",
    checked: false
  }
];
// todoListの値を保持するuseReducer
const [state, dispatch] = useReducer(reducer, initialState);

action

ADDでは、新しい項目をTodoListに追加するようにしたいため、入力欄と追加するボタンを設置します。
ボタンをクリックした際にuseReducerを呼び出せるように、onClick内でdispatchを呼ぶようにします。

<input
  type="text"
  value={text}
  className="addInput"
  onChange={(e) => setText(e.target.value)}
/>
<button
  className="addBtn"
  onClick={() => dispatch({ type: "ADD", text: text })}
>Add todo</button>

reducer

ボタンをクリックした際に呼び出す内容をreducerに書きます。
ADDを行う際に必要な要素は予めAddActionとして定義しておくとわかりやすいです。

// 必要な要素をAddAction内に書いておく
type AddAction = {
  type: "ADD";
  text: string;
};

const reducer = (
  state: State[],
  action: AddAction | RemoveAction | CheckedAction | RemoveCheckedAction
): State[] => {
  // 渡した要素はactionで呼び出すことが可能
  switch (action.type) {
    case "ADD":
      // 現在のtodolist(state)に今回入力したtextを追加
      return [
        ...state,
        { id: state.slice(-1)[0].id + 1, text: action.text, checked: false }
      ];
    case "REMOVE":
       ~~~

REMOVE、COMPLETEの処理も書き加えたものが以下になります。
useStateと比較して、useReducerでは同じstateに対しての複数の処理をわかりやすく簡潔に定義することができます。

最後に

  • 基本的なReactのコードの機能を知ることで、コードが書きやすく読みやすくなりました。
  • カスタムフックやreact-hook-formについてまとめていきます。

Discussion

まっきんとっしゅまっきんとっしゅ

1点間違えているところがあったのでコメントします。

setState((prevState) => {
 // 更新する処理
prevState + 1
};

これだと更新されないので

setState((prevState) => {
 // 更新する処理
return prevState + 1
};

setState((prevState) =>  prevState + 1)

が正しいです。