博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
1. Context - React跨组件访问数据的利器
阅读量:6291 次
发布时间:2019-06-22

本文共 6487 字,大约阅读时间需要 21 分钟。

《react-router-dom源码揭秘》系列

Context提供了一种跨组件访问数据的方法。它无需在组件树间逐层传递属性,也可以方便的访问其他组件的数据

在经典的React应用中,数据是父组件通过props向子组件传递的。但是在某些特定场合,有些数据需要在各个组件之间共享。Context为我们提供一种组件之间共享数据的方式,可以避免数据在组件树上逐层传递

使用Context的场合

Context可以在组件树的组件之间共享“全局”数据。例如:登陆的用户信息,用户选择的主题、语言等等。下面的例子中,我们“手动”自上而下传递theme属性,用来设定Button的样式。

class App extends React.Component {  render() {    return 
; }}function Toolbar(props) { // The Toolbar component must take an extra "theme" prop // and pass it to the ThemedButton. This can become painful // if every single button in the app needs to know the theme // because it would have to be passed through all components. return (
);}class ThemedButton extends React.Component { render() { return ; }}复制代码

使用Context,我们可以避免通过多个中间组件传递props

// Context lets us pass a value deep into the component tree// without explicitly threading it through every component.// Create a context for the current theme (with "light" as the default).const ThemeContext = React.createContext('light');class App extends React.Component {  render() {    // Use a Provider to pass the current theme to the tree below.    // Any component can read it, no matter how deep it is.    // In this example, we're passing "dark" as the current value.    return (      
); }}// A component in the middle doesn't have to// pass the theme down explicitly anymore.function Toolbar(props) { return (
);}class ThemedButton extends React.Component { // Assign a contextType to read the current theme context. // React will find the closest theme Provider above and use its value. // In this example, the current theme is "dark". static contextType = ThemeContext; render() { return ; }}复制代码

有时候,有些数据需要被很多组件访问,而且这些组件在组件树的不同层上。Context可以使我们以“广播”的形式,在各个组件中共享数据的改变

Context相关API

React.createContext

const MyContext = React.createContext(defaultValue);复制代码

创建一个新的Context对象。当React渲染一个组件,且该组件注册了Context时,它将读取父组件中,距离该组件最近的Provider组件的Context

defaultValue只有在“Consumer”组件找不到Provider组件时,才会被使用。

Context.Provider

复制代码

每个Context对象都携带一个名叫Provider的React组件。Provider可以使得“Consumer”组件监听context的变更

通过向Provider的后代Consumer组件传递value的prop,一个Provider可以与多个Consumer组件建立联系。

所有的后代Consumer组件在Provider的value属性更新后,都会被重新渲染。这个更新从Provider到其后代Consumer组件之间传播,但是并不会触发shouldComponentUpdate方法。所以即使Consumer组件的祖先组件没有更新,Consumer组件也会更新

Context使用与Object.is相同的算法来对比value的新、旧值,以判定其value是否被更新了

注意

当向value传递对象时,这种判定value是否改变的方式可能会引起问题。请参加.

Class.contextType

class MyClass extends React.Component {  componentDidMount() {    let value = this.context;    /* perform a side-effect at mount using the value of MyContext */  }  componentDidUpdate() {    let value = this.context;    /* ... */  }  componentWillUnmount() {    let value = this.context;    /* ... */  }  render() {    let value = this.context;    /* render something based on the value of MyContext */  }}MyClass.contextType = MyContext;复制代码

为class的contextTpe属性赋值一个Context对象后,我们可以通过this.context在组件的各个声明周期函数中获取到当前的Context对象的方法

注意:

通过这种方式,每个组件只能注册一个context对象。如果需要读取多个context的value值,参加.

如果编码中使用了ES实验中的语法,那么可以使用类的静态(static)成员来初始化contextTYpe.代码如下:

class MyClass extends React.Component { static contextType = MyContext; render() {   let value = this.context;   /* render something based on the value */ }}复制代码

Context.Consumer

{value => /* render something based on the context value */}
复制代码

Consumer是一个监听context变化的React组件。它使得我们可以在一个函数组件中,监听contxt的改变。

Consumer组件要求其子元素为一个函数。该函数的参数接收当前的context的value值,要求返回一个React节点(node) 传递给该函数的参数value等于距离此Consumner最近的外层Provider组件的context值。如果没有外层的Provider组件,则等于调用createContext()时传递的参数值(context的默认值)。

注意

更多关于“子元素为一个函数”的信息,请参加

栗子

在嵌套组件中更新Context

开发中,我们经常需要在某些嵌套结构很深的组件上更新context的value值。此时,我们可以向下传递一个函数,用它来更新context的value。代码如下:

theme-context.js

// Make sure the shape of the default value passed to// createContext matches the shape that the consumers expect!export const ThemeContext = React.createContext({  theme: themes.dark,  toggleTheme: () => {},});复制代码

theme-toggler-button.js

import {ThemeContext} from './theme-context';function ThemeTogglerButton() {  // The Theme Toggler Button receives not only the theme  // but also a toggleTheme function from the context  return (    
{({theme, toggleTheme}) => (
)}
);}export default ThemeTogglerButton;复制代码

app.js

import {ThemeContext, themes} from './theme-context';import ThemeTogglerButton from './theme-toggler-button';class App extends React.Component {  constructor(props) {    super(props);    this.toggleTheme = () => {      this.setState(state => ({        theme:          state.theme === themes.dark            ? themes.light            : themes.dark,      }));    };    // State also contains the updater function so it will    // be passed down into the context provider    this.state = {      theme: themes.light,      toggleTheme: this.toggleTheme,    };  }  render() {    // The entire state is passed to the provider    return (      
); }}function Content() { return (
);}ReactDOM.render(
, document.root);复制代码

使用多个Contexts

为了保持React的快速渲染,我们需要将每个consumer组件编写成一个独立的组件节点(node)

// Theme context, default to light themeconst ThemeContext = React.createContext('light');// Signed-in user contextconst UserContext = React.createContext({  name: 'Guest',});class App extends React.Component {  render() {    const {signedInUser, theme} = this.props;    // App component that provides initial context values    return (      
); }}function Layout() { return (
);}// A component may consume multiple contextsfunction Content() { return (
{theme => (
{user => (
)}
)}
);}复制代码

如果有两个以上的context经常一起使用,我们需要考虑创建一个render prop component一并提供两个Context

注意

因为context使用引用标示符(reference identity)来判断何时需要重新渲染,所以有些情况下,当provider的父元素重新渲染时,会触发consumer的非内部渲染。例如下面代码,在每次Provider重新渲染时,会重新渲染所有的consumer组件。因为会一直创建一个新的对象赋值给value(value一直在变)

class App extends React.Component {  render() {    return (      
); }}复制代码

为了避免这个问题,可以将value放在组件的state中

class App extends React.Component {  constructor(props) {    super(props);    this.state = {      value: {
something: 'something'}, }; } render() { return (
); }}复制代码

转载地址:http://sqkta.baihongyu.com/

你可能感兴趣的文章
virtualbox 笔记
查看>>
Git 常用命令
查看>>
驰骋工作流引擎三种项目集成开发模式
查看>>
SUSE11修改主机名方法
查看>>
jdk6.0 + Tomcat6.0的简单jsp,Servlet,javabean的调试
查看>>
Android:apk签名
查看>>
2(2).选择排序_冒泡(双向循环链表)
查看>>
MySQL 索引 BST树、B树、B+树、B*树
查看>>
微信支付
查看>>
CodeBlocks中的OpenGL
查看>>
短址(short URL)
查看>>
第十三章 RememberMe——《跟我学Shiro》
查看>>
mysql 时间函数 时间戳转为日期
查看>>
索引失效 ORA-01502
查看>>
Oracle取月份,不带前面的0
查看>>
Linux Network Device Name issue
查看>>
IP地址的划分实例解答
查看>>
如何查看Linux命令源码
查看>>
运维基础命令
查看>>
入门到进阶React
查看>>