0x000 概述

这一章讲组件间通信,组件通信分为很多种:

  • 父组件向子组件发起通信
  • 子组件向父组件发起通信
  • 兄弟组件间通讯
  • 远程组件通信

在组件通信中,有一种错误的说法,那就是父组件调用子组件,子组件调用父组件,这种说法其实是错误的,并没有什么父组件调用子组件,这是一种传统的说法,从传统dom的操作方式来的,但是在react中,使用这种方法描述组件间的通信是不合适的。在我看来,用事件通知模型来描述可能更合适。

0x001 父组件向子组件发起通信

父组件向子组件发起通信很简单,直接使用props
import React from 'react'
import ReactDom from 'react-dom'

class Sub extends React.Component {
    constructor() {
        super()
        this.state = {
            num: 0
        }
    }

    componentWillReceiveProps(nextProps) {
        this.setState({
            num: nextProps.num
        })
    }

    render() {
        return <div>
            <p>props.num:{this.props.num}</p>
            <p>state.num:{this.state.num}</p>
        </div>
    }
}

class App extends React.Component {
    constructor() {
        super()
        this.state = {
            num: 0
        }
        setInterval(() => {
            this.setState({
                num: ++this.state.num
            })
        }, 1000)

    }

    render() {
        return <Sub num={this.state.num}/>
    }
}

ReactDom.render(
    <App></App>,
    document.getElementById('app')
)

查看浏览器:
图片描述

说明:App组件每秒更新一次num,同时将num通过props传递给子组件SubSub通过props访问父级传递下来的num,并渲染到界面上,同时也将该参数保存到自己的state中,也渲染到界面上。可以看到,父级更新了num,子组件也同步更新了,也就完成了父组件向子组件发起通信的目的。

0x002 子组件向父组件发起通信

子组件向父组件发起通信则是通过事件触发,我们可以想想平时的html

<button onclick="ourFunc('you click me')">确认</button>

我们给button绑定了click事件,所以当我们点击按钮的时间,该事件就会被触发,然后执行我们的函数ourFunc并传递参数'you click me',这就是事件通知。现在我们假设button是我们实现的自定义组件,那么onclick就是我们传递的props。众所周知,js中,函数也是变量,所以在react中,props也可以传递函数:

import React from 'react'
import ReactDom from 'react-dom'

class MyButton extends React.Component {
    constructor() {
        super()
        this.state = {
            num: 0
        }
    }

    handleOnClick() {
        let num = ++this.state.num
        this.setState({
            num: num
        })
        this.props.onClick(num)
    }

    render() {
        return <button onClick={() => this.handleOnClick()}>{this.props.text}</button>
    }
}

class App extends React.Component {
    constructor() {
        super()
        this.state = {
            num: 0
        }
    }

    render() {
        return <div>
            <p>点击了 {this.state.num} 次</p>
            <MyButton text='确认' onClick={(text) => this.handleOnClick(text)}/>
        </div>
    }

    handleOnClick(num) {
        this.setState({
            num: num
        })
    }
}

ReactDom.render(
    <App></App>,
    document.getElementById('app')
)

查看浏览器:
图片描述

说明:我们声明了一个MyButton组件,并传递了两个属性,一个是text,一个是onClick,注意,这里的onClick只是一个名字叫做onClick的属性,而不是点击事件,只是我们借用了这个名字而已,真正的点击事件必须绑定在dom上。所以这里叫什么名字都无所谓,不过为了好看,还是叫着名字比较好。
MyButton中,我们真正的绑定了onClick事件,并在该事件触发的时候,调用传递过来的props.onClick,并吧点击的次数作为参数,这样,ApphandleOnClick就会被促发,同时接收到num参数,用于显示用户的点击次数。
从表面上看,我们就好像在MyButton上绑定了事件一般,和web中的十分类似,但是这种机制更加的灵活,比如我们不一定要叫onClick,我们可以将之和业务绑定,叫做onUserAdd,这样,似乎就实现了自定义事件,并且具有一定的业务特点,非常的灵活。

0x003 兄弟组件通信

兄弟组件通信其实很简单,借助公共的父组件作为中间桥梁,然后通过上面说的props和事件机制,就完成了兄弟组件的通信,
稍微修改一下0x002的栗子就可以搞定了

class MyView extends React.Component {
    render() {
        return <p>点击了 {this.props.num} 次</p>
    }
}

class MyButton extends React.Component {
    constructor() {
        super()
        this.state = {
            num: 0
        }
    }

    handleOnClick() {
        let num = ++this.state.num
        this.setState({
            num: num
        })
        this.props.onClick(num)
    }

    render() {
        return <button onClick={() => this.handleOnClick()}>{this.props.text}</button>
    }
}

class App extends React.Component {
    constructor() {
        super()
        this.state = {
            num: 0
        }
    }

    render() {
        return <div>
            <MyView num={this.state.num}/>
            <MyButton text='确认' onClick={(text) => this.handleOnClick(text)}/>
        </div>
    }

    handleOnClick(num) {
        this.setState({
            num: num
        })
    }
}

ReactDom.render(
    <App></App>,
    document.getElementById('app')
)

查看浏览器:
图片描述

说明:MyButtonMyView是挂载在App的子组件,他们是同级的兄弟组件,而他们之间的通信可以借助App作为中转,将数据从一个MyButton传递到MyView。具体过程:

  • MyButton点击事件触发后触发父组件传递进来的onClick
  • App监听MyButtononClick事件并将该事件的num保存到自己的state
  • state.num又传递到了MyView

所以当我们点击按钮的时候,点击次数就增加了。

0x004 远程组件通信

从上克制,组件间的通信可以通过props传递普通变量和函数来实现,但是如果事件嵌套的层级太深,这套模型就显得麻烦,必须要不断的传递props,不但冗余而且不好维护。
当然我们也可以通过适当的组件设计来避免过深的组件嵌套通信。
那如果无法避免呢?
那就需要用其他方式来实现了,比如,使用订阅-发布模型可以实现无视距离和组件的通信,甚至无视框架和位置,但是很容易引起混乱,适用于全局通知。
以下是简单实现:

  • 实现通用的订阅-发布模型框架
let eventMap = {}

class MyEvent {

    static pub(name, data) {
        if (!eventMap.hasOwnProperty(name)) return
        let callbacks = eventMap[name]
        if (callbacks.length === 0) return
        callbacks.forEach((callback) => {
            callback(data)
        })


    }

    static sub(name, callback) {
        let callbacks = []
        if (eventMap.hasOwnProperty(name)) {
            callbacks = eventMap[name]
        }
        callbacks.push(callback)
        eventMap[name] = callbacks
    }
}

export default MyEvent

该框架只有两个方法,和一个事件队列,pub用来发布一个事件,sub用来订阅一个事件,调用pub发布一个事件以后,sub了该事件的方法就会被执行

MyEvent.sub('EventA',(data)=>{console.log(data)})
MyEvent.pub('EventA',{num:1})
// 查看控制台
{num:1}
  • 编写组件

const EVENT_BUTTON_CLICK = 'EVENT_BUTTON_CLICK'

class MyView extends React.Component {
    constructor() {
        super()
        this.state = {
            num: 0
        }
        MyEvent.sub(EVENT_BUTTON_CLICK, (num) => {
            this.setState({
                num: num.num
            })
        })

    }

    render() {
        return <p>点击了 {this.state.num} 次</p>
    }
}

class MyButton extends React.Component {

    constructor() {
        super()
        this.state = {
            num: 0
        }
    }

    handleOnClick() {
        let num = ++this.state.num
        this.setState({
            num: num
        })
        MyEvent.pub(EVENT_BUTTON_CLICK, {num: num})
    }

    render() {
        return <button onClick={() => this.handleOnClick()}>{this.props.text}</button>
    }
}

class App extends React.Component {
    constructor() {
        super()
    }

    render() {
        return <div>
            <MyView/>
            <MyButton text='确认'/>
        </div>
    }


}

ReactDom.render(
    <App></App>,
    document.getElementById('app')
)

可以看到,我们使用的是发布-订阅模型,并没有通过父组件来做兄弟组件的传递中介,没有太多的props嵌套。所以这就解决了远程组件通信、深度组件嵌套的问题。当然也带来了另一个问题,那就是事件维护。如果事件太多,将会导致无法维护和流程混乱......不可乱用,可用于系统的中心事件控制,也就是单一的事件源输出。

组件间通信可以选择像redux这样的库来处理。

0x005 资源


followWinter
1.5k 声望82 粉丝

暂时没有