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

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;
}

在这里插入图片描述
我们知道,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.createContext(defaultValue)
    React.createContext("light"), 创建一个Context对象且该对象的_currentValue属性初始化为"light",即context._currentValue="light"
  • Context.Provider
    <Context.Provider>是一个组件,接受value属性来更新 context._currentValue
    <Context.Provider value='dark'></Context.Provider>,所以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.Consumer>也是一个组件,消费组件,就是用到了Context的组件。
    <Context.Consumer>的子组件必须是一个函数,该函数接受一个参数,该参数值就是this.context
    <Context.Consumer>会从组件树中离自己最近的<Context.Provider>中获取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值是不同的。
在这里插入图片描述
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 (
    //         <Context.Consumer>
    //             {value => <button className={value} {...this.props}>按钮</button>}
    //         </Context.Consumer>
    //     )  
    // }
}
Button.contextType = Context;

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

在这里插入图片描述

扩展运算符(…)的应用

{...this.props}或者{...props}
在这里插入图片描述
看下面这个简单的例子来理解下。

  • 没有使用扩展运算符
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"
        //     }
        // })     
  • <Context.Consumer>只接受函数为 子组件
//以函数为子组件,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这个数据了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值