React 组件通信方式

React 组件通信方式

一.前言

React应用是由组件堆积的形式而成的,组件之间相互独立,但是可以相互通信的,React组件的数据是单向数据流的

二.组件通信

1.父组件通信子组件

父组件要通信子组件是很简单的,使用Props进行属性传值即可!

export default class Parent extends React.Component {
    render() {
        return (
            <div>
                父组件
                <Son name="liuqiao"></Son>
            </div>
        );
    }
}


class Son extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <div>
                {this.props.name}
            </div>
        );
    }
}

2.子组件通信父组件

React虽然是单向数据流的,但是不影响子组件向父组件通信.通过父组件可以向子组件传递函数这个特性,利用回调函数来实现子组件通信父组件


export default class Parent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name: '我是父组件'
        };
    }

    handleClick = text => {
        this.setState({
            name: text
        });
    }
    render() {
        return (
            <div>
                {this.state.name}
                <Son handleClick={this.handleClick}></Son>
            </div>
        );
    }
}


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

        this.state = {
            msg: '我要向父组件传递数据'
        };
    }

    render() {
        return (
            <div>
                <button onClick={
                    () => {
                        this.props.handleClick(this.state.msg);
                    }
                }>按钮</button>
            </div>
        );
    }
}

上述代码中,我在Son组件中要想向父组件Parent传递数据,使用了一个从父组件传递过来的回调函数,此回调函数在子组件中触发,传递相应的参数,也就是要传递的数据.然后在父组件中接收并处理.从而达到子组件通信父组件.

PS:一般情况下,回调函数会与setState成对出现

3.context跨级组件通信(生产-消费者模式)

context的设计目的是为了共享对于一个组件树而言是全局性的数据.可以尽量减少逐层传递,但是不建议用context,因为当结构复杂时,这种全局变量不易追溯到源头,不知道他从哪里传递过来的,会导致应用混乱,难以维护.

context适用的场景最好是全局性的信息,且不可变.如用户信息,界面颜色主题定制

// 状态树跨级通信
import React from 'react';

//定义一个全局的context
const GlobalContext = React.createContext();

export default class Context extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            msg: "来自老祖宗的问候",
            isCreated: false
        };
    }
    render() {
        return (
            //提供一个生产者
            <GlobalContext.Provider value={
                {
                    //传递给消费者的数据
                    msg: this.state.msg,
                    //传递给消费者,一个函数,提供改变生产者内部的一个参数
                    onChangeShow: (text) => {
                        this.setState({
                            msg: text
                        });
                    },
                }
            }>
                <div>
                    <h1>我是父组件,我提供数据给子孙吗:{this.state.msg}</h1>
                    <Son></Son>
                </div>
            </GlobalContext.Provider>
        );
    }
}

class Son extends React.Component {
    render() {
        return (
            <GlobalContext.Consumer>
                {
                    context => (
                        <div>
                            <h1>我是儿子组件,我要消费父亲的数据,{context.msg}</h1>
                            <GrandSon></GrandSon>
                        </div>
                    )

                }
            </GlobalContext.Consumer>
        );
    }
}

class GrandSon extends React.Component {
    render() {
        return (
            <GlobalContext.Consumer>
                {
                    context => (
                        <div>
                            <h1>我是孙子组件,我要消费祖宗的数据,{context.msg}</h1>
                            <button onClick={() => {
                                context.onChangeShow('我要改变我祖宗的规矩');
                            }
                            }>我要改变我祖宗的规矩</button>
                        </div>
                    )
                }

            </GlobalContext.Consumer>

        );
    }
}

context中有个两个需要理解的概念:

  • 生产者 Provider

    用在组件树中更外层的位置。它接受一个名为 value 的 prop,其值可以是任何 JavaScript 中的数据类型

  • 消费者consumer

    可以在 Provider 组件内部的任何一层使用。它接收一个名为 children 值为一个函数的 prop。这个函数的参数是 Provider 组件接收的那个 value prop 的值,返回值是一个 React 元素(一段 JSX 代码)。

通常消费者是一个或多个子节点,所以context的设计模式是生产-消费者模式

PS:GlobalContext.Consumer内必须是回调函数,通过context方法改变根组件状态

context优缺点:

  • 优点:跨组件访问数据

  • 缺点:react组件树种某个上级组件shouldComponetUpdate 返回false,当context更新时,不会引起下级组件更新

4.非父子组件通信(发布订阅模式)

发布订阅模式优点:

  • 1.耦合度低,发布者和订阅者互不干扰,他们能够独立运行,不用担心开发过程中两者之间的关系
  • 2.易于扩展,让系统无论什么时候都可以扩展,该订阅的订阅,该发布的发布
  • 3.灵活性,只要遵守一份协议,不需要担心不同的组件是如何组合在一起的

类似于观察者模式,实现发布订阅模式需要3个重要核心

  • 存储消息
  • 订阅消息
  • 发布消息
// 发布订阅模式实现
const Event = {
    //存储消息
    cacheList: {},

    /**
     * 订阅消息
     * @param {消息类型} type 
     * @param {回调函数} callback 
     */
    subscribe(type, callback) {
        //如果存在此类型的消息,就push
        if (this.cacheList[type]) {
            this.cacheList[type].push(callback);
        }
        else {
            // 无此类消息,直接赋值回调函数
            this.cacheList[type] = [callback];
        }

    },
    //发布消息
    dispatch(type, data) {
        // 发布消息时,如果没有此类型的消息,直接退出
        if (!this.cacheList[type]) {
            return;
        }
        else {
            //循环执行回调函数
            var currentCache = this.cacheList[type];

            currentCache.map((ele,index)=>{
                currentCache[index](data);
            });
        }
    }
};

以上代码,定义了一个发布订阅模式的核心,传递消息者,需要发布消息,接收消息者,需要订阅组件,订阅时,可以在React的生命周期函数中执行!

export default class Person extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            msg: "来自老祖宗的问候",
        };
    }

    //组件订阅消息,此时涉及到一个生命周期,当组件加载完成时执行的钩子函数
    render() {
        return (
            <div>
                <button onClick={() => {
                    Event.dispatch('taggle', this.state.msg);
                }}>我是祖宗组件,我要发布消息</button>
                <Son></Son>
            </div>
        );
    }
}

class Son extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            msg: "来自儿子的问候",
        };
    }

    //当组件加载完成时,直接订阅消息
    componentDidMount() {
        Event.subscribe('taggle', (data) => {
            this.setState({
                msg: data
            });
        });
    }

    render() {
        return (
            <div>
                <h1>我是儿子组件,{this.state.msg}</h1>
                <GrandSon></GrandSon>
            </div>
        );
    }
}

class GrandSon extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            msg: "来自孙子的问候",
        };
    }

    //当组件加载完成时,直接订阅消息
    componentDidMount() {
        Event.subscribe('taggle', (data) => {
            this.setState({
                msg: data
            });
        });
    }
    render() {
        return (
            <div>
                <h1>我是孙子组件,{this.state.msg}</h1>
            </div>
        );
    }
}

5.状态提升(中间人模式)

React中的状态提升概括来说,就是将多个组件需要共享的状态提升到它们最近 的父组件上.在父组件上改变这个状态然后通过props分发给子组件.

其实状态提升,说白了,就是找他们共同的爹

//状态提升
import React from 'react';

export default class State extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            msg: '我是父亲,共享数据'
        };
    }

    ChangeMsg = (text) => {
        this.setState({
            msg: text
        });
    }

    changMe = () => {
        this.setState({
            msg: '都消停点,不要传了'
        });
    }
    render() {
        return (
            <div>
                {/* 自定义事件,回调函数 */}
                <ChildOne role={this.state.msg} onChangeMsg={this.ChangeMsg}></ChildOne>
                <ChildTwo role={this.state.msg} onChangeMsg={this.ChangeMsg}></ChildTwo>
                <button onClick={this.changMe}>问候一下</button>
            </div>
        );
    }
}

class ChildOne extends React.Component {
    change = () => {
        this.props.onChangeMsg('我把数据传给弟弟');
    }
    render() {
        return (
            <div>
                <h1>我是大儿子 {this.props.role}</h1>
                <button onClick={this.change}>改变</button>
            </div>
        );
    }
}

class ChildTwo extends React.Component {
    change = () => {
        this.props.onChangeMsg('我把数据传给哥哥');
    }
    render() {
        return (
            <div>
                <h1>我是小儿子 {this.props.role}</h1>
                <button onClick={this.change}>改变</button>
            </div>
        );
    }
}

在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state。这就是所谓的状态提升

6.ref标记实现通信

一般不建议使用ref,因为ref这种方式,权限太大了,可以直接获取整个子组件 的对象

export default class App extends Component {
    render() {
        return (
            <div>
                <button onClick={() => {
                    console.log(this.refs.mychild.state.myname);
                    this.refs.mychild.handleEvent("父组件的问候");
                }}>子组件对象</button>
                {/* ref属性加载组件上,此时调用它的组件会获取到整个子组件的对象 */}
                <Child ref='mychild' />
            </div>
        )
    }
}

class Child extends Component {
    state = {
        myname: 'liuqiao'
    }
 	handleEvent = (text) => {
        console.log("父组件来了" + text);
        this.setState({
            myname: text
        });
    }
    render() {
        return (
            <div>
                我的名字:{this.state.myname}
            </div>
        )
    }
}

三.总结

1.父通子,子通父

父通子不用说,props传递,子通父,也简单,回调函数通信

2.非父子组件通信,跨级深

非嵌套组件的通信,这类组件的通信可以考虑通过发布订阅模式或者采用context实现

如果采用context,就是利用组件的共同父组件的context对象进行通信

3.兄弟通信

中间人模式,即状态提升!也就是利用父组件实现中转传递,但是会增加子组件和父组件之间的耦合度,如果嵌套深的话,不易找到父组件

所以,我认为非父子通信,跨级比较深的,最好的方式是 发布订阅模式!

猜你喜欢

转载自blog.csdn.net/liuqiao0327/article/details/107242329
今日推荐