手把手教你解决Taro中非页面组件时,全局store的数据改变后,组件不更新的问题。

222 阅读2分钟

在taro中,我们常用mobx作为全局的store解决方案,它提供的makeAutoObservable可以将类中的成员变量转成一个响应式数据,然后通过其提供的observer方法,将组件传入这个方法,当响应式数据有变化时observer就会执行其传入的函数。但由于小程序的限制,这只能用于页面级的组件,一个普通的组件即便使用observer包裹,是不能触发重新渲染的。对于这个问题,我们完全可以借助hooks结合订阅发布的方式来搞定它。

如何做订阅发布呢,首先我们得知道数据什么时候变化这个问题,而mobx提供了reaction方法,这就像一个监视器一样,你可以传入要观察得数据,和数据变化后得处理函数,如下:

reaction(
  () => 观察的数据,
  (res) => {
    数据变化后的回调函数
  }
);

我们定义一个简单全局store为例,设置一个用于保存订阅含糊的map,当数据变化时,我们在reaction的回调中调用订阅的函数,将新的数据传递给订阅函数即可实现数据发布订阅的功能,如下:

class Store {
  userInfo = new UserInfo();
  /**
   * 事件通道
   */
  eventChannelMap: {[key: string]: Function} = {};
  
  constructor() {
    makeAutoObservable(this); // 自动将所有属性和方法转换为 observable/action
    reaction(
      () => this.userInfo,
      (userInfo) => {
        const keys = Object.keys(this.eventChannelMap);
        keys.forEach(key => {
          if (key.includes('userInfo')) this.eventChannelMap[key](userInfo);
        })
      }
    );
  };
}
// 我们再提供一个订阅方法,也就是订阅方调用这个方法传入要订阅处理函数,如下:
/**
 * 注册事件监听
 */
registerEvent = (key: string, callback: Function) => {
    this.eventChannelMap[key] = callback;
};

接下来我们如何实现,让界面变化呢?其实也很简单,因为组件内的state变化会触发组件的重新渲染,所以我们只需要实现一个hook方法,这个方法专门用来定义state,然后我们再这个hook函数中订阅需要变化的数据即可,当数据变化时,修改hook函数中的state, 将状态作为返回值即可,在我们要使用的组件中引入使用这些状态,当hooks函数的状态改变时,便会触发组件状态的更新,如下:

import { store } from '@/store';
import { useRef, useState } from 'react';
import { uuid } from '@/utils';
import Taro from '@tarojs/taro';

const useGlobalState = () => {

  const keyId = useRef(uuid());

  const [ state, setState ] = useState({
    userInfo: store.userInfo,
    checkAuthDialogVisible: store.checkAuthDialogVisible,
    imMsgCount: websocket.getUnreadCount(),
  });
  
  store.registerStateChangeEvent(`userInfo_${keyId.current}`, (userInfo) => {
    setState({ ...state, userInfo });
  });
  store.registerStateChangeEvent(`checkAuthDialogVisible_${keyId.current}`, (checkAuthDialogVisible) => {
    setState({ ...state, checkAuthDialogVisible });
  });
  websocket.registerImMsgCountListener(`navBarKey_${keyId.current}`, (count: number) => {
    setState({ ...state, imMsgCount: count });
  })
  
  const authorization = Taro.getStorageSync('Authorization');

  return { ...state, authorization };
};

export default useGlobalState;

在我们要使用的非页面组件中使用定义的状态,状态改变了,就会触发页面的更新:

import useGlobalState from '@/hooks/useGlobalState';
const UserInfoCard = () => {
    const { userInfo } = useGlobalState();
    
    return <View>{userInfo.userName}</View>
}

如果有用记得给个小心心哦~