Oasis's Cloud

一个人的首要责任,就是要有雄心。雄心是一种高尚的激情,它可以采取多种合理的形式。
—— 《一个数学家的辩白》

NutUI React 在 Taro 中实现函数调用的方法


背景

在 NutUI React Taro 1.x 的实现中,由于微信小程序下不能动态插入标签,所以 NutUI React Taro 中的 Toast 组件采用的是标签方式的使用方法。

然而 Toast 标签的使用方式比较繁琐。此用法如下所示:

import React, {useState} from 'react'
import {Toast} from '@/packages/nutui.react.taro'

function Demo() {
  const [showToast, setShowToast] = useState(false)

  return (
    <Toast
      msg={'标签'}
      visible={showToast}
      onClose={() => {
        setShowToast(false)
      }}
    />
  )
}

在这个例子中,需要通过增加 state 的方法,来控制 Toast 组件的 visible,从而实现 toast 展示或隐藏。现在仅仅是控制 Toast 的显示或隐藏,在实际的业务中,还需要控制 Toast 的类型,Toast 的 msg。这样就需要增加多个 state。而标签组件的使用方式在进行需要处理跨组件的通信。从而进一步增加 Toast 的使用难度。

能不能通过给 Toast 增加 show() 方法来控制 Toast 的展示?

function OtherComponent() {
  return <Toast />
}
function Demo() {
  return (
    <Button onClick={() => Toast.show()}>Click me</Button>
  )
}

实现

Toast.show 方法主要的责任是通知 Toast 调用 state 相关的处理逻辑,将 Toast 组件展示出来。在众多设计模式中,发布订阅模式提供了这类问题的解决模板。

在发布订阅模式中,我们使用一个中介者(事件总线)来管理发布者和订阅者之间的通信。示例如下:

class EventBus {
  constructor() {
    this.subscribers = {};
  }

  subscribe(event, callback) {
    if (!this.subscribers[event]) {
      this.subscribers[event] = [];
    }
    this.subscribers[event].push(callback);
  }

  publish(event, data) {
    if (this.subscribers[event]) {
      this.subscribers[event].forEach(callback => callback(data));
    }
  }
}

// 用法
const eventBus = new EventBus();

eventBus.subscribe("message", data => {
  console.log(`Received message: ${data}`);
});

eventBus.publish("message", "Hello subscribers!");

Taro 提供了 Taro.Events,借助 Taro.EVents 可以实现发布订阅模式中的中介者(事件总线)。 我们先来看下 Taro.Events 的使用方法,以便直观的对照发布订阅模式的 Demos。下面代码来自 Taro 文档

import Taro, { Events } from '@tarojs/taro'

const events = new Events()

// 监听一个事件,接受参数
events.on('eventName', (arg) => {
  // doSth
})

// 监听同个事件,同时绑定多个 handler
events.on('eventName', handler1)
events.on('eventName', handler2)
events.on('eventName', handler3)

// 触发一个事件,传参
events.trigger('eventName', arg)

// 触发事件,传入多个参数
events.trigger('eventName', arg1, arg2, ...)

// 取消监听一个事件
events.off('eventName')

// 取消监听一个事件某个 handler
events.off('eventName', handler1)

// 取消监听所有事件
events.off()

在 Toast 组件的实现中,消息的名称通过 router + id 的方式处理。而且为了便于使用,将 Taro.Events 封装成了名为 useCustomEvent 的 hook。

export const customEvents = new Events()
function useCustomEvent(selector, cb) {
  useEffect(() => {
    customEvents.on(path, cb)
    return () => {
      customEvents.off(path)
    }
  }, [])
}

这样 Toast 组件中可以直接通过 useCustomEvent 订阅事件的处理逻辑

useCustomEvent(
  porps.id,
  ({ status }) => {
    if (status) {
      show()
    } else {
      hide()
    }
  }
)

Toast.show 方法可以通过 customEvents 事件总线发布消息。

function show(selector) {
  const path = useCustomEventsPath(selector)
  customEvents.trigger(path, { status: true })
}

以上是 Toast 实现函数调用的演示,代码细节可以查看 NutUI React 仓库