這一篇開始會進入另一個新章節,只是這個章節我只會簡單的拆成兩篇,所以內容相對會少一點,那麼什麼是 Redux 呢?這一篇就讓我們來了解一下吧。
那麼什麼是 Redux 呢?Redux 其實是一個狀態管理工具,你也可以把它想像成容器,它可以幫助我們管理整個網頁應用程式的狀態,而 Redux 本身是可以獨立使用的。
剛剛有提到 Redux 是專門用於狀態管理的工具並且可以獨立使用,所以在官方首頁上就可以看到一句話:
A Predictable State Container for JS Apps
所以我們可以得知 Redux 本身是可以獨立使用的,但是部分人看到 Redux 會以為它就是專屬於 React 的狀態管理工具,如同 Vue 的 Vuex 一樣,也因為 Redux 本身可以獨立運作的關係,所以 Redux 也可以和 React 以外的框架或是函式庫一起使用,如:Angular、Vue、jQuery 等,除此之外 Redux 也非常小一包,只有 2kb 左右的大小。
或許這時候你可能會想到前面章節所介紹的 useReducer
範例:
const reducer = (state, action) => {
switch(action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return { count: 0 }
}
}
const App = () => {
const initialState = {
count: 0,
};
const [state, dispatch] = React.useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<br />
<button onClick={() => dispatch({ type: "decrement" })}>Decrement</button>
<button onClick={() => dispatch({ type: "increment" })}>Increment</button>
</div>
);
}
const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);
root.render(<App />);
那麼 useReducer
跟 Redux 有什麼關係呢?本質上來講 useReducer
是 React 官方參考 Redux 後所提供的一個 Hook,但實際上來講兩者能做的事情還是有一定差異的,例如:Redux 可以讓我們在不同的頁面或是不同的元件之間共享狀態,而 useReducer
則是只能在同一個元件之間共享狀態,除非、你有使用到 Context API,否則正常情況下 useReducer
是無法在不同的元件之間共享狀態的。
那麼剛剛有提到 Redux 本身是可以獨立使用的,只是如果可以獨立運作的話,那 React Redux 跟 Redux 有什麼關係呢?其實是因為 React 非常常使用 Redux 來管理狀態,所以 React 官方就提供了一個 React Redux 的套件,這個套件就是將 Redux 跟 React 進行整合,讓我們可以更方便的使用 Redux。
接下來我們要來安裝 React Redux 與 Redux Toolkit,這兩個套件都是 Redux 的相關套件,而 Redux Toolkit 則是 Redux 官方提供的一個套件,它可以讓我們更方便的使用 Redux。
那麼為什麼要用 Redux Toolkit 呢?其實你到 Redux Toolkit 官網可以看到有列出三個重點:
基於上述三點的關係,所以我們這邊會使用 Redux Toolkit 來進行 Redux 的設定,因此我們要先安裝 Redux Toolkit 跟 React Redux:
npm install react-redux @reduxjs/toolkit
安裝時要注意一下 React Redux 8.x 至少需要 React 16.8.3 以上的版本才能夠使用 React Hook 唷。
那麼接著這邊將會使用這一份範例程式碼當作範例,建議可以先下載下來這樣子才方便後面的學習。
如果沒有任何問題的話,請先在 src
資料夾下建立一個 store
資料夾,然後在 store
資料夾下建立一個 index.js
檔案,接著在 index.js
檔案中加入以下程式碼:
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({
reducer: {},
});
export default store;
接著打開 src/main.jsx
檔案,將引入 import store from './store'
、import { Provider } from 'react-redux'
,並將 <Provider store={store}>
包住 <App />
:
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import store from './store'
import { Provider } from 'react-redux'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Provider store={ store }>
<App />
</Provider>
</React.StrictMode>
)
到目前為止,我們已經完成了 React Redux 的設定,所以接著我們要來調整一下原本 ToDoLint 的程式碼,讓它可以使用 Redux 來管理狀態。
接下來我們要建立 React Slice,所以請在 store
資料夾下建立一個 todoSlice.js
檔案,然後在 todoSlice.js
檔案中引入 createSlice
:
import { createSlice } from "@reduxjs/toolkit";
而 createSlice
是 Redux Toolkit 提供的一個方法,它可以讓我們更方便的建立 Redux Slice,而 createSlice
會回傳一個物件,裡面包含了 reducer
跟 actions
,所以我們可以這樣子使用:
const todoSlice = createSlice({
name: "todo"
});
那麼 createSlice
裡面有兩個東西很重要,分別是 initialState
跟 reducers
,initialState
是初始的狀態,而 reducers
則是一個物件,裡面包含了所有的 action
,action
你也可以想像成函式,而這些函式會帶入 state
跟 action
這兩個參數。
接著我們就來把 ToDoLint 的程式碼轉換成 Redux 的程式碼,首先我們要先建立 initialState
,所以在原本寫在 App.jsx 的 const [ todoList, setTodoList ] = useState(JSON.parse(localStorage.getItem('todoList')) || []);
就會變成 initialState
的值:
import { createSlice } from "@reduxjs/toolkit";
const store = createSlice({
initialState: {
todoLint: []
},
reducer: {},
});
接著 addTodo
呢?則會放在 reducer
:
import { createSlice } from "@reduxjs/toolkit";
const todoSlice = createSlice({
name: "todo",
initialState: {
todoList: [],
},
reducers: {
addTodo (state, action) {
}
},
});
接著我們要把原本的 setTodoList
改成 state.todoList.push(action.payload);
即可。
在 Slice 這邊有一件事情很重要,我們在匯出的時候,不是直接匯出 todoSlice
,而是匯出 todoSlice.reducer
,因為 todoSlice
會包含 reducer
跟 actions
,而我們只需要 reducer
,所以我們要匯出 todoSlice.reducer
跟 todoSlice.actions
:
export const { addTodo } = todoSlice.actions;
export default todoSlice.reducer;
到目前為止,我們已經把原本的程式碼轉換成 Redux 的程式碼了,接著我們要把 todoSlice
匯出,並且在 src/store/index.js
檔案中引入 todoSlice
:
import { configureStore } from "@reduxjs/toolkit";
import todoSlice from "./todoSlice";
const store = configureStore({
reducer: {
todoSlice,
},
});
export default store;
最後回到 App.jsx 檔案,我們要把原本的 useState
改成 useSelector
跟 useDispatch
,所以要引入 useSelector
跟 useDispatch
:
import { useSelector, useDispatch } from "react-redux";
接著該如何取得放在 Redux 的 todoList
呢?我們可以這樣子取得:
const todoList = useSelector((state) => state.todoSlice.todoList);
至於 addTodo
的話則還必須額外引入 addTodo
:
import { addTodo } from "./store/todoSlice";
那麼這邊就有一個問題發生了,由於在 App.jsx 我們是命名為 addTodo
,而在 todoSlice
裡面我們是命名為 addTodo
,所以會發生命名衝突,所以我們要把 App.jsx 的 addTodo
改成 handleAddTodo
,這樣子才不會影響到 todoSlice
,所以目前程式碼變成以下這樣:
const handleAddTodo = (event) => {
dispatch(addTodo({
id: Date.now(),
name: event.target.previousElementSibling.value,
status: false
}
));
event.target.previousElementSibling.value = '';
};
接著原本的 List
是接收著 setTodoList
,除此之外也要將原本寫在 List
底下的 updateTodo
改放到 todoSlice
裡面,所以目前程式碼變成以下這樣:
import { createSlice } from "@reduxjs/toolkit";
const todoSlice = createSlice({
name: "todo",
initialState: {
todoList: [],
},
reducers: {
addTodo (state, action) {
state.todoList.push(action.payload);
},
updateTodo(state, action) {
const id = action.payload;
const index = state.todoList.findIndex((todo) => todo.id === Number(id));
state.todoList[index].status = !state.todoList[index].status;
},
},
});
export const { addTodo, updateTodo } = todoSlice.actions;
export default todoSlice.reducer;
接著原本在 List 的程式碼就改成以下
const handleUpdateTodo = (event) => {
const { id } = event.target.dataset;
dispatch(updateTodo(id));
}
這樣子我們就將原本的程式碼轉換成 Redux 的程式碼了,而這邊也是簡單的快速入門而已,但範例程式碼一樣都放在這邊。
本文將會同步更新到我的部落格