React学习笔记-Context篇

2,203 阅读4分钟

概述

React Context 是React官方团队为了react中跨组件共享数据而提供的一个特性。

官方是这样描述Context的:

In a typical React application, data is passed top-down (parent to child) via props, but this can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree.

在我们不想一层一层的使用Props来传递数据的时候,我们可以使用Context来完成相同的工作。

下面我们围绕以下问题说说 react 中 Context使用方法:

  1. Context中包含哪些内容?
  2. 如何创建Context?
  3. 如何使用Context传递数据?
  4. 多个Context如何混合使用?
  5. 如何使用动态的Context?
  6. 如何在嵌套的组件中修改Context数据?

Context API

Context相关的API包含以下几种:

  1. React.createContext
  2. Context.Provider
  3. Context.Consumer
  4. class.contextType

其中,React.createContext可以创建Context对象,每个Context对象中包含ProviderConsumer组件。Provider组件允许包裹的组件订阅该Context对象的变化,Consumer组件允许其包裹的组件消费Context对象中的数据。通过将创建好的Context对象挂载到class.contextType中,我们可以在组件内部通过this.context消费该Context对象上的数据。 我们可以使用public class fields中的static创建contentType

const SomeContext = React.createContext(null);
class SomeCom extends React.Component {
    static contentType = SomeContext;
    render() {
        let value = this.context;//value为SomeContext中的数据
        ...
    }
}

如何创建Context?

我们可以通过的React.createContext来创建Context。

const context1 = React.createContext(null);//无默认值
const context2 = React.createContext('xxx')//指定默认值

如何使用Context传递数据?

创建ThemeContext:

import React from "react"
export const ThemeContext = React.createContext('light');

在上层组件中使用Context.Provider订阅数据:

import { ThemeContext } from "./context/theme-context";//加载context
class App extends React.Component {
    static contentType = ThemeContext
    render() {
        return (
            <ThemeContext.Provider>
                <Box />
            </ThemeContext.Provider>
        )
    }
}

在下层组件中使用contextType或者Context.Consumer消费数据:

class Box extends React.Component {
  static contextType = ThemeContext;
  render() {
    let theme = this.context;
    let style = {
      width: 100,
      height: 100,
      margin: "0 auto"
    };
    if (theme === "light") {
      style.backgroundColor = "#ccc";
    } else {
      style.backgroundColor = "#000";
    }
    return (
        <ThemeContext.Consumer>
            {
                vlaue=>{
                    return (
                        <div style={value}></div>
                    )
                }
            }
            <div style={this.context}></div>
        </ThemeContext.Consumer>
    );
  }
}

多个Context如何混合使用?

在我们实际的开发过程中,用到的Context对象可能不止是一个,这时我们需要多个Context共同使用,这时底层组件消费数据只能使用Context.Consumer组件。

import React from "react";
import { ThemeContext } from "./context/theme-context";
import { UserContext } from "./context/user-context";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: "light",
      user:{
        name:'test',
        changeUserName:this.changeUserName.bind(this)
      }
    };
    this.changeTheme = this.changeTheme.bind(this);
    this.changeUserName = this.changeUserName.bind(this);
  }
  changeTheme() {
    this.setState(state => {
      return {
        theme: state.theme === "light" ? "dark" : "light"
      };
    });
  }
  changeUserName() {
    console.log(this.state.user.name)
    this.setState({
      user:{
        name:this.state.user.name==='test'?'test1':'test',
        changeUserName:this.changeUserName.bind(this) 
      }
    })
  }
  render() {
    return (
      <div className="App">
        <button onClick={this.changeTheme}>Change Theme</button>
        <UserContext.Provider value={this.state.user}>
          <ThemeContext.Provider value={this.state.theme}>
            <Box />
            <UserContainer />
            <ThemeContext.Consumer>
              {value => <div>theme:{value}</div>}
            </ThemeContext.Consumer>
          </ThemeContext.Provider>
        </UserContext.Provider>
      </div>
    );
  }
}

class Box extends React.Component {
  static contextType = ThemeContext;
  render() {
    let theme = this.context;
    let style = {
      width: 100,
      height: 100,
      margin: "0 auto"
    };
    if (theme === "light") {
      style.backgroundColor = "#ccc";
    } else {
      style.backgroundColor = "#000";
    }
    return <div style={style}></div>;
  }
}

class UserContainer extends React.Component {
  render() {
    return (
      <UserContext.Consumer>
          {
            value=>{
              return (
              <div>
                username:{value.name}
                <button onClick={value.changeUserName}>change user name</button>
                </div>
              )
            }
          }
      </UserContext.Consumer>
    )
  }
}

export default App;

动态Context

动态Context就是将Context.Provider的value赋值为组件的state。在该组件state发生变化后便会响应的更新对应Context中的数据,同时这样的做可以防止由于Provider重新渲染导致Consumer的意外渲染。

const ThemeContext = React.createContext('light')
class App extends React.Component {
    constructor() {
        this.state = {
            theme:'dark'
        }
        this.changeTheme = () =>{
            this.setState(state=>{
                return {
                    theme:state.theme=='light'?'dark':'light'
                }
            })
        }
    }
    render() {
        <ThemeContext.Provider value={this.state.theme}>
            <button onClick="changeTheme">changeTheme</button>
        </ThemeContext.Provider>
    }
}

class ThemeBox extends React.Component {
    static contextType = ThemeContext;
    const style = {
        background:this.context=='light':'#ccc':'#000';
    }
    rener() {
        <div style={style}></div>
    }
}

如何在底层的组件中修改Context中的数据?

我们可以将顶层的方法传入Context对象中,使该方法一同传递到底层组件中。这样我们便可以在底层组件中调用顶层组件方法,然后在该方法中修改组件state,将state数据动态同步到Context数据当中。

import React from "react";
import { ThemeContext } from "./context/theme-context";

import "./App.css";
import { UserContext } from "./context/user-context";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: "light",
      user:{
        name:'test',
        changeUserName:this.changeUserName.bind(this)
      }
    };
    this.changeTheme = this.changeTheme.bind(this);
    this.changeUserName = this.changeUserName.bind(this);
  }
  changeTheme() {
    this.setState(state => {
      return {
        theme: state.theme === "light" ? "dark" : "light"
      };
    });
  }
  changeUserName() {
    console.log(this.state.user.name)
    this.setState({
      user:{
        name:this.state.user.name==='test'?'test1':'test',
        changeUserName:this.changeUserName.bind(this) 
      }
    })
  }
  render() {
    return (
      <div className="App">
        <button onClick={this.changeTheme}>Change Theme</button>
        <UserContext.Provider value={this.state.user}>
          <ThemeContext.Provider value={this.state.theme}>
            <Box />
            <ComB />
            <UserContainer />
            <ThemeContext.Consumer>
              {value => <div>theme:{value}</div>}
            </ThemeContext.Consumer>
          </ThemeContext.Provider>
        </UserContext.Provider>
      </div>
    );
  }
}

class Box extends React.Component {
  static contextType = ThemeContext;
  render() {
    let theme = this.context;
    let style = {
      width: 100,
      height: 100,
      margin: "0 auto"
    };
    if (theme === "light") {
      style.backgroundColor = "#ccc";
    } else {
      style.backgroundColor = "#000";
    }
    return <div style={style}></div>;
  }
}

class UserContainer extends React.Component {
  render() {
    return (
      <UserContext.Consumer>
          {
            value=>{
              return (
              <div>
                username:{value.name}
                <button onClick={value.changeUserName}>change user name</button>
                </div>
              )
            }
          }
      </UserContext.Consumer>
    )
  }
}

export default App;

总结

在我们日常用到的通用组件中,很多组件都是利用react Context特性来实现的。例如react-redux、react-router等等。

下面我们来对上面所介绍的知识做个总结:

  1. Context中数据只能有顶层向底层传递
  2. 顶层采用Context.Provider组件包裹,使用Context.Consumer组件获取数据
  3. 将组件state传递给Context可以实现动态的Context