在基礎入門的文章當中,提及了幾種跨元件傳遞 state 的方式(ReactJS入門 - 各階層Component 溝通),但隨著專案增胖及元件的增加,這些方式都會使得 state 難以維護(每一個元件都必須維護自己的 state,更別說跨元件互動了)。
Redux 可以讓網站所有的 state 統一管理,也能跨元件取用,還能讓資料以及畫面更新分開處理。
npm install --save redux react-redux
分別安裝 redux 以及 react-redux兩個套件。
Reducer 會負責管理 state 狀態,以及處理不同的 action。
先在 src 下建立 reducer 資料夾,並在資料夾中新增一個 todolist.js 的檔案
const initState = {
todoList: ['第一件事情', '第二件事情'],
};
這裡建立一組初始資料,這樣比較能知道這個 reducer 是在儲存什麼內容的。
const todoReducer = (state = initState, action) => {
switch (action.type) {
default:
return state;
}
};
export default todoReducer;
Reducer 第一個參數會先將預設的 state 傳給 reducer 儲存,第二個參數則是傳入要 reducer 對 state 做什麼 action(後面會提到)。
在這裏,如果不做任何的 action,預設就是回傳 state。
store 會負責管控 reducer,集中管理專案中的所有 reducer。
先在 src 下建立 store 目錄並在 store 資料夾建立 index.js。
如果跟本範例一樣只有一個 reducer ,只需要 import reducer 並呼叫 createStore 就好
import { createStore } from 'redux';
import todoReducer from '../reducer/todolist';
const store = createStore(todoReducer);
export default store;
如果日後專案有多個 reducer 需要整合,則需要先呼叫 combineReducers 後,再呼叫 createStore。
import { createStore, combineReducers } from 'redux';
import todoReducer from '../reducer/todolist';
import otherReducer from '../reducer/other';
const allReducer = combineReducers({
todoReducer,
otherReducer,
});
const store = createStore(allReducer);
Provider 會接收指定的 store,與之前 ContextAPI 是差不多的用法。通常我會把 Provider 放在專案的根元件上(大概就是呼叫 ReactDOM.render 的地方,通常是在 index.js)。
我們打開 src/index.js,然後引入 store 以及 Provider,並將元件以 <Provider> 元件包起來,記得將 store 這個 props 指定給 provider。
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './store';
import { Provider } from 'react-redux';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
useSelector 可以讓我們把 store 當中的資料提取到元件當中。
先打開 src/App.js ,先引入 useSelector 。
import { useSelector } from 'react-redux';
function App() {
const todolist = useSelector(state => state.todoList);
return (
<div className="App">
{
todolist.map(e => (
<h1>{ e }</h1>
))
}
</div>
);
}
export default App;
告訴 useSelector 要取用哪一個 state(這邊取用剛剛由 todoReducer 管理的 todoList)。
基本上到這邊可以執行了,可以看到預設建立的兩個項目。
在完成 store 以及 reducer 的建立之後,我們要來建立不同的 Action 來改變 reducer 所保管的 state。
先建立一個 action 目錄,並建立一個 todolist.js 的檔案,並在檔案內宣告一個命令名稱的常數:
export const ADD_TODO = 'ADD_TODO';
這裡為使可讀性高,因此會採用全大寫以分辨常數。
再來要建立傳給 Reducer 的物件,告訴 Reducer 動作以及參數:
export const addTodo = todo => (
{
type:ADD_TODO,
payload:{
todo,
}
}
)
由於 todo 會根據使用者輸入而不同,所以要將其寫成閉包丟入 payload 當中。
回到 Reducer 的部分,要告訴 reducer 如何處理不同的 Action。
先打開 src/redcuer/todolist.js ,先將剛剛建立好的 action 檔案引入,並在 switch case 當中插入剛剛建立好的 action:
import * as actions from '../action/todolist'
const initState = {
todoList: ['第一件事情', '第二件事情'],
};
const todoReducer = (state = initState, action) => {
switch (action.type) {
case actions.ADD_TODO:
return {
...state,
todoList:[
...state.todoList,
action.payload.todo,
]
}
default:
return state;
}
};
export default todoReducer;
在 case 當中不用再以字串打 "ADD_TODO "了,可以直接使用剛剛在 action/todolist.js 中建立好的。
要注意的是,最後要 return 一個新的 state ,而不能只是更新之前就的 state,例如:
state.todoList.push(action.payload.todo);
如果只是將新的值 push 到原本的 state,這樣並不會觸發畫面更新。
最後要藉由 useDispatch 去觸發剛才已經建立好的 Action,先打開 src/App.js,並引入 useDispatch 以及要呼叫的 Action:
import { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo } from './action/todolist'
function App() {
const todolist = useSelector(state => state.todoList);
const dispatch = useDispatch();
const [newTodo, setNewTodo] = useState();
return (
<div className="App">
<input value={newTodo} onChange={(e)=>{ setNewTodo(e.target.value); }}></input>
<button type="button" onClick={ ()=> {dispatch(addTodo(newTodo));} }>新增</button>
{
todolist.map(e => (
<h1>{ e }</h1>
))
}
</div>
);
}
export default App;
然後新增一個 input 以及 button,並以 state 來管理 input 的值。而 button 則透過 dispatch() 來呼叫剛才定義好的 Action 物件,並記得將 newTodo 帶入 addTodo 參數,否則 action.payload.todo 會是 undefined。
之後執行,應該可以得到這樣的結果:
本文已經完成了基本的 redux 使用,要特別注意在 switch case 的 return 部分,必須要回傳一個新的 state 而不能只更新其中的值,否則不會呼叫畫面 re-render。
而之後文章可能搭配其他套件來串接網路的 API,這樣比較符合實際的使用情況。
關於作者
粉絲專頁
文章分類