返回介绍

让 React 从 Redux 中获取数据

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

我们已经创建了一个使用不可变数据类型保存应用状态的 Redux Store。我们还拥有接受不可变数据为参数的
无状态的纯 React 组件。如果我们能使这些组件从 Store 中获取最新的状态数据,那真是极好的。当状态变化时,
React 会重新渲染组件,pure render mixin 可以使得我们的 UI 避免不必要的重复渲染。

相比我们自己手动实现同步代码,我们更推荐使用[react-redux] [ https://github.com/rackt/react-redux]包来做

npm install --save react-redux

这个库主要做的是:

  1. 映射 Store 的状态到组件的输入 props 中;
  2. 映射 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 不需要绑定任何数据;
  • VoteWinner 组件只使用父组件传递来的数据,所以它们也不需要绑定;
  • 剩下的组件( VotingResults )目前都是使用的硬编码数据,我们现在需要将其绑定到 Store 上。

让我们从 Voting 组件开始。使用 react-redux 我们得到一个叫 connect 的函数:

connect(mapStateToProps)(SomeComponent);

该函数的作用就是将 Redux Store 中的状态数据映射到 props 对象中。这个 props 对象将会用于连接到的组件中。
在我们的 Voting 场景中,我们需要从状态中拿到 pairwinner 值:

//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 技术交流群。

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

发布评论

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