让 React 从 Redux 中获取数据
我们已经创建了一个使用不可变数据类型保存应用状态的 Redux Store。我们还拥有接受不可变数据为参数的
无状态的纯 React 组件。如果我们能使这些组件从 Store 中获取最新的状态数据,那真是极好的。当状态变化时,
React 会重新渲染组件,pure render mixin 可以使得我们的 UI 避免不必要的重复渲染。
相比我们自己手动实现同步代码,我们更推荐使用[react-redux] [ https://github.com/rackt/react-redux]包来做 :
npm install --save react-redux
这个库主要做的是:
- 映射 Store 的状态到组件的输入 props 中;
- 映射 actions 到组件的回调 props 中。
为了让它可以正常工作,我们需要将顶层的应用组件嵌套在 react-redux 的 Provider 组件中。
这将把 Redux Store 和我们的状态树连接起来。
我们将让 Provider 包含路由的根组件,这样会使得 Provider 成为整个应用组件的根节点:
//src/index.jsx
import React from 'react';
import Router, {Route, DefaultRoute} from 'react-router';
import {createStore} from 'redux';
import {Provider} from 'react-redux';
import reducer from './reducer';
import App from './components/App';
import {VotingContainer} 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={VotingContainer} />
</Route>;
Router.run(routes, (Root) => {
React.render(
<Provider store={store}>
{() => <Root />}
</Provider>,
document.getElementById('app')
);
});
接下来我们要考虑一下,我们的那些组件需要绑定到 Store 上。我们一共有 5 个组件,可以分成三类:
- 根组件
App
不需要绑定任何数据; Vote
和Winner
组件只使用父组件传递来的数据,所以它们也不需要绑定;- 剩下的组件(
Voting
和Results
)目前都是使用的硬编码数据,我们现在需要将其绑定到 Store 上。
让我们从 Voting
组件开始。使用 react-redux 我们得到一个叫 connect 的函数:
connect(mapStateToProps)(SomeComponent);
该函数的作用就是将 Redux Store 中的状态数据映射到 props 对象中。这个 props 对象将会用于连接到的组件中。
在我们的 Voting
场景中,我们需要从状态中拿到 pair
和 winner
值:
//src/components/Voting.jsx
import React from 'react/addons';
import {connect} from 'react-redux';
import Winner from './Winner';
import Vote from './Vote';
const Voting = React.createClass({
mixins: [React.addons.PureRenderMixin],
render: function() {
return <div>
{this.props.winner ?
<Winner ref="winner" winner={this.props.winner} /> :
<Vote {...this.props} />}
</div>;
}
});
function mapStateToProps(state) {
return {
pair: state.getIn(['vote', 'pair']),
winner: state.get('winner')
};
}
connect(mapStateToProps)(Voting);
export default Voting;
在上面的代码中, connect
函数并没有修改 Voting
组件本身, Voting
组件依然保持这纯粹性。而 connect
返回的是一个 Voting
组件的连接版,我们称之为 VotingContainer
:
//src/components/Voting.jsx
import React from 'react/addons';
import {connect} from 'react-redux';
import Winner from './Winner';
import Vote from './Vote';
export const Voting = React.createClass({
mixins: [React.addons.PureRenderMixin],
render: function() {
return <div>
{this.props.winner ?
<Winner ref="winner" winner={this.props.winner} /> :
<Vote {...this.props} />}
</div>;
}
});
function mapStateToProps(state) {
return {
pair: state.getIn(['vote', 'pair']),
winner: state.get('winner')
};
}
export const VotingContainer = connect(mapStateToProps)(Voting);
这样,这个模块现在导出两个组件:一个纯 Voting
组件,一个连接后的 VotingContainer
版本。
react-redux 官方称前者为“蠢”组件,后者则称为”智能”组件。我更倾向于用“pure”和“connected”来描述它们。
怎么称呼随你便,主要是明白它们之间的差别:
- 纯组件完全靠给它传入的 props 来工作,这非常类似一个纯函数;
- 连接组件则封装了纯组件和一些逻辑用来与 Redux Store 协同工作,这些特性是 redux-react 提供的。
我们得更新一下路由表,改用 VotingContainer
。一旦修改完毕,我们的投票界面将会使用来自 Redux Store 的数据:
//src/index.jsx
import React from 'react';
import Router, {Route, DefaultRoute} from 'react-router';
import {createStore} from 'redux';
import {Provider} from 'react-redux';
import reducer from './reducer';
import App from './components/App';
import {VotingContainer} 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={VotingContainer} />
</Route>;
Router.run(routes, (Root) => {
React.render(
<Provider store={store}>
{() => <Root />}
</Provider>,
document.getElementById('app')
);
});
而在对应的测试代码中,我们则需要使用纯 Voting
组件定义:
//test/components/Voting_spec.jsx
import React from 'react/addons';
import {List} from 'immutable';
import {Voting} from '../../src/components/Voting';
import {expect} from 'chai';
其它地方不需要修改了。
现在我们来如法炮制投票结果页面:
//src/components/Results.jsx
import React from 'react/addons';
import {connect} from 'react-redux';
import Winner from './Winner';
export const Results = React.createClass({
mixins: [React.addons.PureRenderMixin],
getPair: function() {
return this.props.pair || [];
},
getVotes: function(entry) {
if (this.props.tally && this.props.tally.has(entry)) {
return this.props.tally.get(entry);
}
return 0;
},
render: function() {
return this.props.winner ?
<Winner ref="winner" winner={this.props.winner} /> :
<div className="results">
<div className="tally">
{this.getPair().map(entry =>
<div key={entry} className="entry">
<h1>{entry}</h1>
<div className="voteCount">
{this.getVotes(entry)}
</div>
</div>
)}
</div>
<div className="management">
<button ref="next"
className="next"
onClick={this.props.next}>
Next
</button>
</div>
</div>;
}
});
function mapStateToProps(state) {
return {
pair: state.getIn(['vote', 'pair']),
tally: state.getIn(['vote', 'tally']),
winner: state.get('winner')
}
}
export const ResultsContainer = connect(mapStateToProps)(Results);
同样我们需要修改 index.jsx
来使用新的 ResultsContainer
:
//src/index.jsx
import React from 'react';
import Router, {Route, DefaultRoute} from 'react-router';
import {createStore} from 'redux';
import {Provider} from 'react-redux';
import reducer from './reducer';
import App from './components/App';
import {VotingContainer} from './components/Voting';
import {ResultsContainer} 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={ResultsContainer} />
<DefaultRoute handler={VotingContainer} />
</Route>;
Router.run(routes, (Root) => {
React.render(
<Provider store={store}>
{() => <Root />}
</Provider>,
document.getElementById('app')
);
});
不要忘记修改测试代码啊:
//test/components/Results_spec.jsx
import React from 'react/addons';
import {List, Map} from 'immutable';
import {Results} from '../../src/components/Results';
import {expect} from 'chai';
现在你已经知道如何让纯 react 组件与 Redux Store 整合了。
对于一些只有一个根组件且没有路由的小应用,直接连接根组件就足够了。根组件会将状态数据传递给它的子组件。
而对于那些使用路由,就像我们的场景,连接每一个路由指向的处理函数是个好主意。但是分别为每个组件编写连接代码并
不适合所有的软件场景。我觉得保持组件 props 尽可能清晰明了是个非常好的习惯,因为它可以让你很容易清楚组件需要哪些数据,
你就可以更容易管理那些连接代码。
现在让我们开始把 Redux 数据对接到 UI 里,我们再也不需要那些 App.jsx
中手写的硬编码数据了,这样我们的 App.jsx
将会变得简单:
//src/components/App.jsx
import React from 'react';
import {RouteHandler} from 'react-router';
export default React.createClass({
render: function() {
return <RouteHandler />
}
});
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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