# 09_消息订阅发布

📢 大家好,我是Milo

📢 这篇文章是学习 React 中 GitHub 搜索案例(消息订阅发布)的学习笔记

# 引言

在上一节写的 Github 案例中,我们采用的是 axios 发送请求来获取数据,同时我们需要将数据从 Search 组件传给 App,再由 App 组件将数据传给 List 组件,这个过程会显得多此一举。

同时我们要将 state 状态存放在 App 组件当中,但是这些 state 状态都是在 List 组件中使用的。而在 Search 组件中做的,只是更新这些数据。这样也会显得很没有必要,我们完全可以将 state 状态存放在 List 组件中,但是这样我们又会遇到技术难题,兄弟组件间的数据通信。

这里我们就学习一下如何利用 消息订阅发布 来解决 兄弟组件间的通信

# 一、消息发布订阅

我们先简单的说一下消息订阅和发布的机制

就拿我们平常订杂志来说,我们和出版社说我们要订一年的足球周刊,那每次有新的足球周刊,它都会寄来给你。

换到代码层面上,我们订阅了一个消息假设为 A,当另一个人发布了 A 消息时,因为我们订阅了消息 A ,那么我们就可以拿到 A 消息,并获取数据

那我们要怎么实现呢?

# 1 pubsub-js

我们需要先安装这个库

yarn add pubsub-js
1

引入这个库 ,哪里用哪里引入

import PubSub from 'pubsub-js'
1

# 2 订阅消息

我们通过 subscribe 来订阅消息,它接收两个参数,第一个参数是消息的名称,第二个是消息成功的回调,回调中也接受两个参数,一个是消息名称,一个是返回的数据

PubSub.subscribe('search',(msg,data)=>{
  console.log(msg,data);
})
1
2
3

# 3 发布消息

我们采用 publish 来发布消息,用法如下

PubSub.publish('search',{name:'tom',age:18})
1

# 4 举例

比如在Github搜索案例中,将 App 组件中的所有状态和方法全部去除

在Search组件中重写 search 函数,发布消息

// Search/index.jsx
search = () => {
  console.log('Search组件发布消息了')
  PubSub.publish('atguigu',{name:'tom',age:18})
}
1
2
3
4
5

在List组件中,在componentDidMount()钩子中订阅消息

componentDidMount(){
  PubSub.subscribe('atguigu',(_,data)=>{
    console.log('List组件收到数据了',data)
  })
}
1
2
3
4
5

效果展示:

输入123按下搜索键,显示发布消息与订阅消息

jGuQns.png

# 二、重写Github搜索案例

有了这些基础,我们可以完善我们昨天写的 GitHub 案例

# 1 去除App中的初始化状态和数据传递函数

有了消息订阅发布,我们可以将 App 组件中的所有状态和方法全部去除,因为本来就不是在 App 组件中直接使用这些方法的,App 组件只是一个中间媒介而已

# 2 初始化状态

之前 state 状态存放在 App 组件当中,但是这些 state 状态都是在 List 组件中使用的,所以我们将状态放在 List 组件里

在List组件中,初始化状态

// List/index.jsx
// 初始化状态,users初始值为数组
state = {
  users: [], // users初始化为数组
  isFirst: true, // 是否为第一次打开页面
  isLoading: false, // 标识是否处于加载中
  err: '', // 存储请求相关错误信息
}
1
2
3
4
5
6
7
8

# 3 重写search函数发布消息

在Search组件重写 search 函数,完整代码如下:

// Search/index.jsx
search = () => {
  // 获取用户的输入
  const { keyWordElement: { value: keyWord } } = this
  // 发送请求前通知List更新状态
  PubSub.publish('search',{ isFirst: false, isLoading: true })
  // 发送网络请求
  axios.get(`/api1/search/users?q=${keyWord}`).then(
    response => {
      // 请求成功后通知List更新状态
      PubSub.publish('search',{ isLoading: false, users: response.data.items })
    },
    error => {
      // 请求失败通知List更新状态
      PubSub.publish('search',{ isLoading: false, err: error.message })
    }
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

⚠️注意重写的部分:将数据的更新通过 publish 来传递

例如:在发送请求之前,我们需要出现 loading 字样

// 之前的写法
this.props.updateAppState({ isFirst: false, isLoading: true })
// 改为发布订阅方式
PubSub.publish('search',{ isFirst: false, isLoading: true })
1
2
3
4

# 4 页面挂载后订阅消息

在List组件中订阅这个消息即可,并将返回的数据用于更新状态即可

⚠️注意:这里写在 componentDidMount() 钩子里,页面一挂载就订阅消息,从而调用setState,触发更新

// List/index.jsx
componentDidMount(){
  PubSub.subscribe('search',(msg,stateObj)=>{
    this.setState(stateObj)
  })
}
1
2
3
4
5
6

同时上面的代码会返回一个 token ,这个就类似于定时器的编号的存在,我们可以通过这个 token 值,来取消对应的订阅

# 5 页面卸载时取消订阅

在页面要卸载时,在 componentWillUnmount() 钩子里,通过 unsubscribe 来取消指定的订阅

// List/index.jsx
componentWillUnmount(){
  PubSub.unsubscribe(this.token)
}
1
2
3
4

以上就是 Github 搜索案例的改进过程

最终效果图:

# 三、扩展 -- Fetch

首先 fetch 也是一种发送请求的方式,它是在 xhr 之外的一种,我们平常用的 Jquery 和 axios 都是封装了 xhr 的第三方库,而 fetch 是官方自带的库,同时它也采用的是 Promise 的方式,大大简化了写法

如何使用呢?

fetch('http://xxx')
  .then(response => response.json())
  .then(json => console.log(json))
  .catch(err => console.log('Request Failed', err)); 
1
2
3
4

它的使用方法和 axios 非常的类似,都是返回 Promise 对象,但是不同的是, fetch 关注分离,它在第一次请求时,不会直接返回数据,会先返回联系服务器的状态,在第二步中才能够获取到数据

我们需要在第一次 then 中返回 response.json() 因为这个是包含数据的 promise 对象,再调用一次 then 方法即可实现

我们可以利用 asyncawait 配合使用,来简化代码

可以将 await 理解成一个自动执行的 then 方法,这样清晰多了

async function getJSON() {
  let url = 'https://xxx';
  try {
    let response = await fetch(url);
    return await reasponse.json();
  } catch (error) {
    console.log('Request Failed', error);
  }
}
1
2
3
4
5
6
7
8
9

最后关于错误对象的获取可以采用 try...catch 来实现

关于 fetch 的更多内容

强烈推荐阮一峰老师的博文:fetch (opens new window)

# 四、Github 搜索案例相关知识点总结

  1. 设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。

  2. ES6小知识点:解构赋值+重命名

let obj = {a:{b:1}}
const {a} = obj; //传统解构赋值
const {a:{b}} = obj; //连续解构赋值
const {a:{b:value}} = obj; //连续解构赋值+重命名
1
2
3
4
  1. 消息订阅与发布机制
    • 先订阅,再发布(理解:有一种隔空对话的感觉)
    • 适用于任意组件间通信
    • 要在组件的componentWillUnmount中取消订阅
  2. fetch发送请求(关注分离的设计思想)
try {
  const response= await fetch(`/api1/search/users2?q=${keyWord}`)
  const data = await response.json()
  console.log(data);
} catch (error) {
  console.log('请求出错',error);
}
1
2
3
4
5
6
7

非常感谢您的阅读,欢迎提出你的意见,有什么问题欢迎指出,谢谢!🎈