在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>
}
如果有用记得给个小心心哦~