首页 男生 其他 深入浅出React和Redux

3.2.4 组件Context

深入浅出React和Redux 程墨 7004 2021-04-06 02:29

  您可以在百度里搜索“深入浅出React和Redux 艾草文学(www.321553.xyz)”查找最新章节!

  

  3.2.4 组件Context

  在介绍react-redux之前,我们重新看一看现在的Counter和Summary组件文件,发现它们都直接导入Redux Store。

  import store from '../Store.js';

  虽然Redux应用全局就一个Store,这样的直接导入依然有问题。

  在实际工作中,一个应用的规模会很大,不会所有的组件都放在一个代码库中,有时候还要通过npm方式引入第三方的组件。想想看,当开发一个独立的组件的时候,都不知道自己这个组件会存在于哪个应用中,当然不可能预先知道定义唯一Redux Store的文件位置了,所以,在组件中直接导入Store是非常不利于组件复用的。

  一个应用中,最好只有一个地方需要直接导入Store,这个位置当然应该是在调用最顶层React组件的位置。在我们的ControlPanel例子中,就是应用的入口文件src/index.js中,其余组件应该避免直接导入Store。

  不让组件直接导入Store,那就只能让组件的上层组件把Store传递下来了。首先想到的当然是用props,毕竟,React组件就是用props来传递父子组件之间的数据的。不过,这种方法有一个很大的缺陷,就是从上到下,所有的组件都要帮助传递这个props。

  设想在一个嵌套多层的组件结构中,只有最里层的组件才需要使用store,但是为了把store从最外层传递到最里层,就要求中间所有的组件都需要增加对这个store prop的支持,即使根本不使用它,这无疑很麻烦。

  还是来看ControlPanel这个例子,最顶层的组件ControlPanel根本就不使用store,如果仅仅为了让它传递一个prop给子组件Counter和Summary就要求它支持state prop,显然非常不合理。所以,用prop传递store不是一个好方法。

  React提供了一个叫Context的功能,能够完美地解决这个问题。如图3-5所示。

  图3-5 React的Context

  所谓Context,就是“上下文环境”,让一个树状组件上所有组件都能访问一个共同的对象,为了完成这个任务,需要上级组件和下级组件配合。

  首先,上级组件要宣称自己支持context,并且提供一个函数来返回代表Context的对象。

  然后,这个上级组件之下的所有子孙组件,只要宣称自己需要这个context,就可以通过this.context访问到这个共同的环境对象。

  我们尝试给ControlPanel程序加上context功能来优化,相关代码在chapter-three/redux_with_countext/目录中,这个应用是对前面redux_smart_dumb的改进,我们这里只关注改变的代码。

  因为Redux应用中只有一个Store,因此所有组件如果要使用Store的话,只能访问这唯一的Store。很自然,希望顶层的组件来扮演这个Context提供者的角色,只要顶层组件提供包含store的context,那就覆盖了整个应用的所有组件,简单而且够用。

  不过,每个应用的顶层组件不同,在我们的ControlPanel例子里顶层组件是ControlPanel,在另一个应用里会有另一个组件。而且,ControlPanel有它自己的职责,我们来没有理由把它复杂化,没必要非要让它扮演context提供者的功能。

  我们来创建一个特殊的React组件,它将是一个通用的context提供者,可以应用在任何一个应用中,我们把这个组件叫做Provider。在src/Provider.js中,首先定义一个名为Provider的React组件,代码如下:

  import {PropTypes, Component} from 'react';

  class Provider extends Component {

  getChildContext() {

  return {

  store: this.props.store

  };

  }

  render() {

  return this.props.children;

  }

  }

  Provider也是一个React组件,不过它的render函数就是简单地把子组件渲染出来,在渲染上,Provider不做任何附加的事情。

  每个React组件的props中都可以一个特殊属性children,代表的是子组件,比如这样的代码,在Provider的render函数中this.props.children就是两个Provider标签之间的

  

  除了把渲染工作完全交给子组件,Provider还要提供一个函数getChildContext,这个函数返回的就是代表Context的对象。我们的Context中只有一个字段store,而且我们也希望Provider足够通用,所以并不在这个文件中导入store,而是要求Provider的使用者通过prop传递进来store。

  为了让Provider能够被React认可为一个Context的提供者,还需要指定Provider的childContextTypes属性,代码如下:

  Provider.childContextTypes = {

  store: PropTypes.object

  };

  Provider还需要定义类的childContextTypes,必须和getChildContext对应,只有这两者都齐备,Provider的子组件才有可能访问到context。

  有了Provider,我们就可以改进一下应用的入口src/index.js文件了,代码如下:

  import store from './Store.js';

  import Provider from './Provider.js';

  ReactDOM.render(

  

  ,

  document.getElementById('root')

  );

  在前面所有的例子中,React.render的第一个参数就是顶层组件ControlPanel。现在,这个ControlPanel作为子组件被Provider包住了,Provider成为了顶层组件。当然,如同我们上面看到的,Provider只是把渲染工作完全交给子组件,它扮演的角色只是提供Context,包住了最顶层的ControlPanel,也就让context覆盖了整个应用中所有组件。

  至此,我们完成了提供Context的工作,接下来我们看底层组件如何使用Context。

  我们可以顺便看一眼src/views/ControlPanel.js,这个文件和前面的例子没有任何变化,它做的工作只是搭建应用框架,把子组件Counter和Summary渲染出来,和Store一点关系都没有,这个文件既没有导入Store,也没有支持关于store的props。

  在src/views/Counter.js中,我们可以看到对context的使用。作为傻瓜组件的Counter是一个无状态组件,它也不需要和Store牵扯什么关系,和之前的代码一模一样,有变化的是CounterContainer部分。

  为了让CounterContainer能够访问到context,必须给CounterContainer类的context-Types赋值和Provider.childContextTypes一样的值,两者必须一致,不然就无法访问到context,代码如下:

  CounterContainer.contextTypes = {

  store: PropTypes.object

  }

  在CounterContainer中,所有对store的访问,都是通过this.context.store完成,因为this.context就是Provider提供的context对象,所以getOwnState函数代码如下:

  getOwnState() {

  return {

  value: this.context.store.getState()[this.props.caption]

  };

  }

  还有一点,因为我们自己定义了构造函数,所以要用上第二个参数context,代码如下:

  constructor(props, context) {

  super(props, context);

  ...

  }

  在调用super的时候,一定要带上context参数,这样才能让React组件初始化实例中的context,不然组件的其他部分就无法使用this.context。

  要求constructor显示声明props和context两个参数然后又传递给super看起来很麻烦,我们的代码似乎只是一个参数的搬运工,而且将来可能有新的参数出现那样又要修改这部分代码,如果你这样认为的话,可以用下面的方法一劳永逸地解决这个问题,代码如下:

  constructor() {

  super(...arguments);

  }

  我们不能直接使用arguments,因为在JavaScript中arguments表现得像是一个数组而不是分开的一个个参数,但是我们通过扩展标示符就能把arguments彻底变成传递给super的参数。

  在结束之前,让我们重新审视一下Context这个功能,Context这个功能相当于提供了一个全局可以访问的对象,但是全局对象或者说全局变量肯定是我们应该避免的用法,只要有一个地方改变了全局对象的值,应用中其他部分就会受影响,那样整个程序的运行结果就完全不可预期了。

  所以,单纯来看React的这个Context功能的话,必须强调这个功能要谨慎使用,只有对那些每个组件都可能使用,但是中间组件又可能不使用的对象才有必要使用Context,千万不要滥用。

  对于Redux,因为Redux的Store封装得很好,没有提供直接修改状态的功能,就是说一个组件虽然能够访问全局唯一的Store,却不可能直接修改Store中的状态,这样部分克服了作为全局对象的缺点。而且,一个应用只有一个Store,这个Store是Context里唯一需要的东西,并不算滥用,所以,使用Context来传递Store是一个不错的选择。 深入浅出React和Redux

目录
设置
手机
书架
书页
评论