初识客户端的 Redux Store
Redux 将会充当我们 UI 界面的状态容器,我们已经在服务端用过 Redux,之前说的很多内容在这里也受用。
现在我们已经准备好要在 React 应用中使用 Redux 了,这也是 Redux 更常见的使用场景。
和在服务端一样,我们先来思考一下应用的状态。客户端的状态和服务端会非常的类似。
我们有两个界面,并在其中需要显示成对的用于投票的条目:
此外,结果页面需要显示票数:
投票组件还需要记录当前用户已经投票过的选项:
结果组件还需要记录胜利者:
注意这里除了 hasVoted
外,其它都映射着服务端状态的子集。
接下来我们来思考一下应用的核心逻辑,actions 和 reducers 应该是什么样的。
我们先来想想能够导致应用状态改变的操作都有那些?状态改变的来源之一是用户行为。我们的 UI 中存在两种
可能的用户操作行为:
- 用户在投票页面点击某个投票按钮;
- 用户点击下一步按钮。
另外,我们知道我们的服务端会将应用当前状态发送给客户端,我们将编写代码来接受状态数据,这也是导致状态
改变的来源之一。
我们可以从服务端状态更新开始,之前我们在服务端设置发送了一个 state
事件。该事件将携带我们之前设计的客户端
状态树的状态数据。我们的客户端 reducer 将通过一个 action 来将服务器端的状态数据合并到客户端状态树中,
这个 action 如下:
{
type: 'SET_STATE',
state: {
vote: {...}
}
}
让我们先写一下 reducer 测试代码,它应该接受上面定义的那种 action,并合并数据到客户端的当前状态中:
//test/reducer_spec.js
import {List, Map, fromJS} from 'immutable';
import {expect} from 'chai';
import reducer from '../src/reducer';
describe('reducer', () => {
it('handles SET_STATE', () => {
const initialState = Map();
const action = {
type: 'SET_STATE',
state: Map({
vote: Map({
pair: List.of('Trainspotting', '28 Days Later'),
tally: Map({Trainspotting: 1})
})
})
};
const nextState = reducer(initialState, action);
expect(nextState).to.equal(fromJS({
vote: {
pair: ['Trainspotting', '28 Days Later'],
tally: {Trainspotting: 1}
}
}));
});
});
这个 renducers 接受一个来自 socket 发送的原始的 js 数据结构,这里注意不是不可变数据类型哦。我们需要在返回前将其
转换成不可变数据类型:
//test/reducer_spec.js
it('handles SET_STATE with plain JS payload', () => {
const initialState = Map();
const action = {
type: 'SET_STATE',
state: {
vote: {
pair: ['Trainspotting', '28 Days Later'],
tally: {Trainspotting: 1}
}
}
};
const nextState = reducer(initialState, action);
expect(nextState).to.equal(fromJS({
vote: {
pair: ['Trainspotting', '28 Days Later'],
tally: {Trainspotting: 1}
}
}));
});
reducer 同样应该可以正确的处理 undefined
初始化状态:
//test/reducer_spec.js
it('handles SET_STATE without initial state', () => {
const action = {
type: 'SET_STATE',
state: {
vote: {
pair: ['Trainspotting', '28 Days Later'],
tally: {Trainspotting: 1}
}
}
};
const nextState = reducer(undefined, action);
expect(nextState).to.equal(fromJS({
vote: {
pair: ['Trainspotting', '28 Days Later'],
tally: {Trainspotting: 1}
}
}));
});
现在我们来看一下如何实现满足上面测试条件的 reducer:
//src/reducer.js
import {Map} from 'immutable';
export default function(state = Map(), action) {
return state;
}
reducer 需要处理 SET_STATE
动作。在这个动作的处理中,我们应该将传入的状态数据和现有的进行合并,
使用 Map 提供的 merge 将很容易来实现这个操作:
//src/reducer.js
import {Map} from 'immutable';
function setState(state, newState) {
return state.merge(newState);
}
export default function(state = Map(), action) {
switch (action.type) {
case 'SET_STATE':
return setState(state, action.state);
}
return state;
}
注意这里我们并没有单独写一个核心模块,而是直接在 reducer 中添加了个简单的 setState
函数来做业务逻辑。
这是因为现在这个逻辑还很简单~
关于改变用户状态的那两个用户交互:投票和下一步,它们都需要和服务端进行通信,我们一会再说。我们现在先把
redux 添加到项目中:
npm install --save redux
index.jsx
入口文件是一个初始化 Store 的好地方,让我们暂时先使用硬编码的数据来做:
//src/index.jsx
import React from 'react';
import Router, {Route, DefaultRoute} from 'react-router';
import {createStore} from 'redux';
import reducer from './reducer';
import App from './components/App';
import Voting from './components/Voting';
import Results from './components/Results';
const store = createStore(reducer);
store.dispatch({
type: 'SET_STATE',
state: {
vote: {
pair: ['Sunshine', '28 Days Later'],
tally: {Sunshine: 2}
}
}
});
const routes = <Route handler={App}>
<Route path="/results" handler={Results} />
<DefaultRoute handler={Voting} />
</Route>;
Router.run(routes, (Root) => {
React.render(
<Root />,
document.getElementById('app')
);
});
那么,我们如何在 react 组件中从 Store 中获取数据呢?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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