這系列文章主要會分成上下兩集,上集會講團隊目前遇到的問題,跟如何建立基礎的 Redux-toolkit,下集會講如何使用 RTK-query
目前團隊在重構舊有專案,打算使用 Redux-toolkit
+ RTK-query
去取代原有的 useContext api
目前專案使用技術
- Next.js
- TypeScript
沒學過或是不熟悉 TypeScript
的讀者不要害怕,因為這篇會是使用 JavaScript
來寫
useContext 的問題
- 不同
component
之間的狀態很難共享,由於專案是使用Next.js
去開發,所以context
必須要包在page
裡面,當遇到需要跨頁面的需求時,就會需要把context
寫到_.app
file 裡面 - 當很多需要共享的資料時,我們的
_.app
file 會變得很肥大,形成所謂的context hell
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
同時建立 reducer
跟 action
createSlice
有三個必填的部分,分別是: name
、initialState
、reducers
分別代表名稱、預設值跟 reducer
的 function
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-redux
的 useSelector
這邊有兩種寫法,沒有對錯
- 直接在畫面上調用
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 的使用方法,大家可以期待一下