React中Context

何时使用context

假设有层级
A->B->C->D,如果A需要传递一个参数给D,根据前文的知识,只能通过props参数一层一层传递。

介绍

Contexts
是React的一个重要属性,但是到目前为止,这个属性在正式的文档里面还没有对它进行正式介绍,在
reactv0.1.4将会正式发布这个属性。下面先来介绍一下它的使用方式。
Contexts 可以把父组件的的数据,方法传给子组件使用.
而不用每一次都使用给子组件赋值属性的方式,传递方式.

原文地址: React
context


在之前react的工程项目中,关于数据流动以及父子组件中数据通信大都是通过react-redux、redux来完成,虽然可以解决问题,但是这种数据管理是较为复杂的,在最新的react16.3中推出了Context
API,降低了开发的复杂度。
下面通过代码来进行详细的分析
首先创建一个context的实例

在一个经典的 React
应用中,组件之间通信是常用到的技术方案。在父子组件之间通常通过 props
来传递参数,而非父子组件就比较麻烦了,要么就一级一级通过 props
传递,要么就使用 Redux or Mobx
这类状态管理的状态管理库,但是这样无疑增加了应用的复杂度。在 FEers
的期盼中,React 团队终于从 16.3.0 版本开始新增了一个新的 API
Context,福音啊。好了,今天我就来一起学习一下这个新的 Context

Context
设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。

有没有更便捷的方法呢?React提供了context机制来解决这个问题。
context涉及到父级(发送方)和子级(接收方),父级设置context,子级读取context。

React.withContext

会执行一个指定的上下文信息的回调函数,任何在这个回调函数里面渲染的组件都有这个context的访问权限。

var A = React.createClass({
    contextTypes: {
        name: React.PropTypes.string.isRequired,
    },
    render: function() {
        return < div > My name is: {
            this.context.name
        } < /div>;
    }
});
React.withContext({'name': 'Jonas'}, function () {
    // Outputs: "My name is: Jonas"
React.render( < A / >, document.body);
    });

任何想访问context里面的属性的组件都必须显式的指定一个contextTypes
的属性。如果没有指定改属性,那么组件通过 this.context
访问属性将会出错。
如果你为一个组件指定了context,那么这个组件的子组件只要定义了contextTypes
属性,就可以访问到父组件指定的context了。

var A = React.createClass({
    render: function() {
        return < B / >;
    }
});
var B = React.createClass({
    contextTypes: {
        name: React.PropTypes.string
    },
    render: function() {
        return < div > My name is: {
            this.context.name
        } < /div>; }});React.withContext({'name': 'Jonas'}, function () { React.render(<A / > ,
        document.body);
    });

为了减少文件的引用,你可以为contextTypes放到一个mixin 中,这样
用到的组件引用这个 mixin 就行了。

var ContextMixin = {
    contextTypes: {
        name: React.PropTypes.string.isRequired
    },
    getName: function() {
        return this.context.name;
    }
};
var A = React.createClass({
    mixins: [ContextMixin],
    render: function() {
        return < div > My name is {
            this.getName()
        } < /div>; }});React.withContext({'name': 'Jonas'}, function () { / / Outputs: "My name is: Jonas"React.render( < A / >, document.body);
    });
import React from 'react';
import { render } from "react-dom";
const GlobalContext = React.createContext('dark');

什么时候使用 Contsxt

Context 目的是为了共享可以被认为是 React
组件“全局”树的数据。例如当前应用的主题、首选语言等等。接下来看看通过
propsContext 两种方式实现按钮组件样式参数传递方式的对比:

  • props

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}
Toolbar(props) {
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}
ThemedButton(props) {
  return <Button theme={props.theme} />;
}
  • Context

// 创建 context 实例
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}
Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

ThemedButton(props) {
  return (
    <ThemeContext.Consumer>
      {theme => <Button {...props} theme={theme} />}
    </ThemeContext.Consumer>
  );
}
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。// 为当前的 theme 创建一个 context。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} /; }}

我们先来看一下父级的一个实现:

getChildContext

和访问context 的属性是需要通过 contextTypes指定可访问的
元素一样。getChildContext指定的传递给子组件的属性需要先通过
childContextTypes指定,不然会产生错误。

// This code *does NOT work* becasue of a missing property from childContextTypes
var A = React.createClass({

    childContextTypes: {
        // fruit is not specified, and so it will not be sent to the children of A
        name: React.PropTypes.string.isRequired
    },

    getChildContext: function() {
        return {
            name: "Jonas",
            fruit: "Banana"
        };
    },

    render: function() {
        return < B / >;
    }
});

var B = React.createClass({

    contextTypes: {
        fruit: React.PropTypes.string.isRequired
    },

    render: function() {
        return < div > My favorite fruit is: {
            this.context.fruit
        } < /div>;
    }
});


/ / Errors: Invariant Violation: A.getChildContext() : key "fruit"is not defined in childContextTypes.React.render( < A / >, document.body);

假设你的应用程序有多层的context。通过withContext和
getChildContext指定的context元素都可以被子组件引用。但是子组件是需要通过
contextTypes来指定所需要的context 元素的。

var A = React.createClass({
    childContextTypes: {
        fruit: React.PropTypes.string.isRequired
    },
    getChildContext: function() {
        return {
            fruit: "Banana"
        };
    },
    render: function() {
        return < B / >;
    }
});
var B = React.createClass({
    contextTypes: {
        name: React.PropTypes.string.isRequired,
        fruit: React.PropTypes.string.isRequired
    },
    render: function() {
        return < div > My name is: {
            this.context.name
        }
        and my favorite fruit is: {
            this.context.fruit
        } < /div>; }});React.withContext({'name': 'Jonas'}, function () { / / Outputs: "My name is: Jonas and my favorite fruit is: Banana"React.render( < A / >, document.body);
    });

context 是就近引用的,如果你通过withContext指定了context元素,然后又通过
getChildContext指定了该元素,该元素的值将会被覆盖。

var A = React.createClass({
    childContextTypes: {
        name: React.PropTypes.string.isRequired
    },
    getChildContext: function() {
        return {
            name: "Sally"
        };
    },
    render: function() {
        return < B / >;
    }
});
var B = React.createClass({
    contextTypes: {
        name: React.PropTypes.string.isRequired
    },
    render: function() {
        return < div > My name is: {
            this.context.name
        } < /div>; }});React.withContext({'name': 'Jonas'}, function () { / / Outputs: "My name is: Sally"React.render( < A / >, document.body);
    });

生成的context对象具有两个组件类对象

API

Context
主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。

class MessageList extends React.Component {
  getChildContext() {
    return {color: "purple"};
  } // 此具返回childContext具体值

  render() {
    const children = this.props.messages.map((message) =>
      <Message text={message.text} />
    );
    return <div>{children}</div>;
  }
}

MessageList.childContextTypes = {  // 通过childContextTypes定义context中变量类型
  color: PropTypes.string
};

总结

通过context传递属性的方式可以大量减少 通过显式的通过
props逐层传递属性的方式。这样可以减少组件之间的直接依赖关系。

{
  Provider: React.ComponentType<{value: T}>,
  Consumer: React.ComponentType<{children: (value: T)=> React.ReactNode}>
}

React.createContext

创建一个 ContextReact.createContext 提供了 {Provider,Comsumer}
两个方法,上面的代码也可以这个来写:

const {Provider,Comsumer} = React.createContext('light');

class App extends React.Component {
  render() {
    return (
      <Provider value="dark">
        {/* ... */}
      </Provider>
    );
  }
}
{/* ... */}
ThemedButton(props) {
  return (
    <Consumer>
      {/* ... */}
    </Consumer>
  );
}

APIReact.createContext

父级通过childContextType来定义context中的类型,以及通过getChildContext来返回context的具体value。

接下来创建Provider对象,该对象类似react-redux中的Provide对象

Provider

这里的 Provider 类似 react-redux 中的 Provider
组件,用来注入全局的 data (允许 Consumer 订阅 Context
的变化)。一个 Provider 可以连接到多个 Consumer

创建一个 Context 对象。当 React 渲染一个订阅了这个 Context
对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider
中读取到当前的 context 值。

再来看一下子级如何获取父级设置的context:

class GlobalContextProvider extends React.Component {
  // 类似redux中的初始化状态
  state = {
    theme: 'dark'
  };

  // 类似reducer
  handleContextChange = action => {
    switch (action.type) {
      case "UPDATE_THEME":
        return this.setState({
          theme: action.theme
        });
      default:
        return;
    }
  };

  render() {
    return (
      <GlobalContext.Provider
        value={{
          dispatch: this.handleContextChange,
          theme: this.state.theme
        }}
      >
        {this.props.children}
      </GlobalContext.Provider>
    );
  }
}

Consumer

Consumer 组件,表示要消费 Provider 传递的数据(订阅 Context
的响应组件)。当 Provider 发生变化的时候,所有的 Consumer 都会被
re-rendered

只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue
参数才会生效。这有助于在不使用 Provider
包装组件的情况下对组件进行测试。注意:将 undefined 传递给 Provider
时,消费组件的 defaultValue 不会生效。

class Button extends React.Component {
  render() {
    return (
      <button style={{background: this.context.color}}> // 使用context
        {this.props.children}
      </button>
    );
  }
}

Button.contextTypes = { // 定义接收context
  color: PropTypes.string
};

接下来定义一个组件来改变state

结束语

Context 的引入,一定程度上可以减少不少项目对 redux
全家桶的依赖,从而降低了项目的复杂程度,何乐而不为呢~~


原文更多文章
传送门>>

const MyContext = React.createContext(defaultValue);

子级通过contextTypes来标识自己接收context,然后就能直接使用context了。
如果子级没有定义contextTypes,则context对象将为空。另外,如果定义了contextTypes,以下回调中都会添加一个新参数context参数:

const SubComponent = props => (
  <div>
    {/* 类似action,触发后改变状态 */}
    <button
      onClick={() =>
        props.dispatch({
          type: "UPDATE_THEME",
          theme: "light"
        })
      }
    >
      change theme
    </button>
    <div>{props.theme}</div>
  </div>
);

Context.Provider

constructor(props, context)
componentWillReceiveProps(nextProps, nextContext)
shouldComponentUpdate(nextProps, nextState, nextContext)
componentWillUpdate(nextProps, nextState, nextContext)
componentDidUpdate(prevProps, prevState, prevContext)

最后利用到上述提到的Consumer对象加载状态并挂载到dom节点上

每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅
context 的变化。Provider 接收一个 value 属性,传递给消费组件。一个
Provider 可以和多个消费组件有对应关系。多个 Provider
也可以嵌套使用,里层的会覆盖外层的数据。

最后,函数式组件中,通过定义contextType也能使用context:

class App extends React.Component {
  render() {
    return (
      <GlobalContextProvider>
        <GlobalContext.Consumer>
          {context => (
            <SubComponent
              theme={context.theme}
              dispatch={context.dispatch}
            />
          )}
        </GlobalContext.Consumer>
      </GlobalContextProvider>
    );
  }
}

render(<App />, document.getElementById("root"));

当 Provider 的 value
值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部
consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer
组件在其祖先组件退出更新的情况下也能更新。

const PropTypes = require('prop-types');

const Button = ({children}, context) =>
  <button style={{background: context.color}}>
    {children}
  </button>;

Button.contextTypes = {color: PropTypes.string};

那么是不是就是可以利用新的API来代替redux呢?答案当然是否定的
我们可以看到上述的使用Context的方式与redux很类似,因此如果很复杂的应用这样的写法无异于使代码复杂混乱,因此可以这样进行选择:

通过新旧值检测来确定变化,使用了与 Object.is 相同的算法。

因为函数式组件不支持state、ref,一般不建议使用函数式组件。

  1. 注入式的组件,类似背景、语言这种控制全局的变量,可以选择这种
  2. 对于那些复杂的数据交互,父子组件通信还是选择redux
MyContext.Provider value={/* 某个值 */}

想学计算机技术吗?需要1对1专业级导师指导吗?想要团队陪你一起进步吗?欢迎加我为好友!微信号:iTekka。

参考文章

Context.Consumer

  1. React’s new Context
    API

  2. 从新的 Context API 看 React
    应用设计模式

这需要函数作为子元素这种做法。这个函数接收当前的 context 值,返回一个
React 节点。传递给函数的 value 值等同于往上组件树离这个 context 最近的
Provider 提供的 value 值。如果没有对应的 Provider,value
参数等同于传递给 createContext() 的 defaultValue。

MyContext.Consumer {value = /* 基于 context 值进行渲染*/}/MyContext.Consumer

发表评论

电子邮件地址不会被公开。 必填项已用*标注