问题背景
在 uniapp 开发中,我们经常需要在应用启动时(onLaunch
)执行一些异步操作(如获取 token、用户信息等),而这些数据又需要在页面生命周期(如 onLoad
、onShow
)中使用。
由于异步操作的特性,可能会导致页面生命周期执行时,onLaunch
中的异步操作还未完成。
如在 onLaunch 中进行登录后取得 token,项目各页面需要带上该 token 请求其他接口。
// App.vue
// 在 App.vue 的 onLaunch 生命周期请求获取token
onLaunch(() => {
uni.login({
success: async (res) => {
const { token } = await request('xxxx/login', {
code: res.code,
});
// 保存到缓存,请求其他接口时传入
uni.setStorageSync(TOKEN_KEY, token);
},
});
});
// 其他页面
onShow(async () => {
// 获取页面数据,该 getPageData 方法需要token
getPageData();
});
传统解决方案的局限性
现阶段网上都是使用 promise 来解决的,大体思路如下所示:
// main.js
app.config.globalProperties.$onLaunched = new Promise((resolve) => {
app.config.globalProperties.$isResolve = resolve;
});
// App.vue
<script setup>
import { getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance();
const { $isResolve } = proxy;
onLaunch(() => {
uni.login({
success: async (res) => {
const { token } = await request('xxxx/login', {
code: res.code,
});
$isResolve();
// 保存到缓存,请求其他接口时传入
uni.setStorageSync(TOKEN_KEY, token);
},
});
});
</script>
const { proxy } = getCurrentInstance();
// 其他页面
onShow(async () => {
// 等 vue 实例 $onLaunched resolve 后再执行获取页面数据的操作
await proxy.$onLaunched;
// 获取页面数据,该 getPageData 方法需要token
getPageData();
});
上面的方法虽然可以解决异步问题,但是使用起来却很麻烦。
想象下这种场景:
页面中需要依赖 onLaunch 获取的多个数据,上面的方法使用起来就捉襟见肘,并且很难维护
// App.vue
onLaunch(() => {
// 获取token
// 获取配置信息
// 获取用户信息
});
// 页面
onShow(() => {
getData1(); // 依赖onLaunch中获取的token
getData2(); // 依赖onLaunch中获取的配置信息和token
getData3(); // 依赖onLaunch中获取的token、配置信息、用户信息
});
常见的 Promise 解决方案虽然能解决问题,但存在以下不足:
- 代码冗余:每个页面都需要等待 Promise 解析
- 维护困难:当依赖多个异步数据时,代码复杂度急剧上升
- 可读性差:业务逻辑与异步等待逻辑混杂
- 扩展性弱:新增依赖项需要修改多处代码
解决方案(下载和使用)
custom-hooks-plus 提供了一种声明式的解决方案,通过自定义钩子将生命周期与数据状态绑定,优雅地解决了异步时序问题。
如下面这个自定义钩子的触发时机为 Token 的值不为空 + uniapp 的 onShow 生命周期。
onCustomShow(() => {
console.log('onShow+token');
}, 'Token');
下载
npm i custom-hooks-plus
createProxy 的作用(不推荐、推荐使用pinia的形式)
createProxy
的作用就是监听传入对象的变化。
// global.ts 文件
import { createProxy } from 'custom-hooks-plus'
interface GlobalData {
token: string
userInfo: number
}
export const globalData = createProxy({
token: '',
userInfo: {
name: ''
}
})
export function set<K extends keyof GlobalData>(key: K, val: GlobalData[K]) {
globalData[key] = val
}
export function get<K extends keyof GlobalData>(key: K): GlobalData[K] {
return globalData[key]
}
init 的作用
init 方法的定义为:
declare type PiniaWatchConfig = {
key: string;
type: 'pinia';
store: any;
onUpdate?: (val: any) => boolean;
};
declare type PromiseEntry = {
status: PromiseStatus;
resolve: Function;
type?: 'pinia' | 'default';
onUpdate?: (val: any) => boolean;
};
declare type WatchConfig = PiniaWatchConfig | DefaultWatchConfig;
declare type WatchConfigCollection = {
[key: string]: WatchConfig;
};
/**
*
* @param watchObject 监听的键
* @param target 传入的store
* @returns
*/
export declare function init(watchObject: WatchConfigCollection): void;
具体方法为:
// App.vue 中使用
import { init } from 'custom-hooks-plus';
import { useCounterStore } from '@/store/index';
// 推荐使用pinia
init({
Login: {
key: 'token', // 监听global文件中globalData的token的变化
onUpdate: (val) => {
return !!val;
},
},
UserInfo: {
key: 'userInfo', // 监听global文件中globalData的userinfo的变化
},
Name: {
key: 'userInfo.name', // 监听global文件中globalData的userinfo.name的变化
},
Count: {
key: 'counter', // 监听 useCounterStore 中 state 的 counter的变化
type: 'pinia',
store: useCounterStore(), // type传入pinia类型需要传入store实例
onUpdate: (val) => {
return val === 2;
}, // 更新条件为 val 等于 2
},
});
使用自定义生命周期钩子
// 页面中使用
<script setup lang="ts">
import { onCustomLoad, onCustomShow } from 'custom-hooks-plus';
onCustomLoad((options) => {
console.log('LoginUserInfo钩子执行-onCustomLoad1', options);
console.log('globalData的token和userInfo都被修改了才会触发');
}, ['Login', 'UserInfo']);
onCustomShow(() => {
console.log('LoginUserInfo钩子执行-onCustomShow2');
console.log('globalData的token和userInfo都被修改了才会触发');
}, ['Login', 'UserInfo']);
onCustomShow(() => {
console.log('UserInfoLogin钩子执行-onCustomShow3');
console.log('globalData的token被修改了才会触发');
}, ['Login']);
</script>
可用的自定义钩子
支持的自定义钩子 | 执行时机 |
---|---|
onCustomLaunch | 对应 uniapp 的 onLaunch |
onCustomLoad | 对应 uniapp 的 onLoad |
onCustomCreated | 渲染时机为 Vue2 的 created |
onCustomShow | 对应 uniapp 的 onShow |
onCustomMounted | 对应 uniapp 的 onMounted |
onCustomReady | 对应 uniapp 的 onReady |
总结
通过 custom-hooks-plus
提供的自定义钩子,我们能够优雅地解决 uniapp 中 onLaunch
与页面生命周期的异步问题。这种方法不仅提高了代码的可读性和可维护性,还为复杂的数据依赖场景提供了灵活的解决方案。相比传统的 Promise 方案,这种声明式的方法更符合现代前端开发的理念,能够有效提升开发效率和代码质量。