阅读 252

从编程中的context来说react - context(16.x

参考资料:

  1. 编程中什么是「Context(上下文)」?.
  2. react - context官方文档.
  3. React Context(上下文) 作用和使用 看完不懂 你打我.
  4. React - Context 源码 github.

编程上的context

what is context ?

在这里插入图片描述

这是牛津词典里有关 context 的解释。通常我们在编程当中会把 context 叫做 上下文,不过我认为这里似乎叫做 语境更合适一些。但是不管是叫做 上下文或是 语境,都还是有些抽象。

从语文的角度理解

在学生时代,语文试卷中都会有个阅读理解部分,通常会有个问题类似于“结合上下文,x x x x x x x”,例如:

..... 林冲大叫一声“啊也!” ..... 问:这句话林冲的“啊也”表达了林冲怎样的心里? 答:啊你妈个头啊!

看,一篇文章,给你摘录一段,没前没后,你读不懂。因为有 语境,就是语言环境存在,一段话说了什么,要通过上下文(文章的上下文)来推断。

从编程的角度理解

那么从编程的角度又该如何理解呢?通常我们写代码的时候,除了一些简单的函数不需要外部变量,其他的复杂些的函数大多会依赖一些外部变量。而一旦需要外部变量,那么这段代码就不是完整的,不是能够独立运行的,而这里这些程序运行所必需的外部变量(也可以理解为前置条件),就叫做 上下文

react - context

首先,react 在 16.3 引入了context,经过不过的迭代,到现在16.8.6的版本中, 官方对context的定义为 --- Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。归类于高级指引部分,属于react的高级api。

何时使用context

通常在我们写react组件的时候,数据传递一般采用单相数据流的方式,这样可以使数据流向变得的简单而清晰。但在某些使用场景中需要共享一些对于一个组件树而言是**“全局”**的数据,这时就需要在每一个层级手动修改传输的props,过于麻烦。 例如官方文档中提供的demo:

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  // Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。
  // 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事,
  // 因为必须将这个值层层传递所有组件。
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />;
  }
}
复制代码

在上述代码中,修改Button的theme,那么需要从App中修改theme,传给Toolbar,并由Toolbar传给ThemedButton使用,如此层层传递。如果ThemedButton与App之前还存在更多的嵌套关系,那么必需将theme在中间的各层级间层层传递,显然,这样过于繁琐了。

使用 context, 我们可以避免通过中间元素传递 props

// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
    // 无论多深,任何组件都能读取这个值。
    // 在这个例子中,我们将 “dark” 作为当前的值传递下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 读取当前的 theme context。
  // React 会往上找到最近的 theme Provider,然后使用它的值。
  // 在这个例子中,当前的 theme 值为 “dark”。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}
复制代码

这是16.8.6版本中的文档demo(本文不对16.x之前的context使用方式做探讨,有兴趣同学可自行研究)

API

  1. 首先使用createContext方法创建一个Context对象。包含ProviderConsumer两个组件。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。
const ThemeContext = React.createContext('light');
复制代码
  1. 使用**Provider(生产者)**组件来传递theme,圈定组件树范围,无论其内嵌套多深,任何组件都能读取theme。每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。
class App extends React.Component {
  render() {
    // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
    // 无论多深,任何组件都能读取这个值。
    // 在这个例子中,我们将 “dark” 作为当前的值传递下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}
复制代码
  1. 当子孙组件需要使用theme时,指定contextType使用当前的Context
class ThemedButton extends React.Component {
  // 指定 contextType 读取当前的 theme context。
  // React 会往上找到最近的 theme Provider,然后使用它的值。
  // 在这个例子中,当前的 theme 值为 “dark”。
  static contextType = ThemeContext;
  render() {
  // 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。
    return <Button theme={this.context} />;
  }
}
复制代码

4.中间无需使用theme的组件,不需要层层传递props

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}
复制代码
  1. 3中的代码也可使用Consumer(消费者), 他是专门消费Provider(生产者) 产生的数据。
 class ThemedButton extends React.Component {
  // 指定 contextType 读取当前的 theme context。
  // React 会往上找到最近的 theme Provider,然后使用它的值。
  // 在这个例子中,当前的 theme 值为 “dark”。
  static contextType = ThemeContext;
  render() {
    return (
    	<ThemeContext.Consumer>
   	         //Consumer容器,可以拿到上文传递下来的 theme 属性,并可以展示对应的值
      		{(theme) =>
      			<Button theme={theme} />
            }
        </ThemeContext.Consumer>
    );
  }
}
复制代码

注意事项

1.因为每次Context值变更时,Consumer都会接受到相应的变化通知,这里可能会有一些陷阱,当 Provider 的父组件进行重渲染时,可能会在 Consumers 组件中触发意外的渲染。例如

class App extends React.Component {
  render() {
  //当每一次 Provider 重渲染时(即每次render时{something: 'something'}都指向一个新对象,拓展1),以下的代码会重渲染所有下面的 consumers 组件,因为 value 属性总是被赋值为新的对象
    return (
      <Provider value={{something: 'something'}}>
        <Toolbar />
      </Provider>
    );
  }
}
复制代码

为了防止这种情况,将 value 状态提升到父节点的 state 里:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: {something: 'something'},
    };
  }

  render() {
    return (
      <Provider value={this.state.value}>
        <Toolbar />
      </Provider>
    );
  }
}
复制代码

2.组件的复用性降低

拓展

1.引用类型 2.mobx-react / react-router / react-redux 均适用context,有兴趣可自行探究。

文章分类
前端
文章标签