使用 Redux-toolkit + RTK-query 取代 useContext (上)

janlin002
8 min readOct 4, 2023
Photo by Lautaro Andreani on Unsplash

這系列文章主要會分成上下兩集,上集會講團隊目前遇到的問題,跟如何建立基礎的 Redux-toolkit,下集會講如何使用 RTK-query

目前團隊在重構舊有專案,打算使用 Redux-toolkit + RTK-query 去取代原有的 useContext api

目前專案使用技術

  • Next.js
  • TypeScript

沒學過或是不熟悉 TypeScript 的讀者不要害怕,因為這篇會是使用 JavaScript 來寫

useContext 的問題

  1. 不同 component 之間的狀態很難共享,由於專案是使用 Next.js 去開發,所以 context 必須要包在 page 裡面,當遇到需要跨頁面的需求時,就會需要把 context 寫到 _.app file 裡面
  2. 當很多需要共享的資料時,我們的_.app file 會變得很肥大,形成所謂的 context hell
https://dev.to/hyposlasher/no-more-react-context-hell-4pm3

3. 相同 provider 下的 component 會同時 re-render,不過這個問題可使用 useMemo 或是傳遞 children 解決

4. 沒有 devtools,在 debug 上偏麻煩

當初使用 useContext 的考量點

當初團隊在考慮使用的工具時,主要考量點就是不確定這個套件能「活多久」,畢竟我們目前做的是一個需要長期維護的 2C 產品,如果使用的套件沒辦法升級上去的話,之後的工程師會被迫去寫一個被淘汰的技術,所以才會考慮去使用由 React 官方維護的 context api

為什麼突然要改成 Redux-toolkit?

會考慮轉換成 Redux-toolkit 的原因是因為發現 context hell 太過嚴重,加上 Redux-toolkit 也很成熟了,所以打算先從小一點的專案慢慢換過去

Redux 基本概念

相信有寫過 React 的讀者,對於 Redux 一定不陌生,我這邊就不多做介紹了

建立基礎 Redux-toolkit 架構

首先先建立基礎環境

npm i redux @reduxjs/toolkit

建立 store

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({
reducer: {
...
}
})

這邊 reducer 可以先不寫沒關係,等到我們的 reducer 架設好以後,再回來補上

建立好 store以後,須將 store綁定在專案的最上層,讓 children components 可以使用到,這個觀念跟 context 一樣,這時候我們會需要使用到 Provider

import { Provider } from 'react-redux'

function App() {
return {
<Provider store={store}>
{children}
</Provider>
}
}

建立 reducer & action

Redux-toolkit 跟過往的 Redux 略顯不同,他可以使用 createSlice 同時建立 reduceraction

createSlice 有三個必填的部分,分別是: nameinitialStatereducers 分別代表名稱、預設值跟 reducerfunction

import { createSlice } from '@reduxjs/toolkit'

const initialState = {
profile: {
name: '',
age: ''
}
}

const userSlice = createSlice({
name: "user", // 代表這個 slice 的名字
initialState, // 預設值
reducers: {
// 改變 initialState 的 function
setLogin(state, action){
state.profile = action
}
}
})

export const { setLogin } = userSlice.actions // 將 action 導出

export default userSlice.reducer // 將 reducer 導出,記得這邊是 reducer 不是 reudcers

將 reducer 寫入 store

簡單建立好 reducer 以後,需要將這個 reducer 寫到剛剛的 store 裏面

import { userSlice } from './store/userSlice'

const store = configureSore({
reducer: {
// key: value
user: userSlice
}
})

在畫面中調用 store 裡的資料

需要在畫面中調用 store 裡面的資料,我們會需要使用到 react-reduxuseSelector
這邊有兩種寫法,沒有對錯

  1. 直接在畫面上調用
import { useSelector } from 'react-redux'

const user = useSelector(state => state.user)

2. 在 createSlice 那頁,export 出來

export const selectTodo = (state) => state.user;

const user = useSelector(selectTodo)

因為資料是從 store 裡取出的,所以 state.xxx 裡面的 xxx 就是你在 store建立的名字,例如: user: userSlice ,那麼如果想要取用 userSlice裡面的資料,就要使用 user 去取

在畫面中調用 action

import { useDispatch } from 'react-redux'
import { setLogin } from '../store/userSlice'

const dispatch = useDispatch(setLogin(...))

常見問題

Q: createSlice 裡面的 name,一定要跟 configureStore reducers 裡面的 key 一樣嗎(程式碼裡面有標示 => 這個)?

const store = configureStore({
reducer: {
todo => 這個: todoReducer,
},
})

export const todoSlice = createSlice({
name: "todo", => 這個
initialState: {
todolist: [
{ id: 1, name: "first todo on redux" },
{ id: 2, name: "second todo in list" },
],
},
reducers: {
addTodo: (state, action) => {
state.todolist.push(action.payload)
},
},
})

A: 不用,但建議一樣,這樣比較好管理,首先 configureStore 裡面的 todo 代表的是 store 對應到的 key ,所以當你需要使用 todoReducer 裡的資料會寫成

const todo = useSelector(state => state.todo)

然後 slice 裡面的 todo 就只是單純 slice 的名稱,不過如果大家有在用 Redux-devtools 的話,這個 slice name 非常好用,為了辨識我將 name 改成 todozz

export const todoSlice = createSlice({
name: "todozz",
initialState: {
todolist: [
{ id: 1, name: "first todo on redux" },
{ id: 2, name: "second todo in list" },
],
},
reducers: {
addTodo: (state, action) => {
state.todolist.push(action.payload)
},
},
})

當今天 addTodo 被觸發以後,Redux-devtools 會顯示 todozz/addTodo

是不是感覺超級方便?

下集我們會來講 RTK-query 的使用方法,大家可以期待一下

--

--