【React】Context实现组件间数据共享

文章目录

    • props实现数据传递
    • 用Context共享数据
    • 动态Context
      • demo1
        • 扩展运算符(...)的应用
        • 可能的坑
      • demo2
    • 用props传递组件

props实现数据传递

//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from "./components/app.js";
ReactDOM.render(<App  />,document.querySelector('#root'));
//app.js
import React from 'react';
import Toolbar from "./toolbar.js";

function App(props){
     
    return <Toolbar theme={
     'dark'} />
}
export default App;
//toolbar.js
import React from 'react';
import Button from "./Button/button.js";

function Toolbar(props){
     
    return (
        <div>
            <Button theme={
     props.theme} />
        </div>
    );
}
export default Toolbar;
//button.js
import React from 'react';
import "./button.css";

function Button(props){
     
    return <button className={
     props.theme}>按钮</button>
}
export default Button;
//button.css
.dark{
     
    background-color:darkkhaki;
}

【React】Context实现组件间数据共享_第1张图片
我们知道,props可以将数据从父组件传递给子组件。
现在,顶级组件App要传递theme这个数据给底层组件Button,经历了这么一个传递路径:App–>Toolbar–>Button
然而,Toolbar根本用不上theme,它只是一个中转站。
在组件设计过程中,我们常常尽力弱化组件间的依赖关系,以期望组件之间彼此独立。
这里用组件扮演中转站的角色明显辜负了我们的期望。
如果有个全局变量就好了,诶,React给我们提供了类似于全局变量的东西来实现数据共享,那就是Context。

用Context共享数据

//新增context.js,输出context对象
import React from "react";
export default React.createContext('light');
//修改app.js
import React from 'react';
import Toolbar from "./toolbar.js";
import Context from "./context.js";

function App(props){
     
    return (
        <Context.Provider value='dark'>
            <Toolbar/>
        </Context.Provider>
    );
}

export default App;
//修改toolbar.js
import React from 'react';
import Button from "./Button/button.js";

function Toolbar(){
     
    return (
        <div>
            <Button/>
        </div>
    );
}

export default Toolbar;
//修改button.js
import React from 'react';
import "./button.css";
import Context from "../context.js";

class Button extends React.Component{
      
    static contextType = Context;
    render(){
     
        return <button className={
     this.context}>按钮</button>
    }
}
//Button.contextType = Context;
export default Button;

使用static contextType = Context可能遇到问题:Support for the experimental syntax ‘classProperties’ isn’t currently enabled。
安装插件@babel/plugin-proposal-class-propertie,并将其添加至webpack配置文件里可解决。

            {
     
                test:/\.js$/,
                include:/src/,
                exclude:/node_modules/,
                use:{
     
                    loader:'babel-loader',
                    options:{
     
                        presets:['@babel/preset-react'],
                        plugins:["@babel/plugin-proposal-class-properties"]
                    }
                }
            }

redux里,组件间共享store就是用Context实现的。
好了,继续了解下Context。
【React】Context实现组件间数据共享_第2张图片

  • React.createContext(defaultValue)
    React.createContext("light"), 创建一个Context对象且该对象的_currentValue属性初始化为"light",即context._currentValue="light"
  • Context.Provider
    是一个组件,接受value属性来更新 context._currentValue
    ,所以context._currentValue="dark"
  • class.contextType/this.context
    Button.contextType=Context,即Button类的contextType属性是一个Context对象。
    React在解析Button类时发现它有contextType属性,且该属性是一个对象,于是获取其_currentValue值并赋给Button实例的context属性,即this.context=context._currentValue
    所以在本例中,this.context的值就是"dark"。
  • Context.Consumer
    也是一个组件,消费组件,就是用到了Context的组件。
    的子组件必须是一个函数,该函数接受一个参数,该参数值就是this.context
    会从组件树中离自己最近的中获取this.context
    看下面这个例子,新建一个Text组件,用来显示this.context值,这个Text组件就是消费组件。现在,在App组件中引入Text组件并放于两个不同的位置。
import React from "react";
import Context from "./context.js";

function Text(){
     
    return (
        <Context.Consumer>
        {
     
            value => <span>{
     value}</span> 
        }
        </Context.Consumer>
    );
}

export default Text;
import React from 'react';
import Toolbar from "./toolbar.js";
import Context from "./context.js";
import Text from "./text.js";
function App(props){
     
    return (
        <>
        <Context.Provider value='dark'>
            <Text/>
            <Toolbar/>
        </Context.Provider>
        <Text/>
        </>
    );
}

export default App;

瞧,两个 Text组件取到的this.context值是不同的。
【React】Context实现组件间数据共享_第3张图片
context实现数据共享,相关应用还可以看下 购物车实例。

动态Context

demo1

使用this.state同步记录this.context,点击按钮可更新this.state,从而改变this.context

//button.css
.dark{
     
    background-color:darkkhaki;
}
.light{
     
    background-color:lightyellow;
}
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from "./components/app.js";
ReactDOM.render(<App/>,document.querySelector('#root'));
//app.js
import React from 'react';
import Toolbar from "./toolbar.js";
import Context from "./context.js";

class App extends React.Component{
     
    constructor(props){
     
        super(props);
        this.state = {
     
            theme:"dark"
        }
        this.onChangeTheme = this.onChangeTheme.bind(this);
    }
    onChangeTheme(){
     
        this.setState(state => ({
     
            theme:state.theme==="light"?"dark":"light"
        }));
        // this.setState(state => {
     
        //     return {
     
        //         theme:state.theme==="light"?"dark":"light"
        //     }
        // })     
    }
    render(){
     
        return (
            <Context.Provider value={
     this.state.theme}>
                <Toolbar changeTheme={
     this.onChangeTheme}/>
            </Context.Provider>
        );
    }
}
export default App;
//toolbar.js
import React from 'react';
import Button from "./Button/button.js";

function Toolbar(props){
     
    return (
        <div>
            <Button onClick={
     props.changeTheme}/>
        </div>
    );
}

export default Toolbar;
//button.js
import React from 'react';
import "./button.css";
import Context from "../context.js";

class Button extends React.Component{
      
    render(){
     
        return <button className={
     this.context}
                    //    onClick={this.props.onClick}
                    {
     ...this.props}
                    >按钮</button>
    }
    // render(){
     
    //     return (
    //         
    //             {value => }
    //         
    //     )  
    // }
}
Button.contextType = Context;

export default Button;
//context.js
import React from "react";
export default React.createContext('light');

在这里插入图片描述

扩展运算符(…)的应用

{...this.props}或者{...props}
【React】Context实现组件间数据共享_第4张图片
看下面这个简单的例子来理解下。

  • 没有使用扩展运算符
import React from 'react';

function Greeting(props){
     
    const {
     firstname,lastname} = props;
return <span>Welcome,{
     firstname} {
     lastname}</span>;
}
function App(){
     
    return <Greeting firstname="Steven" lastname="Jobs"/>;
}
export default App;
  • 使用扩展运算符
import React from 'react';

function Greeting(props){
     
    const {
     firstname,lastname} = props;
return <span>Welcome,{
     firstname} {
     lastname}</span>;
}
function App(){
     
    const obj = {
     
        firstname:"Steven",
        lastname:"Jobs"
    }
    return <Greeting {
     ...obj} />;
}
export default App;

可能的坑

  • 箭头函数返回一个对象,将该对象用()括起来即可
        this.setState(state => ({
     
            theme:state.theme==="light"?"dark":"light"
        }));
        // this.setState(state => {
     
        //     return {
     
        //         theme:state.theme==="light"?"dark":"light"
        //     }
        // })     
  • 只接受函数为 子组件
//以函数为子组件,OK
<Context.Consumer>
    {
     value => <button className={
     value} {
     ...this.props}>按钮</button>}
</Context.Consumer>
//子组件不是函数,NOK
//A context consumer expects a single child that is a function 
<Context.Consumer>
    <button className={
     this.context} {
     ...this.props}>按钮</button>
</Context.Consumer>

demo2

使用Context传递函数,解决 组件嵌套太深 难以更新this.context的问题。
和 demo1 一样,使用 this.state同步记录this.context
不同的是,这里的this.context 是 一个对象。

//context.js
import React from "react";
export default React.createContext({
     
    theme:"light",
    changeTheme: () => {
     }
});
//app.js
import React from "react";
import Context from "./context.js";
import Button from "./Button/button.js";

class App extends React.Component{
     
    constructor(props){
     
        super(props);

        this.onChangeTheme = this.onChangeTheme.bind(this);
        this.state = {
     
            theme:"light",
            changeTheme:this.onChangeTheme
        }
    }
    onChangeTheme(){
     
        this.setState(state => ({
     
            theme:state.theme==="light"?"dark":"light"
        }));
    }
    render(){
     
        return (
            <Context.Provider value={
     this.state}>
                <Button />
            </Context.Provider>
        )
    }
}

export default App;
//button.js
import React from "react";
import Context from "../context.js";
import "./button.css";

class Button extends React.Component{
     
    render(){
     
        return (
            <Context.Consumer>
                {
     
                    ({
     theme,changeTheme}) => <button className={
     theme} onClick={
     changeTheme}>按钮</button>
                }
            </Context.Consumer>
        )  
    }
}
Button.contextType = Context;

export default Button;

用props传递组件

不用Context,用props传递组件 也可以解决中转站的问题。

//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from "./components/app.js";
ReactDOM.render(<App theme='dark' />,document.querySelector('#root'));
//app.js
import React from 'react';
import Button from "./Button/button.js";
import Toolbar from "./toolbar.js";

function App(props){
     
    const button = <Button theme={
     props.theme}/>
    return (
            <Toolbar button={
     button}>
            </Toolbar>

    );
}
export default App;

//toolbar.js
import React from 'react';

function Toolbar(props){
     
    return (
        <div>
        {
     props.button}
        </div>
    );
}

export default Toolbar;
//button.js
import React from 'react';
import "./button.css";

function Button(props){
     
    return <button className={
     props.theme}>按钮</button>
}
export default Button;

既然顶层组件App和底层组件Button都用到了theme,那它俩自己商量着解决就好了,没有必要将根本用不着themeToolbar也牵扯进来。于是,在App中将Button组合进来,并将Button这个组件通过props传递下去,这样其它组件就不用关心theme这个数据了。

你可能感兴趣的:(react)