Open hysryt opened 3 years ago
アクション:ストアのデータを書き換える何かしらのイベント リデューサー:現在のストアのデータとアクションを受け取り、アクションの内容に応じてストアのデータを更新して返す関数 ストア:ステートツリーを保持する
アクションのログを取るだけで全ての変化を記録できる。
npm install redux react-redux
TypeScriptの場合は型も一緒にインストール
npm install redux react-redux @types/react-redux
https://redux-toolkit.js.org/ Reduxを使うときはRedux Toolkitも一緒に使うことが推奨されている。(もちろん ReduxToolkit無しでも書ける) Reduxを使ったアプリを簡単に、ベストプラクティスに則って記述できる。
npm install @reduxjs/toolkit
const action = {
type: 'increment',
};
アクションはプレーンなオブジェクト。
type
フィールドは必須、domain/eventName
の形式で書くことが多い。
負荷情報を持たせることも可能で、一般的に payload
フィールドとして追加する。
const reducer = (state, action) => {
...
return newState;
}
リデューサーはステートとアクションを受け取り、新しいステートを返す関数。 アクションの内容に応じてステートを更新する。 引数で受け取ったステートは書き換えられないため、ステートをコピーし、そちらを書き換える。
リデューサーの中で非同期通信をしたりランダム値を計算してはならない。 引数が同じ場合は同じ結果を返さなければならない。 非同期通信の結果やランダム値が必要な場合はアクションにその値を持たせ、リデューサーでは更新だけを行う。
const store = createStore(reducer);
ストアはリデューサーを1つ持つ。 規模が大きいアプリの場合は、リデューサーは子リデューサーを持つ。 ストアが直接保持するリデューサーのことをルートリデューサーと呼ぶ。
store.dispatch(action);
ストアは dispatch
関数でアクションを受け取る。
アクションを受け取ると、ストアが保持している現在のステートと受け取ったアクションをルートリデューサーに渡し、ステートを更新する。
ストアを拡張する。 例えば以下のような機能を実現する。
const store = configureStore({
reducer: {
counter: counterReducer,
},
});
Redux Toolkit では createStore
の代わりに configureStore
を使ってストアを生成する。
configureStore
の第一引数には reducer
を指定(必須)したオブジェクトを渡す。
このリデューサーは createSlice
によって生成したオブジェクト内の関数。
リデューサーは機能ごとに分割して設定できる。
reducer に指定したオブジェクトのキー名はそのままステートのキー名となる。
機能ごとに分割したリデューサーとアクションのセット。 リデューサーは機能ごとに分割したステートを対象とする。 分割したリデューサーはスライスリデューサーと呼ばれることもある。
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
});
createSlice
は複数の更新関数を一つのリデューサーにまとめてくれる。
また、アクションを生成する関数も生成してくれる。
counterSlice.reducer; // リデューサー
counterSlice.actions.increment(); // typeが "counter/increment" のアクションを生成する
アクションのtypeは createSliceに渡した name
の値と、 reducers
に渡したオブジェクトのキー名を合わせたものとなる。
createSlice
内のリデューサーは引数で受け取ったステートをそのまま変更できる。
// 通常のリデューサー
const newState = {
...state,
value: state.value + 1,
};
// createSlice内のリデューサー
state.value += 1;
内部でImmerというライブラリを使っているとのこと。
リデューサーと一緒にアクションのpayloadを設定する関数を設定できる。
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
incrementAmount: {
reducer(state, action) {
state.value += action.payload.amount;
},
prepare(amount) {
return { payload: { amount } };
}
}
},
});
内部で非同期処理を行い、処理が終わったタイミングでdispatchを行う関数のことをサンクと呼ぶ。
サンクを作成する関数をサンククリエーター関数と呼ぶ
サンククリエーター関数を実行すると dispatch
と getState
を引数としてとる関数(サンク関数)が返ってくる。
サンク関数を store.dispatch
に渡すと非同期処理が行われ、終わったタイミングでdispatchが実行される。
// サンククリエーター関数
const incrementAsync = amount => {
// サンク関数
return (dispatch, getState) => {
setTimeout(() => {
dispatch(incrementByAmount(amount));
}, 1000);
}
};
store.dispatch(incrementAsync(5));
サンクを使用するには redux-thunk
ミドルウェアが必要。
Redux Tookitのデフォルト設定で redux-thunk
は追加されている。
内部でajax通信を行うサンク関数の例。
// サンククリエーター関数
const fetchUserById = userId => {
// サンク関数
return async (dispatch, getState) => {
try {
const user = await userAPI.fetchById(userId)
dispatch(userLoaded(user))
} catch (err) {
// エラー処理
}
}
}
start/success/failure アクションを自動的にディスパッチするサンクを作成する。
const fetchPosts = createAsyncThunk('posts/fetchPosts', async() => {
const response = await client.get('/fakeApi/posts');
return response.posts;
});
第一引数はアクションタイプのプレフィックス。
第二引数はPromiseを返す処理。
dispatch(fetchPosts())
を実行するとまずタイプが posts/fetchPosts/pending
のアクションをディスパッチする。
そしてPromiseが解決できると post/fetchPosts/fulfilled
または post/fetchPosts/rejected
のアクションをディスパッチする。
Redux自体の機能。 ストアから特定のステートを取得するフック。
const count = useSelector(state => {
return state.counter.value,
});
ストアが更新されると useSelector
は再実行される。
ステートが変更されている場合はコンポーネントを再レンダリングする。
Redux自体の機能。 ディスパッチ関数を取得するフック。
const dispatch = useDispatch();
dispatch({
type: "counter/increment",
});
userSelector のメモ化版。 特定の値が変更された時のみセレクターを実行し、再レンダリングを行う。
要素のコレクション(ユーザーリストや投稿リストなど)を扱いやすくするためのAPI。 コレクションを操作するための関数やセレクタが自動生成されたり、ソート順が維持されたりといったメリットがある。
createEntityAdapter
関数を使うとステートはデフォルトで ids
配列と entities
オブジェクトを持つオブジェクトとなる。
entities
オブジェクトのキーは ids
配列に格納されてる各IDとなる。
これによって find
関数ではなく entities[id]
のように直接要素へアクセスできるようになるため高速。
createEntityAdapter
の第一引数にはオプションを渡す。
setComparer
オプションは比較関数を設定できる。この比較順に都度 ids
配列が並び替えられる。
const adapter = createEntityAdapter({
sortComparer: (a, b) => b.date.localeCompare(a.date)
});
console.log(adapter.getInitialState()); // { ids: [], entities: {} }
ステートにプロパティを追加したい場合は getInitialState
関数の第一引数に指定する。
console.log(adapter.getInitialState({
otherState: 'other',
})); // { ids: [], entities: {}, otherState: 'other' }
コレクションに要素を追加する場合は addOne
関数、または addMany
関数を使用する。
addOne
関数は一つの要素、addMany
関数は複数の要素を追加する。
引数指定する state は可変である必要がある。
同じように、更新と追加を行いたい場合は upsertOne
と upsertMany
関数が使える。
getSelectors
関数で汎用的なセレクタ関数を生成できる。
export const {
selectAll,
selectById,
selectIds,
} = postAdapter.getSelectors(state => state.posts);