Reactを実装しはじめて、いよいよステートが色々なところに散らばってわけ分からなくなってきたので、Reduxを導入しようとしたら上手くいきませんでした。
そのため一旦Reduxは諦めてhooks(useReducer、useContext)を使うことにしたらやりたいことはできたし、とても簡単でしたので備忘録的に残しておきます。
useContext導入以前
export const Sample = () => {
const [id, setId] = useState('');
const eventHandler: (id: string) => void = id => {
setState(id);
};
return (
<Container>
<LeftContainer eventHandler={eventHandler} />
<RightContainer id={id} />
</Container>
);
}
このような形でstateの中身やstateを変える関数を子要素に渡して、その子要素がさらに孫要素に渡して、、、(以下無限)みたいなことをしてました。いわゆるprop drillingっていうやつですね。
useContext導入後
useReducer
も一緒に使って、親要素に全て集約し、その子孫のコンポーネントであればどこでも呼び出せるようにします。
export type StateType = {
id: string;
};
export type ActionType = {
type: string;
id: string;
};
export const myReducer = (state: StateType, action: ActionType) => {
switch (action.type) {
case 'setId':
return { ...state, id: action.id };
default:
return state;
}
};
親要素:
import React, { useState, createContext, useReducer } from 'react';
import { Child } from './Child';
import { myReducer, StateType, ActionType } from './reducer';
// contextの型を宣言
// stateはParentコンポーネントのステート
// dispatchはステートを変える関数(正確にはどの処理を行うか振り分けるためのもの)
type ContextType = {
state: StateType;
dispatch: React.Dispatch<ActionType>;
};
// ステートの初期値を宣言
const initialState = { id: '' };
// コンテキストを作成する
export const ParentContext = createContext({} as ContextType);
const Parent = () => {
// useReducerを使ってstateとdispatchを宣言。reducerは上記で作成済みのものを使う
const [state, dispatch] = useReducer(myReducer, initialState);
return (
<ParentContext.Provider value={{ state, dispatch }}>
<Child />
</ParentContext.Provider>
);
};
export default Parent;
retunで返しているJSX要素を見ていただけると分かりますが、Childコンポーネントにはpropsとして何も渡していません。
import React, { useContext } from 'react'
import { ParentContext } from '../Parent';
const Child = () => {
// ここではstateとdispatch両方受け取っているが、必要な方だけ受け取ることも可能
const { state, dispatch } = useContext(ParentContext);
return(
<div
id="newId"
onClick={(e) =>
dispatch({
type: 'setId', // reducerで指定したtypeを使う
id: e.target.id,
})
}
>
{state}
</div>
);
}
親要素(先祖要素)から受け取ったcontextを使うことで、propsの受け渡しをせずに、子孫要素で作成したcontextを使うことができるようになりました!
ちなみにuseContextを使っても、propsは普通に子要素に受けわたすことができるので使い分けも可能です。
将来的に複雑なことをするかもと思い、Reduxの導入をチャレンジしたのですが、結果的には今はやりたいことがこれだけで十分でした。
学習コストがほぼ0なのと、Reduxのステートの管理とは干渉しないっぽいのでとりあえず導入してみるのがおすすめです。
ちなみに
Reduxの導入を諦めた理由は、バックエンドにGraphQLを使っていて、そのクライアントにapollo-clientを使用しているのですが、それがどうも干渉してる感じでした。(ここがわからず2日くらい上手くいかないと悩んでた)
apollo-clientのバージョン1ではReduxと併用できて、バージョン2でできなくなったものの、apollo-cache-reduxというライブラリを使えば可能ということだったのですが、apollo-cache-reduxは公式でもうメンテナンスしないみたいになってたので、結局Reduxは使えず。。。
Apollo Client自体でステートの管理もできそうな感じしてるのですが、とりあえず今回はuseContext使おうと思いました。
参考資料
Reduxから Context API with Hooks へ
【react hooks】useContextを使ってみた
React createContext issue in Typescript?
reduxでgraphqlを使う
apollo-cache-redux(github)