美文网首页
React03-eventHub与Redux

React03-eventHub与Redux

作者: 我也不知道啊丶 | 来源:发表于2019-01-07 23:55 被阅读0次

    任意组件之间的通信
    假设有如下需求:一个家族里一个爷爷两个爸爸,两个爸爸分别有两个儿子,这4个儿子共用一笔家庭基金,需要在其中一个儿子花钱的时候通知到另外所有人
    效果如下


    点击一次花钱按钮表示花掉了100块钱
    儿子2花掉了100块,所以大家显示余额9900

    先来看看实现方法

    function Son1(props){
        return (
            <div className='son'>
                儿子1
                <button className='btn' onClick={props.spend}>花钱</button>
                <h1>{props.money}</h1>
            </div>
        )
    }
    function Son2(props){
        return (
            <div className='son'>
                儿子2
                <button className='btn' onClick={props.spend}>花钱</button>
                <h1>{props.money}</h1>
            </div>
        )
    }
    function Son3(props){
        return (
            <div className='son'>
                儿子3
                <button className='btn' onClick={props.spend}>花钱</button>
                <h1>{props.money}</h1>
            </div>
        )
    }
    function Son4(props){
        return (
            <div className='son'>
                儿子4
                <button className='btn' onClick={props.spend}>花钱</button>
                <h1>{props.money}</h1>
            </div>
        )
    }
    class BigPapa extends React.Component{
        constructor(props){
            super(props)
            this.state = {
    
            }
        }
        render(){
            return (
                <div className='papa'>
                    大爸
                    <Son1 money={this.props.money} spend={this.props.spend}/>
                    <Son2 money={this.props.money} spend={this.props.spend}/>
                </div>
            )
        }
    }
    class YoungPapa extends React.Component{
        constructor(props){
            super(props)
            this.state = {
    
            }
        }
        render(){
            return (
                <div className='papa'>
                    小爸
                    <Son3 money={this.props.money} spend={this.props.spend}/>
                    <Son4 money={this.props.money} spend={this.props.spend}/>
                </div>
            )
        }
    }
    
    class App extends React.Component{
        constructor(props){
            super(props)
            this.state = {
                money : 10000
            }
        }
        spend(){
            this.setState({
                money : this.state.money - 100
            })
        }
        render(){
            return (
                <div className='main'>
                    <BigPapa money={this.state.money} spend={this.spend.bind(this)}/>
                    <YoungPapa money={this.state.money} spend={this.spend.bind(this)}/>
                </div>
            )
        }
    }
    
    ReactDOM.render(
        <App/>,
        document.querySelector('#root')
    )
    

    可以看到,如果要实现组件之间的通信,必须要把状态(数据)和方法放到最顶层(也就是爷爷-App)上,然后一层层传下去。
    如果层级不多不复杂,使用这种方法还行,但是一旦层级过多,这样就会显得非常繁琐,而且容易出错,所以我们换一种思路,使用发布订阅模式(eventHub)

    订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。
    将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相应对象间的一致性,这样会给维护、扩展和重用都带来不便。当一个对象的改变需要同时改变其他对象,而且它不知道具体有多少对象需要改变时,就可以使用订阅发布模式了。
    一个抽象模型有两个方面,其中一方面依赖于另一方面,这时订阅发布模式可以将这两者封装在独立的对象中,使它们各自独立地改变和复用。订阅发布模式所做的工作其实就是在解耦合。让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另一边的变化。

    举个例子理解吧

    现实事例
      不论是在程序世界里还是现实生活中,发布—订阅模式的应用都非常广泛
      比如,小明最近看上了一套房子,到了售楼处之后才被告知,该楼盘的房子早已售罄。好在售楼处工作人员告诉小明,不久后还有一些尾盘推出,开发商正在办理相关手续,手续办好后便可以购买。但到底是什么时候,目前还没有人能够知道。于是小明记下了售楼处的电话,以后每天都会打电话过去询问是不是已经到了购买时间。除了小明,还有小红、小强、小龙也会每天向售楼处咨询这个问题。一个星期过后,该工作人员决定辞职,因为厌倦了每天回答1000个相同内容的电话
      当然现实中没有这么笨的销售公司,实际上故事是这样的:小明离开之前,把电话号码留在了售楼处。售楼处工作人员答应他,新楼盘一推出就马上发信息通知小明。小红、小强和小龙也是一样,他们的电话号码都被记在售楼处的花名册上,新楼盘推出的时候,售楼处工作人员会翻开花名册,遍历上面的电话号码,依次发送一条短信来通知他们
      在上面的例子中,发送短信通知就是一个典型的发布—订阅模式,小明、小红等购买者都是订阅者,他们订阅了房子开售的消息。售楼处作为发布者,会在合适的时候遍历花名册上的电话号码,依次给购房者发布消息
      使用发布—订阅模式有着显而易见的优点:购房者不用再天天给售楼处打电话咨询开售时间,在合适的时间点,售楼处作为发布者会通知这些消息订阅者;购房者和售楼处之间不再强耦合在一起,当有新的购房者出现时,他只需把手机号码留在售楼处,售楼处不关心购房者的任何情况,不管购房者是男是女还是一只猴子。而售楼处的任何变动也不会影响购买者,比如售楼处工作人员离职,售楼处从一楼搬到二楼,这些改变都跟购房者无关,只要售楼处记得发短信这件事情
    DOM事件
      发布—订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案。比如,可以订阅ajax请求的error、succ等事件。或者如果想在动画的每一帧完成之后做一些事情,可以订阅一个事件,然后在动画的每一帧完成之后发布这个事件。在异步编程中使用发布—订阅模式,就无需过多关注对象在异步运行期间的内部状态,而只需要订阅感兴趣的事件发生点
      发布—订阅模式可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口。发布—订阅模式让两个对象松耦合地联系在一起,虽然不太清楚彼此的细节,但这不影响它们之间相互通信。当有新的订阅者出现时,发布者的代码不需要任何修改;同样发布者需要改变时,也不会影响到之前的订阅者。只要之前约定的事件名没有变化,就可以自由地改变它们

    实际上最简单的订阅就是监听DOM事件,比如

    button.on('click',function(data){ data === 'x' }) //订阅
    //就是订阅了button的click事件,一旦发生了就通知我,调用function
    button.trigger('click','x') //发布
    

    看看改写之后的代码

    var money = {
        amount : 10000
    }
    var fnLists = {}
    var eventHub = { //我是管家
        trigger(eventName,data){ //发布事件
            let fnList = fnLists[eventName]
            if(!fnList){ return }
            for(let i = 0; i<fnList.length; i++){
                fnList[i](data)
            }
        },
        on(eventName,fn){ //订阅事件
            if(!fnLists[eventName]){
                fnLists[eventName] = []
            }
            fnLists[eventName].push(fn)
        }
    }
    
    class Son1 extends React.Component{
        constructor(props){
            super(props)
            this.state = {
                money : money
            }
            eventHub.on('我花钱了',(data) => {
                this.setState({
                    money : money
                })
            })
        }
        spend(){
            money.amount -= 100
            eventHub.trigger('我花钱了',100)
            this.setState({
                money : money
            })
        }
        render(){
            return (
                <div className='son'>
                    儿子1
                    <button className='btn' onClick={() => this.spend()}>花钱</button>
                    <h1>{money.amount}</h1>
                </div>
            )
        }
    }
    class Son2 extends React.Component{
        constructor(props){
            super(props)
            this.state = {
                money : money
            }
            eventHub.on('我花钱了',(data) => {
                this.setState({
                    money : money
                })
            })
        }
        spend(){
            money.amount -= 100
            eventHub.trigger('我花钱了',100)
            this.setState({
                money : money
            })
        }
        render(){
            return (
                <div className='son'>
                    儿子2
                    <button className='btn' onClick={() => this.spend()}>花钱</button>
                    <h1>{this.state.money.amount}</h1>
                </div>
            )
        }
    }
    class Son3 extends React.Component{
        constructor(props){
            super(props)
            this.state = {
                money : money
            }
            eventHub.on('我花钱了',(data) => {
                this.setState({
                    money : money
                })
            })
        }
        spend(){
            money.amount -= 100
            eventHub.trigger('我花钱了',100)
            this.setState({
                money : money
            })
        }
        render(){
            return (
                <div className='son'>
                    儿子3
                    <button className='btn' onClick={() => this.spend()}>花钱</button>
                    <h1>{money.amount}</h1>
                </div>
            )
        }
    }
    class Son4 extends React.Component{
        constructor(props){
            super(props)
            this.state = {
                money : money
            }
            eventHub.on('我花钱了',(data) => {
                this.setState({
                    money : money
                })
            })
        }
        spend(){
            money.amount -= 100
            eventHub.trigger('我花钱了',100)
            this.setState({
                money : money
            })
        }
        render(){
            return (
                <div className='son'>
                    儿子4
                    <button className='btn' onClick={() => this.spend()}>花钱</button>
                    <h1>{money.amount}</h1>
                </div>
            )
        }
    }
    

    可以看到4个儿子都通过eventHub.on()订阅了一个叫'我花钱了'的事件,一旦监听到这个事件,就代表有人花钱了,于是就更新一下账户余额,并且4个儿子花钱时都会发布一个我花钱了事件来通知其他人。
    这种方法虽然比最开始那种层层传递的方法要好一些,但是,如果其他人也要随时知道“还剩多少钱呢?”那么所有人都要订阅这个消息
    干脆这样吧,所有人都不准随便花钱(不自己存money),需要付钱的时候就通知一个管家来付,然后由管家把余额告诉爷爷(App),由App通知其他人

    var money = {
        amount : 10000
    }
    var fnLists = {}
    var eventHub = {
        trigger(eventName,data){
            let fnList = fnLists[eventName]
            if(!fnList){ return }
            for(let i = 0; i<fnList.length; i++){
                fnList[i](data)
            }
        },
        on(eventName,fn){
            if(!fnLists[eventName]){
                fnLists[eventName] = []
            }
            fnLists[eventName].push(fn)
        }
    }
    var x = { //我是管家
        init(){
            eventHub.on('我想花钱',function(data){
                money.amount -= data
                render()
            })
        }
    }
    x.init()
    class Son1 extends React.Component{
        constructor(props){
            super(props)
        }
        spend(){
            eventHub.trigger('我想花钱',100)
        }
        render(){
            return (
                <div className='son'>
                    儿子1
                    <button className='btn' onClick={() => this.spend()}>花钱</button>
                    <h1>{this.props.money.amount}</h1>
                </div>
            )
        }
    }
    class Son2 extends React.Component{
        constructor(props){
            super(props)
        }
        spend(){
            eventHub.trigger('我想花钱',200)
        }
        render(){
            return (
                <div className='son'>
                    儿子2
                    <button className='btn' onClick={() => this.spend()}>花钱</button>
                    <h1>{this.props.money.amount}</h1>
                </div>
            )
        }
    }
    class Son3 extends React.Component{
        constructor(props){
            super(props)
        }
        spend(){
            eventHub.trigger('我想花钱',300)
        }
        render(){
            return (
                <div className='son'>
                    儿子3
                    <button className='btn' onClick={() => this.spend()}>花钱</button>
                    <h1>{this.props.money.amount}</h1>
                </div>
            )
        }
    }
    class Son4 extends React.Component{
        constructor(props){
            super(props)
        }
        spend(){
            eventHub.trigger('我想花钱',400)
        }
        render(){
            return (
                <div className='son'>
                    儿子4
                    <button className='btn' onClick={() => this.spend()}>花钱</button>
                    <h1>{this.props.money.amount}</h1>
                </div>
            )
        }
    }
    class BigPapa extends React.Component{
        constructor(props){
            super(props)
        }
        render(){
            return (
                <div className='papa'>
                    大爸<span>{this.props.money.amount}</span>
                    <Son1 money={this.props.money}/>
                    <Son2 money={this.props.money}/>
                </div>
            )
        }
    }
    class YoungPapa extends React.Component{
        constructor(props){
            super(props)
        }
        render(){
            return (
                <div className='papa'>
                    小爸<span>{this.props.money.amount}</span>
                    <Son3 money={this.props.money}/>
                    <Son4 money={this.props.money}/>
                </div>
            )
        }
    }
    
    class App extends React.Component{
        constructor(props){
            super(props)
            this.state = {
                money : money
            }
        }
        render(){
            return (
                <div className='main'>
                    <BigPapa money={this.state.money}/>
                    <YoungPapa money={this.state.money}/>
                </div>
            )
        }
    }
    
    function render(){
        ReactDOM.render(
            <App />,
            document.querySelector('#root')
        )
    }
    render()
    

    这就实现了单向数据流
    总结一下这个模式的特点:

    1. 所有的动作都要通过事件来沟通;
    2. 数据必须放在顶层

    最后来看看Redux

    在Redux里,store是用来存所有数据的地方

    var store = {
        money : money,
        user : user
    }
    

    trigger()叫做action
    它的事件名叫做action.type,后面的参数叫action.payload
    数据的变动叫做reducer
    数据的订阅on()叫做subscribe

    用刚刚的例子就是:



    这里由于我们是用的<script src=''>引入的redux,所有要这样写
    // Redux
    let createStore = Redux.createStore
    let reducers = (state,action) => {
        state = state || {
            money : {amount : 10000}
        }
        switch (action.type){
            case '我想花钱':
                return {
                    money : {
                        amount : state.money.amount - action.payload
                    }
                }
            default:
                return state
        }
    }
    const store = createStore(reducers)
    
    class Son1 extends React.Component{
        constructor(props){
            super(props)
        }
        spend(){
            store.dispatch({type:'我想花钱',payload:100})
        }
        render(){
            return (
                <div className='son'>
                    儿子1
                    <button className='btn' onClick={() => this.spend()}>花钱</button>
                    <h1>{this.props.money.amount}</h1>
                </div>
            )
        }
    }
    class Son2 extends React.Component{
        constructor(props){
            super(props)
        }
        spend(){
            store.dispatch({type:'我想花钱',payload:200})
        }
        render(){
            return (
                <div className='son'>
                    儿子2
                    <button className='btn' onClick={() => this.spend()}>花钱</button>
                    <h1>{this.props.money.amount}</h1>
                </div>
            )
        }
    }
    class Son3 extends React.Component{
        constructor(props){
            super(props)
        }
        spend(){
            store.dispatch({type:'我想花钱',payload:300})
        }
        render(){
            return (
                <div className='son'>
                    儿子3
                    <button className='btn' onClick={() => this.spend()}>花钱</button>
                    <h1>{this.props.money.amount}</h1>
                </div>
            )
        }
    }
    class Son4 extends React.Component{
        constructor(props){
            super(props)
        }
        spend(){
            store.dispatch({type:'我想花钱',payload:400})
        }
        render(){
            return (
                <div className='son'>
                    儿子4
                    <button className='btn' onClick={() => this.spend()}>花钱</button>
                    <h1>{this.props.money.amount}</h1>
                </div>
            )
        }
    }
    class BigPapa extends React.Component{
        constructor(props){
            super(props)
        }
        render(){
            return (
                <div className='papa'>
                    大爸<span>{this.props.money.amount}</span>
                    <Son1 money={this.props.money}/>
                    <Son2 money={this.props.money}/>
                </div>
            )
        }
    }
    class YoungPapa extends React.Component{
        constructor(props){
            super(props)
        }
        render(){
            return (
                <div className='papa'>
                    小爸<span>{this.props.money.amount}</span>
                    <Son3 money={this.props.money}/>
                    <Son4 money={this.props.money}/>
                </div>
            )
        }
    }
    
    class App extends React.Component{
        constructor(props){
            super(props)
        }
        render(){
            return (
                <div className='main'>
                    <BigPapa money={this.props.store.money}/>
                    <YoungPapa money={this.props.store.money}/>
                </div>
            )
        }
    }
    
    function render(){
        ReactDOM.render(
            <App store={store.getState()}/>,
            document.querySelector('#root')
        )
    }
    render()
    store.subscribe(render)
    

    如果想花钱,就得store.dispatch({type:'我想花钱',payload:100})
    事件由reducers来监听,要传入两个参数,一个是之前的state,一个是你的动作action
    所有组件里都不存储state,从最顶级的App那里通过store={store.getState()}传入

    这么看起来,Redux很是啰嗦,那么它跟之前的eventHub相比有什么优势呢?

    1. eventHub的事件名可以随意取,而在Redux里,事件名必须在reducers的列表里,对事件名和参数都做了约束,所有的事件都必须列在里面
    2. Redux的理念是所有的组件都不允许修改store.getState()里的数据..(虽然Redux没有做到,但是这个是js的问题,Redux的API还是暗示了,这个数据应该是只读的,不应该修改它)

    相关文章

      网友评论

          本文标题:React03-eventHub与Redux

          本文链接:https://www.haomeiwen.com/subject/xnvprqtx.html