React中的组件通信——父传子子传父Context

Posted 小小白学计算机

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React中的组件通信——父传子子传父Context相关的知识,希望对你有一定的参考价值。

0、认识组件间的通信

在开发过程中,我们会经常遇到需要组件之间相互进行通信:

  • 比如App可能使用了多个Header,每个地方的Header展示的内容不同,那么我们就需要使用者传递给Header一些数据,让其进行展示;
  • 又比如我们在Main中一次性请求了Banner数据和ProductList数据,那么就需要传递给他们来进行展示;
  • 也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件;

总之,在一个React项目中,组件之间的通信是非常重要的环节; 父组件在展示子组件,可能会传递一些数据给子组件:

  • 父组件通过 属性=值 的形式来传递给子组件数据;

  • 子组件通过 props 参数获取父组件传递过来的数据;

一、父传子通信——类组件

二、父传子通信——函数式组件

三、属性验证——参数propTypes

对于传递给子组件的数据,有时候我们可能希望进行验证,特别是对于大型项目来说:

  • 当然,如果你项目中默认继承了Flow或者TypeScript,那么直接就可以进行类型验证;
  • 但是,即使我们没有使用Flow或者TypeScript,也可以通过 prop-types 库来进行参数验证;
  • 从 React v15.5 开始,React.PropTypes 已移入另一个包中:prop-types 库

更多的验证方式,可以参考官网:https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html

  • 比如验证数组,并且数组中包含哪些元素;
  • 比如验证对象,并且对象中包含哪些key以及value是什么类型;
  • 比如某个原生是必须的,使用 requiredFunc: PropTypes.func.isRequired

如果没有传递,我们希望有默认值呢?

  • 我们使用defaultProps就可以了



四、子组件传递父组件

某些情况,我们也需要子组件向父组件传递消息:

  • 在vue中是通过自定义事件来完成的;
  • 在React中同样是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可;

将计数器案例进行拆解;

  • 将按钮封装到子组件中:CounterButton;
  • CounterButton发生点击事件,将内容传递到父组件中,修改counter的值;

import React, PureComponent from 'react';
class CounterButton extends PureComponent 
    constructor(props) 
        super(props);
    
    render() 
        return (
            <button onClick=e => this.counterIncrement()>CounterButton里的+1</button>
        )
    
    counterIncrement() 
        const increment = this.props
        increment()
        // this.props.increment()
    

class App extends PureComponent 
    constructor(props) 
        super(props);
        this.state = 
            counter: 0
        
    
    render() 
        return (
            <div>
                <h2>当前计数: this.state.counter</h2>
                <button onClick=e => this.increment()>App里的+</button>
                <br/>
                <CounterButton increment=e => this.increment() />
            </div>
        );
    
    increment() 
        this.setState(
            counter: this.state.counter + 1
        )
    

export default App ;

import React,  Component  from 'react';
function ProfileHeader (props) 
    return (
        <div>
            <h2>用户昵称: props.nickname</h2>
            <h2>用户等级: props.level</h2>
        </div>
    )

function Profile (props) 
    return (
        <div>
            /* <ProfileHeader nickname=props.nickname level=props.level /> */
            <ProfileHeader ...props />
            <ul>
                <li>设置1</li>
                <li>设置2</li>
                <li>设置3</li>
                <li>设置4</li>
            </ul>
        </div>
    )

class App extends Component 
    constructor(props) 
        super(props)
        this.state = 
            nickname: 'zepp',
            level: 99
        
    
    render() 
        const nickname, level = this.state

        return (
            <div>
                app
                <Profile nickname=nickname level=level />
            </div>
        );
    


export default App;

五、Context应用场景

非父子组件数据的共享

  • 在开发中,比较常见的数据传递方式是通过props属性自上而下(由父到子)进行传递。
  • 但是对于有一些场景:比如一些数据需要在多个组件中进行共享(地区偏好、UI主题、用户登录状态、用户信息等)。
  • 如果我们在顶层的App中定义这些信息,之后一层层传递下去,那么对于一些中间层不需要数据的组件来说,是一种冗余的操作。

但是,如果层级更多的话,一层层传递是非常麻烦,并且代码是非常冗余的:

  • React提供了一个API:Context;
  • Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props;
  • Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言;

六、Context相关API

1. React.createContext

  • 创建一个需要共享的Context对象:
  • 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的context值;
  • defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值

2. Context.Provider

  • 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化:
  • Provider 接收一个 value 属性,传递给消费组件;
  • 一个 Provider 可以和多个消费组件有对应关系;
  • 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;
  • 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染;

3. Class.contextType

  • 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象:
  • 这能让你使用 this.context 来消费最近 Context 上的那个值;
  • 你可以在任何生命周期中访问到它,包括 render 函数中;

4. Context.Consumer

  • 这里,React 组件也可以订阅到 context 变更。这能让你在 函数式组件 中完成订阅 context。
  • 这里需要 函数作为子元素(function as child)这种做法;
  • 这个函数接收当前的 context 值,返回一个 React 节点;

    什么时候使用默认值defaultValue呢?

什么时候使用Context.Consumer呢?

  • 1.当使用value的组件是一个函数式组件时;
  • 2.当组件中需要使用多个Context时;



import React, Component from 'react';
// 创建Context对象
const UserContext = React.createContext(
    nickname: 'bbb',
    level: -1
)
/* function ProfileHeader (props) 
    return (
        <div>
            <h2>用户昵称: </h2>
            <h2>用户等级: </h2>
        </div>
    )
 */
class ProfileHeader extends Component 
    render() 
        console.log('====================================');
        console.log(this.context);
        console.log('====================================');
        return (
            <div>
                <h2>用户昵称:this.context.nickname </h2>
                <h2>用户等级:this.context.level </h2>
            </div>
        )
    

ProfileHeader.contextType = UserContext
function Profile(props) 
    return (
        <div>
            /* <ProfileHeader nickname=props.nickname level=props.level /> */
            <ProfileHeader/>
            <ul>
                <li>设置1</li>
                <li>设置2</li>
                <li>设置3</li>
                <li>设置4</li>
            </ul>
        </div>
    )

class App extends Component 
    constructor(props) 
        super(props)
        this.state = 
            nickname: 'zepp222',
            level: 99
        
    
    render() 
        const nickname, level = this.state

        return (
            <div>
                app
                <UserContext.Provider value=this.state>
                    <Profile/>
                </UserContext.Provider>

            </div>
        );
    

export default App;

import React, Component from "react";
// 创建Context对象
const UserContext = React.createContext(
    nickname: "bbb",
    level: -1,
);
function ProfileHeader(props) 
    return (
        <UserContext.Consumer>
            (value) => 
                return (
                    <div>
                        <h2>用户昵称:value.nickname </h2>
                        <h2>用户等级:value.level </h2>
                    </div>
                );
            
        </UserContext.Consumer>
    );

/* class ProfileHeader extends Component 
    render() 
        console.log('====================================');
        console.log(this.context);
        console.log('====================================');
        return (
            <div>
                <h2>用户昵称:this.context.nickname </h2>
                <h2>用户等级:this.context.level </h2>
            </div>
        )
    
 */
// ProfileHeader.contextType = UserContext;
function Profile(props) 
    return (
        <div>
            <ProfileHeader/>
            <ul>
                <li>设置1</li>
                <li>设置2</li>
                <li>设置3</li>
                <li>设置4</li>
            </ul>
        </div>
    );

class App extends Component 
    constructor(props) 
        super(props);
        this.state = 
            nickname: "zepp",
            level: 99,
        ;
    
    render() 
        return (
            <div>
                app
                <UserContext.Provider value=this.state>
                    <Profile/>
                </UserContext.Provider>
            </div>
        );
    

export default App;

多个context嵌套:

import React,  Component  from "react";
// 创建Context对象
const UserContext = React.createContext(
  nickname: "bbb",
  level: -1,
);
const ThemeContext = React.createContext(
  color: "black",
);
function ProfileHeader(props) 
  return (
    <UserContext.Consumer>
      (value) => 
        return (
          <ThemeContext.Consumer>
            (theme) => 
              return (
                <div>
                  <h2>用户昵称:value.nickname </h2>
                  <h2>用户等级:value.level </h2>
                  <h2>颜色: theme.color</h2>
                </div>
              );
            
          </ThemeContext.Consumer>
        );
      
    </UserContext.Consumer>
  );

/* class ProfileHeader extends Component 
    render() 
        console.log('====================================');
        console.log(this.context);
        console.log('====================================');
        return (
            <div>
                <h2>用户昵称:this.context.nickname </h2>
                <h2>用户等级:this.context.level </h2>
            </div>
        )
    
 */
ProfileHeader.contextType = UserContext;
function Profile(props) 
  return (
    <div>
      <ProfileHeader />
      <ul>
        <li>设置1</li>
        <li>设置2</li>
        <li>设置3</li>
        <li>设置4</li>
      </ul>
    </div>
  );

class App extends Component 
  constructor(props) 
    super(props);
    this.state = 
      nickname: "zepp",
      level: 99,
    ;
  
  render() 
    return (
      <div>
        app
        <UserContext.Provider value=this.state>
          <ThemeContext.Provider value= color: "red" >
            <Profile />
          </ThemeContext.Provider>
        </UserContext.Provider>
      </div>
    );
  


export default App;

以上是关于React中的组件通信——父传子子传父Context的主要内容,如果未能解决你的问题,请参考以下文章

React数据通信父传子和子传父的使用

Vue2.0的三种常用传值方式父传子子传父非父子组件传值

vue 组件通信 (子传父 , 父传子 , 兄弟通信)

VUE_03 组件化思想全局|局部注册组件之间传值(父传子子传父兄弟传兄弟)匿名|具名|作用域插槽

React传值(子传父,父传子)

Vue组件 父传子 子传父