初识 Actions 和 Reducers
我们有了应用的核心函数,但在 Redux 中我们不应该直接调用函数。在这些函数和应用之间还存在这一个中间层:Actions。
Action 是一个描述应用状态变化发生的简单数据结构。按照约定,每个 action 都包含一个 type
属性,
该属性用于描述操作类型。action 通常还包含其它属性,下面是一个简单的 action 例子,该 action 用来匹配
前面我们写的业务操作:
{type: 'SET_ENTRIES', entries: ['Trainspotting', '28 Days Later']}
{type: 'NEXT'}
{type: 'VOTE', entry: 'Trainspotting'}
actions 的描述就这些,但我们还需要一种方式用来把它绑定到我们实际的核心函数上。举个例子:
// 定义一个 action
let voteAction = {type: 'VOTE', entry: 'Trainspotting'}
// 该 action 应该触发下面的逻辑
return vote(state, voteAction.entry);
我们接下来要用到的是一个普通函数,它用来根据 action 和当前 state 来调用指定的核心函数,我们称这种函数叫:
reducer:
//src/reducer.js
export default function reducer(state, action) {
// Figure out which function to call and call it
}
我们应该测试这个 reducer 是否可以正确匹配我们之前的三个 actions:
//test/reducer_spec.js
import {Map, fromJS} from 'immutable';
import {expect} from 'chai';
import reducer from '../src/reducer';
describe('reducer', () => {
it('handles SET_ENTRIES', () => {
const initialState = Map();
const action = {type: 'SET_ENTRIES', entries: ['Trainspotting']};
const nextState = reducer(initialState, action);
expect(nextState).to.equal(fromJS({
entries: ['Trainspotting']
}));
});
it('handles NEXT', () => {
const initialState = fromJS({
entries: ['Trainspotting', '28 Days Later']
});
const action = {type: 'NEXT'};
const nextState = reducer(initialState, action);
expect(nextState).to.equal(fromJS({
vote: {
pair: ['Trainspotting', '28 Days Later']
},
entries: []
}));
});
it('handles VOTE', () => {
const initialState = fromJS({
vote: {
pair: ['Trainspotting', '28 Days Later']
},
entries: []
});
const action = {type: 'VOTE', entry: 'Trainspotting'};
const nextState = reducer(initialState, action);
expect(nextState).to.equal(fromJS({
vote: {
pair: ['Trainspotting', '28 Days Later'],
tally: {Trainspotting: 1}
},
entries: []
}));
});
});
我们的 reducer 将根据 action 的 type 来选择对应的核心函数,它同时也应该知道如何使用 action 的额外属性:
//src/reducer.js
import {setEntries, next, vote} from './core';
export default function reducer(state, action) {
switch (action.type) {
case 'SET_ENTRIES':
return setEntries(state, action.entries);
case 'NEXT':
return next(state);
case 'VOTE':
return vote(state, action.entry)
}
return state;
}
注意,如果 reducer 没有匹配到 action,则应该返回当前的 state。
reducers 还有一个需要特别注意的地方,那就是当传递一个未定义的 state 参数时,reducers 应该知道如何
初始化 state 为有意义的值。我们的场景中,初始值为 Map,因此如果传给 reducer 一个 undefined
state 的话,
reducers 将使用一个空的 Map 来代替:
//test/reducer_spec.js
describe('reducer', () => {
// ...
it('has an initial state', () => {
const action = {type: 'SET_ENTRIES', entries: ['Trainspotting']};
const nextState = reducer(undefined, action);
expect(nextState).to.equal(fromJS({
entries: ['Trainspotting']
}));
});
});
之前在我们的 cores.js
文件中,我们定义了初始值:
//src/core.js
export const INITIAL_STATE = Map();
所以在 reducer 中我们可以直接导入它:
//src/reducer.js
import {setEntries, next, vote, INITIAL_STATE} from './core';
export default function reducer(state = INITIAL_STATE, action) {
switch (action.type) {
case 'SET_ENTRIES':
return setEntries(state, action.entries);
case 'NEXT':
return next(state);
case 'VOTE':
return vote(state, action.entry)
}
return state;
}
事实上,提供一个 action 集合,你可以将它们分解并作用在当前状态上,这也是为什么称它们为 reducer 的原因:
它完全适配 reduce 方法:
//test/reducer_spec.js
it('can be used with reduce', () => {
const actions = [
{type: 'SET_ENTRIES', entries: ['Trainspotting', '28 Days Later']},
{type: 'NEXT'},
{type: 'VOTE', entry: 'Trainspotting'},
{type: 'VOTE', entry: '28 Days Later'},
{type: 'VOTE', entry: 'Trainspotting'},
{type: 'NEXT'}
];
const finalState = actions.reduce(reducer, Map());
expect(finalState).to.equal(fromJS({
winner: 'Trainspotting'
}));
});
相比直接调用核心业务函数,这种批处理或称之为重放一个 action 集合的能力主要依赖于状态转换的 action/reducer 模型。
举个例子,你可以把 actions 序列化成 json,并轻松的将它发送给 Web Worker 去执行你的 reducer 逻辑。或者
直接通过网络发送到其它地方供日后执行!
注意我们这里使用的是普通 js 对象作为 actions,而并非不可变数据类型。这是 Redux 提倡我们的做法。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论