React03-eventHub与Redux

任意组件之间的通信
假设有如下需求:一个家族里一个爷爷两个爸爸,两个爸爸分别有两个儿子,这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还是暗示了,这个数据应该是只读的,不应该修改它)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,273评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,349评论 3 398
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,709评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,520评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,515评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,158评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,755评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,660评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,203评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,287评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,427评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,122评论 5 349
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,801评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,272评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,393评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,808评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,440评论 2 359

推荐阅读更多精彩内容