React组件间的通信,你至少要知道这些?

2,381 阅读6分钟

本篇文章主要简单介绍一下关于组件间通信的几种方式,文中出现具体代码请查看 Git地址。希望可以对大家有一点帮助,文中如有错误,欢迎指正。

一、父子组件之间通信

在React中父子组件间的通讯主要是通过props进行,子组件接收父组件传过来的props值进行更新,而子组件向父组件通信主要是通过父组件传递过来的回调函数,从而改变状态,不多说,看代码。

父组件


import React, { Component } from 'react';
import Child from './child';

class Parent extends Component {

    state = {
        name: 'lj'
    }

    changeName = name => {
        this.setState({
            name
        });
    }

    render() {
        const { name } = this.state;
        return (
            <div style={{ border: '1px solid' }}>
              parent
              <Child name={name} changeName={this.changeName}/> // 进行值传递
            </div>
          );
    }
}

export default Parent;

子组件


import React from 'react';

function Child(props) {

    const {name, changeName} = props; // 接收值和回调函数

    return (
        <div style={{ border: '1px solid red', margin: 100 }}>
            { name }
            <button onClick={() => changeName('hd')}>按钮</button> // 触发回调函数
        </div>
    );
}

export default Child;

二、组件间通信

1.利用共有的父级

当child1组件value改变之后 通过回调函数changeValue 通知父组件Parent,父组件Parent接收到之后再传递给子组件child2,父组件Parent相当于一个中间件。

1>父组件代码


import React, { Component } from 'react';
import Child1 from './Child1';
import Child2 from './Child2';

class Parent extends Component {

    state = {
        value: ''
    }

    changeValue = value => {
        this.setState({
            value
        });
    }

    render() {
        const { value } = this.state;
        return (
            <div style={{ border: '1px solid' }}>
              <Child1 changeValue={ this.changeValue }/>
              <Child2 value={value}/>
            </div>
          );
    }
}

export default Parent;

2>子组件1代码


import React, { Component } from 'react';

class Child1 extends Component {

    state = {
        value: ''
    }

    inputChange = e => {
        this.setState({
            value: e.target.value
        });
    }

    toChild2 = () => {
        const { changeValue } = this.props;
        const { value } = this.state;
        changeValue(value);
    }

    render() {

        const { value } = this.state;
        return (
            <div style={{ background: 'red', margin: '100px 0px' }}>
                我是child1
                <input type="text" onChange={this.inputChange} value={value}/>
                <button onClick={this.toChild2}>通知B</button>
            </div>
        )
    }
}

export default Child1;

3>子组件2代码


import React, { Component } from 'react';

class Child2 extends Component {

    render() {
        const {value} = this.props;
        return (
            <div style={{ background: 'yellow' }}>
                我是child2,这是child1传过来的值{value}
            </div>
        )
    }
}

export default Child2;

2.利用context

关于context, 这里分成3部分进行介绍,分别是老版本(16版本以下)、新版本(16版本以上)、Hook。

2.1 老版本

getChildContext 根组件中声明,一个函数,返回一个对象,就是context。
childContextTypes 根组件中声明,指定context的结构类型,如不指定,会产生错误。
contextTypes 子孙组件中声明,指定要接收的context的结构类型,可以只是context的一部分结构。contextTypes 没有定义,context将是一个空对象。
this.context 在子孙组件中通过此来获取上下文。

1>父组件


import React, { Component } from 'react';
import Child1 from './Child1';
import Child2 from './Child2';
import PropTypes from 'prop-types';

class Parent extends Component {

    ...

    getChildContext() { 
        return {
            value: this.state.value,
            changeValue: this.changeValue
        }
    }
    render() {
        return (
            <div style={{ border: '1px solid' }}>
              <Child1 />
              <Child2 />
            </div>
          );
    }
}

Parent.childContextTypes = { // 指定context结构类型
    value: PropTypes.string,
    changeValue: PropTypes.func,
}

export default Parent;

2>子组件1


import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Child1 extends Component {

    ...

    toChild2 = () => {
        const { changeValue } = this.context;
        const { value } = this.state;
        changeValue(value);
    }

    render() {

        const { value } = this.state;
        return (
            <div style={{ background: 'red', margin: '100px 0px' }}>
                我是child1
                <input type="text" onChange={this.inputChange} value={value}/>
                <button onClick={this.toChild2}>通知B</button>
            </div>
        )
    }
}
Child1.contextTypes = { // 指定需要获取的context属性
    changeValue: PropTypes.func,
};
export default Child1;

3>子组件2


import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Child2 extends Component {

    render() {
        const {value} = this.context;
        return (
            <div style={{ background: 'yellow' }}>
                我是child2,这是child1传过来的值{value}
            </div>
        )
    }
}

Child2.contextTypes = {
    value: PropTypes.string,
};

export default Child2;

2.2新版本

新版本的React context使用了Provider和Customer模式,和react-redux的模式非常像。在顶层的Provider中传入value, 在子孙级的Consumer中获取该值,并且能够传递函数,用来修改context。

1>父组件


import React, { Component } from 'react';
import Child1 from './Child1';
import Child2 from './Child2';

export const Context = React.createContext();

class Parent extends Component {

    ...

    render() {
        return (
            <Context.Provider value={{ value: this.state.value, changeValue: this.changeValue }}>
                <div style={{ border: '1px solid' }}>
                    <Child1 />
                    <Child2 />
                </div>
            </Context.Provider >
        );
    }
}

export default Parent;

2>子组件1

import React, { Component } from 'react';
import { Context } from './index';

class Child1 extends Component {

    ...

    toChild2 = (fn) => {
        const { value } = this.state;
        fn(value);
    }

    render() {
        return (
            <Context.Consumer>
                {
                    data => {
                        return (
                            <div style={{ background: 'red', margin: '100px 0px' }}>
                                我是child1
                                <input type="text" onChange={this.inputChange} value={this.state.value} />
                                <button onClick={() => this.toChild2(data.changeValue)}>通知B</button>
                            </div>
                        )
                    }
                }
            </Context.Consumer>
        )
    }
}
export default Child1;

3>子组件2


import React, { Component } from 'react';
import { Context } from './index';


class Child2 extends Component {

    render() {
        return (
            <Context.Consumer>
                {
                    data => {
                        return (
                            <div style={{ background: 'yellow' }}>
                                我是child2,这是child1传过来的值{data.value}
                            </div>
                        )
                    }
                }
            </Context.Consumer>

        )
    }
}

export default Child2;

2.3 Hook写法

通过createContext来 进行兄弟组件通信,hook的方式主要是通过useContext,写法基本和2.2的区别不是很大,在这里简单实现以下,另外在这里也说明一下如何使用useImperativeHandle完成父组件调用子组件的方式。

1> 父组件


import React, { createContext, useState } from 'react';
import Child1 from './Child1';
import Child2 from './Child2';

export const Context = createContext();

function Parent() {

    const [value, setValue] = useState('');
    

    function changeValue(value) {
        setValue(value);
    }
    return (
        <Context.Provider value={{ value: value, changeValue: changeValue }}>
            <div style={{ border: '1px solid' }}>
                <Child1 />
                <Child2 />
            </div>
        </Context.Provider >
    );
}

export default Parent;

2>子组件1


import React, { useContext, useState } from 'react';
import { Context } from './index';

function Child1() {

    const [value, setValue] = useState('');
    const { changeValue } = useContext(Context);

    function inputChange(e) {
        setValue(e.target.value);
    }

    function toChild2() {
        changeValue(value);
    }

    return (
        <div style={{ background: 'red', margin: '100px 0px' }}>
            我是child1
            <input type="text" onChange={inputChange} value={value} />
            <button onClick={toChild2}>通知B</button>
        </div>
    )
}
export default Child1;

3>子组件2


import React, { useContext } from 'react';
import { Context } from './index';

function Child2() {

    const { value } = useContext(Context);

    return (
        <div style={{ background: 'yellow' }}>
            我是child2,这是child1传过来的值{value}
        </div>

    )
}

export default Child2;

下面在具体说一下useImperativeHandle。
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用:


    //父组件

    const ref = useRef();

    return (
        <div style={{ border: '1px solid' }}>
            <Child3 ref={ref} />
            <button onClick={() => ref.current && ref.current.changeValue('修改了')}>修改child3的值</button>
        </div>
    );
    
    // 子组件
    
    export default forwardRef((props, ref) => {

        const [value, setValue] = useState('');
    
        useImperativeHandle(ref, () => ({ // 暴露出去的参数以及方法
            value,
            changeValue
        }));
    
        function changeValue(value) {
            setValue(value);
        }
    
        return (
            <div ref={ref}>child3的值{value}</div>
        )
    });

下面说一种不太常用的方式

3.订阅-发布模式

  • 新建一个event.js文件,编写一个发布订阅模式的类。

class Event {
    constructor() {
        this.events = this.events || new Map();
    }
}

//触发
Event.prototype.emit = function(type, ...args) {
    let handler = this.events.get(type);

    if(handler && Array.isArray(handler)) {
        for(let i=0, l=handler.length; i<l; i++) {
            if(args.length > 0) {
                handler[i].apply(this, args);
            }else {
                handler[i].call(this);
            }
        }
    }else {
        if(args.length > 0) {
            handler.apply(this, args);
        }else {
            handler.call(this);
        }
    }

    return;

}

// 订阅
Event.prototype.on = function(type, fn) {
    const handler = this.events.get(type);

    if(!handler) {
        this.events.set(type, fn);
    }else if(handler && typeof handler === 'function') {
        this.events.set(type, [handler, fn]);
    }else {
        handler.push(fn);
    }
}

// 移除监听

Event.prototype.remove = function(type, fn) {
    const handler = this.events.get(type);

    if(handler && typeof handler === 'function') {
        this.events.delete(type, fn);
    }else {
        let position = -1;
        for (let i = 0; i < handler.length; i++) {
            if (handler[i] === fn) {
                position = i;
                return;
            }
        }

        if(position !== -1) {
            handler.splice(position, 1);
            if(handler.length === 1) {
                this.events.set(type, handler[0]);
            }
        }else {
            return this;
        }
    }
}

export default new Event();


//子组件1
export default class Child extends Component {

    setValue = () => {
        event.emit('dispatch', 1); // 触发
    }

    render() {
        return (
            <div>
                子组件1
                <button onClick={this.setValue}>通知B</button>
            </div>
        )
    }
}

// 子组件2
export default class Child1 extends Component {
    constructor() {
        super();
        this.state = {
            value: 0
        }
    }

    componentDidMount() {
        event.on('dispatch', this.valueChange); // 设置监听
    }

      valueChange = value => {
        this.setState({
            value
        })
    }

    componentWillUnmount() {
        event.remove('dispatch', this.valueChange);
    }

    render() {
        return (
            <div>
                这是子组件的value {this.state.value}
            </div>
        )
    }
}

4. Redux

相信熟悉React的小伙伴,对Redux肯定不是很陌生,对于一些大型项目,普遍使用Redux的还是居多的,因为这部分内容还是较多,会在之后和React-Saga进行一个整体的介绍。

三、总结

以上基本总结了我所了解的几种关于React组件间通信的几种方式,关于React通信,不管是项目中还是面试中基本都会用到和问到,希望这篇文章对大家有一定的帮助,如果你还知道其他的几种方式,欢迎在评论中指出哦。