Context - React跨组件访问数据的利器

时间: 2018-11-08阅读: 215标签: react

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


使用Context的场合

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

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

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 (
    <div>
      <ThemedButton theme={props.theme}></ThemedButton>
    </div>
  );
}

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

使用 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 (
      <ThemeContext.Provider value="dark">
        <Toolbar></Toolbar>
      </ThemeContext.Provider>
    );
  }
}

// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

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 <Button theme={this.context} />;
  }
}

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


Context相关API

React.createContext

const MyContext = React.createContext(defaultValue);

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

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


Context.Provider

<MyContext.Provider value={/* some value */}>

每个 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值,参加Consuming Multiple Contexts.

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

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


Context.Consumer

<MyContext.Consumer>
  {value => /* render something based on the context value */}
</MyContext.Consumer>

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

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

注意

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


栗子

在嵌套组件中更新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 (
    <ThemeContext.Consumer>
      {({theme, toggleTheme}) => (
        <button
          onClick={toggleTheme}
          style={{backgroundColor: theme.background}}>
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

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 (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}

function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}

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


使用多个Contexts

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

// Theme context, default to light theme
const ThemeContext = React.createContext('light');

// Signed-in user context
const UserContext = React.createContext({
  name: 'Guest',
});

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // App component that provides initial context values
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

function Layout() {
  return (
    <div>
      <Sidebar />
      <Content />
    </div>
  );
}

// A component may consume multiple contexts
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

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

注意

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

class App extends React.Component {
  render() {
    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>
    );
  }
}


来源:https://segmentfault.com/a/1190000016941968

在react jsx中,为什么使用箭头函数和bind容易出现问题

因为()=>this.deleteUser(user.id)每执行一次就会生成一个新的函数,当然bind也是这样干的,所以在PureComponent的shallowCompare中认为onDeleteClick的值已经被修改,所以触发了重新渲染。看吧,使用箭头函数和bind会造成性能浪费,作为一个节约的程序员应该避免如此。

React-redux中connect的装饰器用法@connect

最近在琢磨react中的一些小技巧,这篇文章记录一下在redux中用装饰器来写connect。通常我们需要一个reducer和一个action,然后使用connect来包裹你的Component。

深入解析React中的元素、组件、实例和节点

eact 中的元素、组件、实例和节点,是React中关系密切的4个概念,也是很容易让React 初学者迷惑的4个概念。现在,我就来详细地介绍这4个概念,以及它们之间的联系和区别,满足喜欢咬文嚼字、刨根问底的同学的好奇心。

React中富文本编辑器的技术选型调研

富文本编辑器是项目中不可或缺的部分,目前市面上可以选择的富文本编辑器种类繁多,如何在项目中选择一款集轻量,美观,稳定,坑少,满足需求的富文本编辑器变成了团队中一个重要的问题。我对这两款富文本编辑器都进行了使用,并结合目前的项目需求进行了比较:react-quill、braft-editor

React 服务端渲染方案完美的解决方案

最近在开发一个服务端渲染工具,通过一篇小文大致介绍下服务端渲染,和服务端渲染的方式方法。在此文后面有两中服务端渲染方式的构思,根据你对服务端渲染的利弊权衡,你会选择哪一种服务端渲染方式呢?

为什么我会选择React+Next.js,而不是Vue或Angular?

本文的目的不是要对 React、Vue 和 Angular 三者进行比较,已经有许多人对这个话题进行了比较深入的探讨。每个人都有自己的偏好。与其他库和框架相比,我更喜欢使用 React 构建用户界面。 对我来说,这是构建用户界面唯一正确的方法,它让我爱上了 React。

React 项目结构和组件命名之道

React 作为一个库,不会决定你如何组织项目的结构。这是件好事,因为这样我们有了充分的自由去尝试不同的组织方式并且选取最适合我们的方式。但是从另一个角度讲,这可能会让刚刚上手 React 的开发者产生些许困惑。

react-router 路由切换动画

因为项目的需求,需要在路由切换的时候,加入一些比较 zb 的视觉效果,所以研究了一下。把这些学习的过程记录下来,以便以后回顾。同时也希望这些内容能够帮助一些跟我一样的菜鸟,让他们少走些坑。可能我对代码的表述不是很到位,希望大家不要介意。机智的你们一定可以看明白。

精通React今年最劲爆的新特性——React Hooks

你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗?你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗?你在还在为组件中的this指向而晕头转向吗?这样看来,说React Hooks是今年最劲爆的新特性真的毫不夸张。

grpc-web与react的集成

使用create-react-app脚手架生成react相关部分,脚手架内部会通过node自动起一个客户端,然后和普通的ajax请求一样,和远端服务器进行通信,只不过这里采用支持rpc通信的grpc-web来发起请求,远端采用docker容器的node服务器,node服务器端使用envoy作为代理

fly63.com版权所有,内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权,请与小编联系!情况属实本人将予以删除!

广告赞助文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯