前情提要:在前面关於 React 的几篇文章,学会了建立 React Component,依据使用者操作在组件间处理资料。接下来就会提到如何使用 Redux,更方便的来管理应用程序中复杂化的资料状态。
在 React 中,State 被用来储存特定的资料状态,网页 UI会根据 State 状态呈现出画面 View,当使用者操作 Action 事件触发资料状态改变,网页 UI 就会依据新的 State 更新网页内容。
若有多个组件需要共享同一个 State,之前虽然提到可以使用提升 State 到最上层 Component,再经过 Props去传递的方法,但当网页内容复杂化,管理的资料状态增加後就很难去控制这些不断变化的 State。
一个很好的解决方法就是将 State 提取到一个地方集中存放,并且指定 State 依据什麽样的 Action 来转变,就可以方便在 Component 间取用共用的资料状态、和操作改变 State 的 Action,这就是 Redux 的作用。
Redux 是如何运作?在 Redux 中的所有资料都遵循一个单向资料流的模式,当使用者触发操作事件,藉由 Dispatch Action 来让 Store 启动 Reducer,根据当前 State 和指定的 Action,产生一个新的 State 回传,Store 再通知所有相关组件进行对应的更新。
在 Codesandbox 新增一个 React 专案,在左方的 Dependencies 安装 Redux,不过我们目前还不会用到 React 的任何功能,下一篇才会讲到如何在 React 专案中使用 Redux。
在 Redux 中的 State 会是一个单一的物件存放在 Store,每次发生状态更新时,都要参考先前的状态来产生一个新的 State,所以我们需要先定义一个最初始的 State,让网页内容能够依据 State 来产生画面。延续之前的计数器范例来举例,新增 reducer.js 定义 initialState
,包含每个成员的姓名和分数资料:
reducer.js
const initialState = {
members: [
{ name: "May", score: 0 },
{ name: "Julia", score: 0 },
{ name: "Selina", score: 0 }
]
};
Action 被用来描述会发生的操作事件,它是一个 JavaScript 物件,包含 type
属性指定 Action 类型、和 payload
属性定义动作所需的额外数据。比如,我们定义一个按下 Plus 按钮的动作 ADD_SCORE
,并且取得该成员的编号索引 idx
,知道是针对哪位成员的分数进行操作。
// 添加一个新的 action type:
export const ADD_SCORE = "ADD_SCORE";
// 定义一个 action
{
type: ADD_SCORE,
payload: { idx }
}
接下来一并定义另一个动作 REDUCE_SCORE
,而通常会建立一个 Action Creator 产生该动作的 Function,回传需要的指定 Action 供 Dispatch 使用,如下面的程序码所示:
action.js
export const ADD_SCORE = "ADD_SCORE";
export const REDUCE_SCORE = "REDUCE_SCORE";
// action creator
export function addScore(idx) {
return {
type: ADD_SCORE,
payload: { idx }
};
}
export function reduceScore(idx) {
return {
type: REDUCE_SCORE,
payload: { idx }
};
}
Reducer 会接收目前的 State 和指定的一个 Action,根据这两个参数计算出下一个 State 并回传给 Store。首先创建一个 Reducer,state
参数的预设值为之前定义的 initialState
;传进来的 action
尚未定义时,就回传当前原本的 State:
export default function appReducer(state = initialState, action) {
switch (action.type) {
default:
return state;
}
}
先来处理 ADD_SCORE
动作,读取 action.type
为 ADD_SCORE
时,执行分数加一的动作,计算出要回传的新 State。这边要注意的是,Reducer 不允许我们直接修改当前的 state
,我们只能通过复制该值来修改。
reducer.js
import { ADD_SCORE, REDUCE_SCORE } from "./actions";
const initialState...
export default function appReducer(state = initialState, action) {
switch (action.type) {
case ADD_SCORE: {
let new_members = state.members;
new_members[action.payload.idx] = {
...new_members[action.payload.idx],
score: new_members[action.payload.idx].score + 1
};
// 回传一份新的state object
return {
members: new_members
};
}
default:
...
}
}
然後是 REDUCE_SCORE
动作的部分:
...
switch (action.type) {
case ADD_SCORE:
...
case REDUCE_SCORE: {
let new_members = state.members;
new_members[action.payload.idx] = {
...new_members[action.payload.idx],
score: new_members[action.payload.idx].score - 1
};
return {
members: new_members
};
}
default:
...
}
}
Redux 实际上只会有一个 Reducer 函数,但我们还是可以将 State 拆分给好几个 Reducer 来控制,透过 combineReducers
合并成一个 rootReducer
,让 Reducer 只处理与各自相关的 State 更新动作。
const initialState2...
export default function Reducer2(state = initialState2, action) {
switch (action.type) {
...
}
}
透过 combineReducers
合并组合多个 Reducer:
import { combineReducers } from 'redux'
import reducer1 from ...
import reducer2 from ...
const rootReducer = combineReducers({
r1: reducer1,
r2: reducer2
})
export default rootReducer
前面我们创建了 Action 与 Reducer,而 Store 的用处就是将它们组合在一起,保存 State、让网页能够读取更新状态,而每一个 Redux 中只会有一个 Store。接下来的动作就是创建 Store,并引入 rootReducer
:
import { createStore } from "redux";
import rootReducer from "./reducer";
const store = createStore(rootReducer);
export default store;
建立好 Store 之後,在 index.js 引入 addScore
和 reduceScore
动作,并通过 Store 提供的方法 store.dispatch()
来调用 Action,就能呼叫对应的 Reducer Function 更新 State。
index.js
import { addScore , reduceScore } from "./actions";
import store from "./store";
// 触发addScore,对编号索引 idx = 1 (第二位成员)执行分数+1的动作
store.dispatch(addScore(1));
这样就完成了 Redux 完整的运作,我们另外使用一些 Store 提供的方法来查看更新後的 State 结果。
store.getState()
:取得目前的 State状态store.subscribe(listener)
:注册 Listener,每次 Dispatch Action 时会呼叫,并且会回传一个用来撤销 Listener 的 Function。index.js
import { addScore , reduceScore } from "./actions";
import store from "./store";
const unsubscribe = store.subscribe(() =>
console.log("State after dispatch: ", store.getState())
);
store.dispatch(addScore(1));
// 撤销 listener
unsubscribe();
成功将第二位成员的分数+1
学到了如何在 Redux 透过 Action 管理更新所有的 State,但我们还没有将 State 放到网页元件里渲染出对应的画面,所以接下来,就会谈到如何在 React 中使用 Redux 来处理资料状态,那就下一篇文章再见罗!
如果文章中有错误的地方,要麻烦各位大大不吝赐教;喜欢的话,也要记得帮我按赞订阅喔❤️
<<: 【踩坑】按钮闪阿闪,gradient 在 hover 时闪烁
>>: [Android Studio 30天自我挑战] Switch 元件介绍
作业系统L3-行程 行程(Process)–正在执行的程序 行程(Process) VS 程序(Pr...
先前将主机已经注册上去了 那接下来就是进到『Task Definitions』开始来建立服务 点选『...
如何制作 Instagram 帖子 3 步曲 从灵感开始 - Visual Paradigm Onl...
Redis.config GENERAL daemonize 是否要用daemon方式启动Redis...
引述自100Days of Swift-Class inheritance: The second ...