上节课我们对GitHub搜索案例做了一个完善,这节课我们来讲一讲消息订阅与发布技术PubSub

回顾

在我们开始这节课的内容之前,我们来对上节课的知识点来做一个简单的回顾。

  • 我们通过一个函数来集合多个更改state的操作
  • 不肯用直接展示错误对象,要展示对象信息

以上便是上节课的主要要点,那么接下来我们开始对PubSub的学习

什么是PubSub

当我们刚看到这个词的时候是不是下意识地会有一个疑问,什么是PubSub

大家说我们现在state都是在那个组件里的?是不是App组件?为什么要放在App组件啊?因为这个state是不是我们SearchList组件都要共用的啊?

为什么共用就一定要放在App组件里呢?我们是不是在之前的案例中还没有学会如何在平级的组件之间来相互传递数据?所以说必须放在父组件中,由父组件统一维护。这也太烦人了啊。

那么我们真的就没法在平级组件之间传递数据吗?其实是有的,就是我们这节课要讲的PubSub技术。

我们这节课呢就借着之前的GitHub搜索案例来往下聊一聊。

消息订阅-发布机制

首先我们PubSub是一个机制,怎么理解呢?比如说订牛奶,现在可能订牛奶的也不多了,我现在住的小区还有住户在订牛奶。那么订牛奶的流程是什么?

  1. 联系商家,交钱,说好地址以及订哪一种牛奶
  2. 工作人员送牛奶上门

那么这个订阅消息和订牛奶差不多

  1. 确定消息名
  2. 发布消息

安装PubSub

其实这个消息订阅发布机制是有很多库都可以实现的,我们选用的是PubSubJS那么就让我们要用这个库,那就要安装,那么我们在命令行里执行npm i pubsub-js来完成安装。

使用方法

那么我们安装好了这个库怎么使用呢?我们来看一下:

image-20220112153638608

这是GitHub中给的文档,其实我们使用的库都快要在GitHub中找到,那么我们来看这两个例子,首先第一步,文档告诉我们怎么引入PubSub,而且把ESCJS的语法都给我们写出来了。

然后我们来看下面的基础案例:

首先定义了一个订阅者叫mySubscriber,这个订阅者是个函数,那么这个函数做了什么呢?先是接收两个参数,一个是消息,一个是数据,这俩是什么我们目前还不知道,然后打印了这两个参数。

在往下看,定义了一个token,调用了Pubsub.subscribe方法,并且传了两个参数,第一个参数就是我们之前说的消息名,第二个参数就是我们定义的订阅者。而且这里传进来的是订阅者的这个函数,并没有调用,那么什么意思?是不是作为回调传进来的?

这一步操作是个什么意思呢,就是说一旦我们有指定消息名的这个消息发布了之后,那么订阅者的这个回调就会被调用。

那么回调被调用的时候都接收到了什么参数呢?也就是说我们的订阅者中的msg参数和data参数到底是个啥啊?其实msg就是消息名,data就是数据,什么意思呢?比如我们有两个组件ABA想要使用B中的数据,那么就要在A中来订阅。然后在B中发布在A中订阅的的消息。这样的话A中就能拿到B发布的消息名以及A想要使用的数据。

那么还有一个问题,为什么我们要定义一个token变量?如果我们这个组件准备卸载掉,不再订阅了那么我们局可以通过这个token来取消订阅。

那么再往下看,我们怎么发布消息呢?就是下面这一行,PubSub.publish方法,这个方法也要接收两个参数,一个是消息名,因为你不传详细名我们根本不知道你发布的到底是什么消息啊。第二个参数就是携带的数据。

订阅关系分析

那么我们大概了解了PubSub的这么一个使用流程,那么我们就在我们的案例中来使用一下试试吧

我们首先来考虑一个问题,在GitHub搜索的这个案例之中,我们有两个子组件,SearchList,而且这两个组件之间是需要共用一套数据的,那么我们是不是可以这这两个组件之间使用PubSub来传递数据?

那么问题来了,这两个组件,谁订阅谁?我们来想一下,我们这个数据是不是在List组件中展示的?那么List要用的数据是哪来的?是不是Search组件通过ajax请求获取到的?那么如果要在这两个组件之间传递数据,是不是要Search组件把数据给List?而 PubSub机制是不是发布者把数据发给订阅者?那么是不是应该List组件订阅Search组件?

应用

当我们确定了订阅关系之后,我们来尝试应用一下吧,我们先一步一步来:

export default class App extends Component {
  render() {
    return (
      <div className="container">
        <Search />
        <List />
      </div>
    );
  }
}

我们来先看App组件既然我们准备让SearchList组件自己来互相传递数据,那么App组件还要有state吗?当然不用,而且state也没了,我们Search也不需要修改App中的state了啊,那么我们之前的updateAppState方法是不是就可以删了?渲染组件的时候也不需要再传props了。

那么还有一点。我们不传了List是不是还要有初始展示的啊?初始化展示以及搜索之后更新页面是不是还是要通过state?那么好:

export default class List extends Component {
  state = {
    users: [],
    isFirst: true,
    isLoading: false,
    err: ""
  };
  render() {
    const { users,isFirst,isLoading,err } = this.state;
    return (...);
  }
}

我们来看List组件,我们页面更新和初始展示都要根据state来,那么我们是不是要把之前App组件中的state拿到List中就行了?那么好现在,我们来看一下页面效果到底行吗?我们这说得跟真的一样,那么我们来看看吧:

我们是不是可以成功把我们的初始展示页面给展示出来了?那么我们现在搜索功能是不是还不行?因为我们把updateAppState都删了那么我们点击搜索的化肯定会报错的啊。而且现在我们是要通过PubSub来实现SearchList的数据传递,那么我们首先是不是要定义订阅者?

但是我们在什么时候来订阅合适呢?我们两个案例一写,大家是不是把生命周期都给忘了?我们现在既然要订阅消息,是不是要List组件刚一挂载完成就订阅才最好?那么是不是到了我们之前所说过的componentDidMount出场的时候了

export default class List extends Component {
  state = {...};

  componentDidMount() {
    this.token = PubSub.subscribe("search", (_, stateObj) => {
      console.log(stateObj);
    });
  }
  componentWillUnmount(){PubSub.unsubscribe(this.token);}

  render() {...}
}

我们来看我们的componentDidMount钩子,我们订阅search这个消息,大家看我们是不是没有先定义订阅者的回调函数啊?我们直接在传参的时候直接用箭头函数来写回调就行了,没必要还单独定义一下。那么我们来看我买的回调函数,是不是就接受了msg和数据,然后在控制台把msg和数据输出一下。既然订阅了,我们肯定就要取消订阅,那么在什么时候取消订阅呢?是不是在组件即将要被卸载的时候取消,那么componentWillUnmount钩子就派上用场了。大家如果生命周期有所遗忘的话,赶紧回去做一些复习。那么我们已经完成订阅了,那么是不是要在Search组件中来发布消息了啊?

export default class Search extends Component {
  render() {...}
  search = () => {
    PubSub.publish('search', { name: "jingxun", age: 18 });
  };
}

我们来看Search组件,我其他不变,把search方法给改一下,我们现在只是测试PubSub这个环境能不能走得通,我们不着急上来就真刀真枪地拿数据去干。我们来看一下我们把search方法我们发布了search消息,因为我们订阅的就是search消息,那么这边肯定要发布search消息,这样我们的List才能拿到数据啊。然后我们带个数据,是一个对象。那么这会儿我们来看一下起作用不:

我们来看,控制台中是不是打印除了我们发布的消息名以及消息带的数据啊?这就说明了List组件中订阅者函数被执行了。

那么现在我们上数据吧:

// List 组件
export default class List extends Component {
  state = {...};

  componentDidMount() {
    PubSub.subscribe("search", (_, stateObj) => {
      this.setState(stateObj);
    });
  }

  render() {...}
}
// Search 组件
export default class Search extends Component {
  render() {...}
  search = () => {
    const { value: keyword } = this.user;
    PubSub.publish('search', { isFirst: false, isLoading: true });
    axios.get(`http://localhost:3000/api1/search/users?q=${keyword}`).then(
      response => PubSub.publish('search', { 
        users: response.data.items, isLoading: false }),
      error => PubSub.publish('search', { err: error.message, isLoading: false })
    )
  };
}

首先我们在List组件中也别在控制台输出了,直接我们接收一个state对象,然后来更新state是不是就好了?然后Search组件中search方法,我们是不是把之前updateAppState的操作全部换成了发布消息的操作了?这样的话我们是不是就可以通过消息发布来更改Lits组件的state从而来实现页面的动态更新?那么我们来看一下效果吧:

初始状态没问题,然后来搜索一下:

搜索中的Loading也没问题,那么看一下搜索结果:

搜索结果也没有问题,那么错误时呢?

这些显示结果都是正常的。

以上便是PubSub的主要内容

总结

  • 使用PubSub完成组件之间数据通信
  • 使用PubSub分两步
  • 确定消息名
  • 发布消息
  • 任何组件如果要接收数据,那么就是订阅者
  • 订阅消息的话要在组件刚挂载完成就订阅

Copyright statement:The articles of this site are all original if there is no special explanation, indicate the source please when you reprint.

Link of this article:https://work.lynchow.com/article/subscriptions_pubsub/