返回介绍

初识 Actions 和 Reducers

发布于 2025-02-17 12:51:31 字数 4510 浏览 0 评论 0 收藏 0

我们有了应用的核心函数,但在 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文