Redux 是可以讓不同的組件,使用到共用的資料和方法,統一集中管理共用的狀態
Redux 不是 React 的 library,但很多 React 專案都會使用 Redux 來管理狀態,這也是學 React 比較複雜的地方🤪
好在官方之後出了 Redux Toolkit,也推薦大家使用 Redux Toolkit 來撰寫 Redux,它是以 Redux 為核心,也比較容易上手!
安裝環境
可依自己的需求安裝環境
創建模板 React + Redux Toolkit
1
| npx create-react-app my-app --template redux
|
安裝 react-redux、@reduxjs/toolkit
1
| npm i react-redux @reduxjs/toolkit
|
認識變數
使用 Redux,你會聽到幾個關鍵字:
- state:用來存放資料狀態
- reducer:用來放改變 state 狀態的方法
- action:reducer 要修改 state 的話,需要傳入 action ,去判斷要啟動哪個 reducer
- Provider:在所有組件的最外面(通常是 Index.js)包一層 Provider,傳入 store,所有被包覆的組件都可以使用到 store 的狀態。
- store:存放 state、reducer、action 的檔案
Redux 流程
- 創建
Store
- 在最外層加入
Provider
,並傳入 store,使整個組件都能使用 store 資料
- 創建
Slice
,設定 state、reducer、action
- 畫面渲染,取得 state、dispatch 方法改變狀態
範例情境:
todoList 列表,點擊按鈕後新增下一條
創建 store
store/index.js
1 2 3 4 5 6 7 8
| import { configureStore } from "@reduxjs/toolkit"; import todoReducer from "./slice/todo";
export default configureStore({ reducer: { todo: todoReducer, }, });
|
configureStore:創建 store 的參數
原生為 creactStore,使用 creactStore 時會有被棄用的警告
如有多個檔案,一樣 import 進來
1 2 3 4 5 6 7 8 9 10
| import { configureStore } from "@reduxjs/toolkit"; import todoReducer from "./slice/todo"; import productReducer from "./slice/product";
export default configureStore({ reducer: { todo: todoReducer, product: productReducer }, });
|
在最外層加入 Provider
src/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import React from 'react'; import { createRoot } from 'react-dom/client'; import App from './App'; import { Provider } from "react-redux"; import store from "./store/index";
const container = document.getElementById('root'); const root = createRoot(container);
root.render( <Provider store={store}> <App /> </Provider> );
|
也可以加在 App.js 的最外層,只要是外層都可以
創建 Slice
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { createSlice } from "@reduxjs/toolkit";
export const todoSlice = createSlice({ name: "todo", initialState: { todolist: [ { id: 1, name: "早上帶波比散步" }, { id: 2, name: "中午帶波比散步" }, { id: 3, name: "晚上帶波比散步" }, { id: 4, name: "睡前帶波比散步" }, ], }, reducers: { addTodo: (state, action) => { state.todolist.push(action.payload); }, }, });
export const { addTodo } = todoSlice.actions; export const selectTodo = (state) => state.todo; export default todoSlice.reducer;
|
createSlice:創建slice,把 Redux 原生的 state、reducer、action 都合在一包,稱之為 slice
name:取一個相關的名稱
initialState:所有狀態的初始值
reducers:存放函式,傳入兩個參數,第一個為需要修改的 state,第二個為 action 讓你傳入的參數
畫面渲染
component/TodoList.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React from "react"; import { useSelector } from "react-redux"; import { selectTodo } from "../store/slice/todo"; const TodoList = () => { const states = useSelector(selectTodo); return ( <ul> {states.todolist.map((i) => ( <li key={i.id}>{i.name}</li> ))} </ul> ); };
export default TodoList;
|
useSelector:要拿取 state,就要使用 Redux 的 useSelector api,傳入你在 slice 建立的 Selector
引用組件到 App.js,dispatch 取得 store 方法
App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { useDispatch, useSelector } from "react-redux"; import TodoList from "./components/TodoList"; import { addTodo } from "./store/slice/todo";
function App() { const dispatch = useDispatch();
const handleAddTodo = () => { dispatch( addTodo({ id: new Date().getTime(), name: "帶波比去公園"}) ) };
return ( <div> <TodoList /> <button onClick={handleAddTodo}>add todo</button> </div> ); }
export default App;
|
非同步操作
做前端一定會碰到 API 的串接,我們需要 createAsyncThunk 方法
我在 public 新增一個要被呼叫的 json
public/todolist.json
1 2 3 4 5 6 7 8
| { "data": [ { "id": 1, "text": "早上帶波比散步" }, { "id": 2, "text": "中午帶波比散步" }, { "id": 3, "text": "晚上帶波比散步" }, { "id": 4, "text": "睡前帶波比散步" } ] }
|
並使用 axios 串接 API
api/index.js
1 2 3 4
| import axios from "axios"; export const getData = () => { return axios.get('/index.json'); }
|
在 slice 的地方引用,並使用 createAsyncThunk 操作非同步
store/slice/todo.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import { getData } from "../../api";
export const fetchData = createAsyncThunk('todo/fetchTodo', async () => { const response = await getData(); return response.data.data })
export const todoSlice = createSlice({ name: "todo",
initialState: { loading: false, entities: [] },
reducers: { },
extraReducers: { [fetchData.pending]: (state) => { state.loading = true },
[fetchData.fulfilled]: (state, { payload }) => { state.loading = false state.entities = payload },
[fetchData.rejected]: (state) => { state.loading = false }, }, });
export const selectTodo = (state) => state.todo; export default todoSlice.reducer;
|
createAsyncThunk:在 Redux Toolkit 要呼叫非同步,需要使用 createAsyncThunk 方法
createAsyncThunk 接受兩個參數,第一個為 action type 字串,第二個為返回的 Promise,並生成一個pending
、 fulfilled
、rejected
,分派 action type 的 thunk
extraReducers:因為非同步的函式不在 createSlice 裡面,如果要在 createSlice 中監聽這些 action type,需要在 extraReducers 使用
dispatch 呼叫方法,並取得 api 回傳的資料
App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { fetchData, selectTodo } from "./store/slice/todo";
function App() { const dispatch = useDispatch(); const { entities, loading } = useSelector(selectTodo);
useEffect(() => { dispatch(fetchData()); }, [])
if (loading) return <p>Loading...</p>
return ( <div> {entities.map(item => (<div key={item.id}>{item.text}</div>))} </div> ); }
export default App;
|
⚠️eslint 出現警告
使用 useEffect 時遇到警告錯誤
1 2
| React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array
|
如果想除掉警告可以把 dispatch 放到第二個參數的陣列裡
1 2 3
| useEffect(() => { dispatch(fetchData()); }, [dispatch])
|
🚀實體工作坊分享
最近時賦學苑開了實體體驗課,即使你對程式碼沒有概念也能上手!Lala 會帶你一起做出一個個人品牌形象網站,帶你快速了解前端的開發流程,快跟我們一起玩轉 Web 吧!
🚀線上課程分享
線上課程可以加速學習的時間,省去了不少看文件的時間XD,以下是我推薦的一些課程
想學習更多關於前後端的線上課程,可以參考看看。
Hahow 有各式各樣類型的課程,而且是無限次數觀看,對學生或上班族而言,不用擔心被時間綁住
如果你是初學者,非常推薦六角學院哦!
剛開始轉職也是上了六角的課,非常的淺顯易懂,最重要的是,隨時還有線上的助教幫你解決問題!
Udemy 裡的課程非常的多,品質普遍不錯,且價格都滿實惠的,CP值很高!
也是很多工程師推薦的線上課程網站。