文章大綱
一個平凡的下午,小口小口啜飲著有點燙口的紅茶拿鐵,突然看到前端對話群組掀起一陣討論,討論著新案子的style解決方案,是要用上次我們一起看到的 Taiwindcss 呢?還是 styled-components 呢?
眾多討論串中,我看到了一句話,讓我的下午變得再也不平凡…
他是這樣說的:
你們決定好就好,但資料流的部分我先安裝 Saga 囉!
等等!那是什麼東西?上網查了一番…是在Redux的基礎上又有一些資料流程的處理工具。
我連Redux都沒用過啊~~(尖叫)
於是先來一篇 Redux 基本功~ 等我學會 Saga 說不定又可以在寫一篇 Saga 的XDD
一開始要先做點功課
首先我們先看官網的介紹,要使用 Redux 的話,推薦可以安裝 Redux-Toolkit,讓編寫 Redux 的過程可以更整齊bla bla…
Redux 也有提供他們使用 Redux-Toolkit 寫好的範例,可以從這邊下載下來看。
$ npx create-react-app my-app --template redux
使用 Redux,你會聽到幾個關鍵字:
- state:有點類似宣告變數,就是存放資料和狀態的地方。
- reducer:用來修改
state的橋樑。在 Redux,你要動到state的狀態,一定要透過reducer。 - action:
reducer要修改state的話,需要傳入action,也就是說reducer要作用的話一定要傳入一個action去判斷要啟動哪個reducer。 - Provider:在所有組件的最外面(通常是
Index.js)包一層Provider,傳入store,所有被包覆的組件都可以使用到store的狀態。 - store:就是
state、reducer、action的集合,換句話說,放state、reducer、action的那隻檔案就叫做store。
Redux 的流程大概會是這樣:
- 創建
Store,用來裝所有要集中處理的資料和方法的地方 - 在最外層使用
Privider把整個 App 包起來,讓所有的組件都可以使用到Store內的資料和方法。 - 撰寫
slice,裡面會存放states(資料)和reducers(方法)。 - 在專案內讀取這些資料或更新這些資料。
安裝環境
一開始你會有一個 react 專案
$ npx create-react-app my-project
然後安裝我們的主角~~~
$ yarn add react-redux @reduxjs/toolkit
創建Store
新增一個檔案store/index.js
import { configureStore } from "@reduxjs/toolkit";
import todoReducer from "./slice/todo";
export default configureStore({
reducer: {
todo: todoReducer,
},
});
configureStore是toolkit的創建store參數,如果你沒有使用toolkit,Redux 有原生的參數可以使用。
你會發現我們在這邊引用了一個slice檔案(等等會創建這個檔案),也就是說你日後會在這邊引入很多slice,可能是order訂單、customer客戶…等,像是幫資料分類的概念。
類似這樣:
import { configureStore } from "@reduxjs/toolkit";
import todoReducer from "./slice/todo";
import orderReducer from "./slice/order";
export default configureStore({
reducer: {
todo: todoReducer,
order: orderReducer,
// ...
},
});
在App.js加入Provider
在這邊要把provider包在最外面,這樣才會整個專案都可以存取到。
放在index.js也可以,只要是最外層就好,但我不是很喜歡我的邏輯去放到index.js,所有修改都從App.js開始就好ㄌ。
import { Provider } from "react-redux";
import store from "@/store";
function App() {
return (
<Provider store={store}>
//...
</Provider>
);
}
export default App;
創建Slice
slice是toolkit的概念,他是把 Redux 原生的state、reducer、action都合在一包,並且叫它slice。
所以看下面程式碼就知道,裡面會包含上述這些東西。
創建store/slice/todo.js
import { createSlice } from "@reduxjs/toolkit";
export const todoSlice = createSlice({
name: "todo",
initialState: {
//...
},
reducers: {
//...
},
});
export default todoSlice.reducer;
createSlice是toolkit的工具,如果你沒有使用toolkit,就要自己創建states、reducers、actions。
首先我們從toolkit引入createSlice,創建的時候裡面要放物件:
initialState:就是放所有狀態初始值,但是slice裡就只需要設定initialState,不需要設定state。reducers:通常是放很多的函式,會有兩個參數state和action,state就是讓你修改狀態,你可以直接對state進行操作;action就是讓你傳入參數。
放好之後會類似這樣:
import { createSlice } from "@reduxjs/toolkit";
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);
},
},
});
export default todoSlice.reducer;
在畫面中引用
我會有兩個檔案,一個是顯示 todo list 的 component,然後在首頁放進這個 component,並且首頁也會有一顆按鈕,可以直接新增 todo。
src/
--- components/
--- Todolist.js
--- store/
--- slice/
--- todo.js
--- index.js
--- App.js
因為按鈕和 todo list 是在不同檔案,這時候就是 Redux 最重要的存在意義,存取全域的資料,讓不同的 component 同時能拿到最新資料狀態。
輸出slice的selector
import { createSlice } from "@reduxjs/toolkit";
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);
},
},
});
export const selectTodo = (state) => state.todo; // <---加入這行
export default todoSlice.reducer;
建立components/TodoList.js
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 的useSelectorapi,傳入你在slice建立的Selector。
這時候你的 component 已經可以取得 Redux 的資料了。
那如果我們想要在頁面中引入 component,並且頁面要直接改變 Redux 的資料呢?
我們在App.js中引入剛剛創建的Todolist.js
import TodoList from "./components/TodoList";
//...
<TodoList />
你可以看到這邊我們並沒有傳入任何資料給 <Todolist />,因為他已經是抓取 Redux 的資料,而不是依賴父元件的props。
還記得在slice中我們有創建一個addTodo的reducer。
export const todoSlice = createSlice({
name: "todo",
initialState: {
//...
},
reducers: {
addTodo: (state, action) => { // <-- 這邊~~~~~~
state.todolist.push(action.payload);
},
},
});
export default todoSlice.reducer;
要取用reduce的話,必須傳入一個action。
但是在Toolkit中,他已經幫我們創建好了,所以我們只要輸出讓頁面可以取用就好。
export const todoSlice = createSlice({
name: "todo",
initialState: {
//...
},
reducers: {
addTodo: (state, action) => {
state.todolist.push(action.payload);
},
},
});
export const { addTodo } = todoSlice.actions; // <-- 加上這行
export default todoSlice.reducer;
在App.js中取用reducer並且傳入action。
import { useDispatch } from "react-redux";
import { addTodo } from "./store/slice/todo";
function App() {
const dispatch = useDispatch();
const handleAddTodo = () => {
dispatch(
addTodo("a new todo item");
);
};
return (
<TodoList />
<button onClick={handleAddTodo}>add todo</button>
)
}
這樣子點下按鈕,就可以直接更新 Redux 的資料,所有有呼叫 Redux 的todo資料的組件都會同步更新囉!





發表評論
想要加入討論嗎?請盡情發表您的想法!