任意组件之间的通信
假设有如下需求:一个家族里一个爷爷两个爸爸,两个爸爸分别有两个儿子,这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()
这就实现了单向数据流
总结一下这个模式的特点:
- 所有的动作都要通过事件来沟通;
- 数据必须放在顶层
最后来看看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相比有什么优势呢?
- eventHub的事件名可以随意取,而在Redux里,事件名必须在
reducers
的列表里,对事件名和参数都做了约束,所有的事件都必须列在里面 - Redux的理念是所有的组件都不允许修改
store.getState()
里的数据..(虽然Redux没有做到,但是这个是js的问题,Redux的API还是暗示了,这个数据应该是只读的,不应该修改它)
网友评论