This commit is contained in:
王鹏
2025-08-14 14:52:01 +08:00
commit 0c2edb6036
1551 changed files with 41152 additions and 0 deletions

21
node_modules/spa-custom-hooks/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 鹅鹅鹅
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

161
node_modules/spa-custom-hooks/README.EN.md generated vendored Normal file
View File

@@ -0,0 +1,161 @@
<div align="center">
<img src="https://photo.zastatic.com/images/common-cms/it/20220106/1641464912638_340742_t.png"/>
<h1 align="center"> spa-custom-hooks </h1>
[![license](https://img.shields.io/badge/license-%20MIT-blue.svg)](https://github.com/1977474741/spa-custom-hooks/blob/main/LICENSE ) [![license](https://img.shields.io/npm/v/spa-custom-hooks?color=red)](https://www.npmjs.com/package/spa-custom-hooks ) [![license](https://img.shields.io/bundlephobia/min/spa-custom-hooks)](https://www.npmjs.com/package/spa-custom-hooks) [![ license](https://img.shields.io/github/last-commit/1977474741/spa-custom-hooks)](https://github.com/1977474741/spa-custom-hooks/commits/main) [ ![license](https://img.shields.io/github/stars/1977474741?style=social)](https://github.com/1977474741)
**Simplified Chinese | [English](./README.EN.md)**
</div>
- [What is spa-custom-hooks? ](#head1)
- [ what's it for? ](#head2)
- [Common application scenarios](#head3)
- [Example of use](#head4)
- [Registration parameter description](#head5)
- [Register CustomHook](#head6)
- [diyHooks object description](#head7)
- [Hook usage rules](#head8)
- [Built-in native hook](#head9)
- [Demo QR Code](#head10)
- [Into the group exchange](#head11)
## <span id="head1">What is spa-custom-hooks? </span>
- A thing that can customize page hooks, you can register global asynchronous tasks, define the triggering conditions of the hooks yourself, and automatically execute the relevant hooks in the page when the conditions are met.
- Supports the use of created, mounted, etc. with vue's native hooks.
- Support vue architecture (including uni-app, wepy, mpvue, etc.) and various small programs.
## <span id="head2"> What is it for? </span>
Solve the problem of page logic relying on global asynchronous data in a simple and elegant way
## <span id="head3"> Common application scenarios</span>
````javascript
export default {
name:'Home',
onCreatedLogin(){
//Successful login (get the token) && page initialization completed
//Tips: Suitable for scenarios where the request sent by a page depends on the token
},
onCreatedUserInfo(){
//Page initialization is complete && Obtaining user information is complete
//Tips: Suitable for scenarios where user information needs to be used to make judgments when the page is initialized, and then go to the page logic
},
onMountedUserInfo(){
//dom rendering completed && access user information completed
//Tips: Suitable for similar scenes where the avatar needs to be rendered on the canvas when entering the page for the first time
},
onReadyShow(){
//The page rendering in the applet is completed && page display
//Tips: Suitable for scenarios where you need to obtain small program components or dom, and will be executed every time the page is displayed
},
}
````
## <span id="head4"> Usage example</span>
*[Click to view the snippet of applet code](https://developers.weixin.qq.com/s/pUQ4Xkma79xd)*
```javascript
//The first step is to install the plug-in:
npm install spa-custom-hooks
//The second step is to register the plug-in in the entry file:
import CustomHook from'spa-custom-hooks';
const diyHooks = {
'UserInfo':{
name:'UserInfo',
watchKey:'userinfo',
deep: true,
onUpdate(val){
// If the nickName in userinfo is not empty, it means that the hook is hit
return !!val.nickName;
}
}
}
//1. Registration method of vue architecture
import store from'./store'
Vue.use(CustomHook ,diyHooks,store)
//2. Registration method of native applet
//Define globalData in advance
const globalData = {
userinfo: {
nickName:''
}
}
CustomHook.install(diyHooks,globalData)
//The third step is to use plug-ins in the business page (any page can be used, with low coupling and less repetitive code):
onLoadUserInfo(){
//Can render canvas
renderCanvas();
}
```
## <span id="head5"> Registration parameter description</span>
#### <span id="head6"> Sign up for CustomHook</span>
````javascript
//vue architecture-main.js
import store from'./store'
import CustomHook from'spa-custom-hooks';
Vue.use(CustomHook,diyHooks,store)
//Native applet architecture-app.js
import CustomHook from'spa-custom-hooks';
CustomHook.install(diyHooks,globalData)
````
#### <span id="head7"> diyHooks object description</span>
````javascript
{
//1. Register the attribute monitoring hook
//UserInfo, hook single name, initial letter capitalized
'UserInfo':{
//name, the full name of the hook, it can be the same as the key above if the monitoring attribute is required, it is required
name:'UserInfo',
//The attribute name in the store to be monitored by watchKey (equivalent to $store.state.userinfo), the attribute monitoring hook mode is required
watchKey:'userinfo',
//Whether to hit by default, not required
hit: false,
//deep Whether to monitor deeply, not required
deep: true,
//The callback executed when the onUpdate property is changed is used to determine whether to hit this hook. It is not required. The default value is equivalent to returning!! val
onUpdate(val){
//This means that userinfo contains nickName and hits this hook. Note that you cannot return asynchronously
return !!val.nickName;
}
},
//2. Register event listener hook
//BeforeMount, hook single name, first letter capitalized
'BeforeMount':{
//name, the name of the native hook, used to hit this hook, required
name:'beforeMount',
//destroy, the opposite hook name, used to cancel the hit, the event listener hook is required
destroy:'destroyed',
//Whether to hit by default, not required
hit: false
}
}
````
## <span id="head8"> Hook usage rules</span>
````javascript
`on{UserInfo}{BeforeMount}{Login}{Position}...` //All registered hooks can be matched at will, the arrangement order does not affect the execution of the hooks, they are all in && relationship
````
## <span id="head9"> Registered life cycle hooks</span>
````javascript
Launch, Created, Load, Attached, Show, Mounted, Ready
//↓↓↓If you need other hooks, you can register by yourself↓↓↓(If a hook of the current framework and its corresponding opposite hook are inconsistent with the following configuration, you also need to manually register, for example, wepy has created but not destroyed)
````
- [Detailed configuration of registered hooks](https://github.com/1977474741/spa-custom-hooks/blob/main/lib/spa-custom-hooks/hooks.js)
- [diyHooks object description](#head7)
## <span id="head10"> Demo QR code</span>
![left image description here](https://pubser-res.zhenai.com/other/temp/202103/20/16460141027094.png?imageMogr2/thumbnail/200x200)
## <span id="head11"> Join group communication</span>
![left image description here](https://pubser-res.zhenai.com/other/temp/202103/20/17024414117439.png?imageMogr2/thumbnail/203x203)
If you have any good suggestions, please raise issues or pr
Click a star if you like

171
node_modules/spa-custom-hooks/README.md generated vendored Normal file
View File

@@ -0,0 +1,171 @@
<div align="center">
<img src="https://photo.zastatic.com/images/common-cms/it/20220106/1641464912638_340742_t.png"/>
<h1 align="center"> spa-custom-hooks </h1>
[![license](https://img.shields.io/badge/license-%20MIT-blue.svg)](https://github.com/1977474741/spa-custom-hooks/blob/main/LICENSE) [![license](https://img.shields.io/npm/v/spa-custom-hooks?color=red)](https://www.npmjs.com/package/spa-custom-hooks) [![license](https://img.shields.io/bundlephobia/min/spa-custom-hooks)](https://www.npmjs.com/package/spa-custom-hooks) [![license](https://img.shields.io/github/last-commit/1977474741/spa-custom-hooks)](https://github.com/1977474741/spa-custom-hooks/commits/main) [![license](https://img.shields.io/github/stars/1977474741?style=social)](https://github.com/1977474741)
**简体中文 | [English](./README.EN.md)**
</div>
- [spa-custom-hooks 是什么?](#head1)
- [ 它有什么用?](#head2)
- [ 常见应用场景](#head3)
- [ 使用示例](#head4)
- [ 注册参数说明](#head5)
- [ 注册 CustomHook](#head6)
- [ diyHooks 对象说明](#head7)
- [ 钩子使用规则](#head8)
- [ 已经内置的原生钩子](#head9)
- [ Demo 二维码](#head10)
- [ 进群交流](#head11)
## <span id="head1">spa-custom-hooks 是什么?</span>
- 一个可以定制页面钩子的东西,你可以注册全局的异步任务,自己定义钩子的触发条件,满足条件时即可自动执行页面里相关的钩子。
- 支持和 Vue 的原生钩子 createdmounted 等随意搭配使用。
- 支持 Vue 架构(包括 uni-app、wepy、mpvue 等)以及各种小程序(微信、支付宝、字节等)。
- 最新版本支持了所有原生小程序组件使用,包括 lifetimes 和 pageLifetimes 钩子,单独对 uniapp 钩子做了适配。
## <span id="head2"> 它有什么用?</span>
- 用简单优雅的方式处理全局数据和页面内逻辑之间的关系,大多为异步问题。
- 对生命周期钩子的组合增强,解决页面内逻辑执行时机问题。
## <span id="head3"> 常见应用场景</span>
```javascript
export default {
name: 'Home',
onCreatedLogin() {
//登录成功拿到token && 页面初始化完成
//Tips适用于某页面发送的请求依赖token的场景
},
onCreatedUserInfo() {
//页面初始化完成 && 获取用户信息完成
//Tips适用于页面初始化时需要用到用户信息去做判断再走页面逻辑的场景
},
onMountedUserInfo() {
//dom渲染完成 && 获取用户信息完成
//Tips适用于首次进入页面需要在canvas上渲染头像的类似场景
},
onReadyShow() {
//小程序内页面渲染完成 && 页面显示
//Tips适用于需要获取小程序组件或者dom并且每次页面显示都会执行的场景
},
};
```
## <span id="head4"> 使用示例</span>
_[点击查看小程序代码片段](https://developers.weixin.qq.com/s/pUQ4Xkma79xd)_
```javascript
//第一步,安装插件:
npm install spa-custom-hooks
//第二步,入口文件里注册插件:
import CustomHook from 'spa-custom-hooks';
const diyHooks = {
'UserInfo':{
name:'UserInfo',
watchKey: ['userinfo'],
deep: true,
onUpdate(val){
//userinfo里的nickName非空则表示命中此钩子
return !!val.nickName;
}
}
}
//1.vue架构的注册方式
import store from './store'
Vue.use(CustomHook ,diyHooks,store)
//2.原生小程序的注册方式
//提前定义globalData
const globalData = {
userinfo: {
nickName: ''
}
}
CustomHook.install(diyHooks,globalData)
//第三步,业务页面里使用插件(任何页面都可以使用,耦合度低,重复性代码少):
onLoadUserInfo(){
//可以渲染canvas了
renderCanvas();
}
```
## <span id="head5"> 注册参数说明</span>
#### <span id="head6"> 注册 CustomHook</span>
```javascript
//vue架构-main.js
import store from './store';
import CustomHook from 'spa-custom-hooks';
Vue.use(CustomHook, diyHooks, store);
//原生小程序架构-app.js
import CustomHook from 'spa-custom-hooks';
CustomHook.install(diyHooks, globalData);
```
#### <span id="head7"> diyHooks 对象说明</span>
```javascript
{
//1.注册属性监听钩子
//注册的钩子单名,首字母大写
'UserInfo':{
//name钩子全称监听属性的话可以和上面的key一致必填
name:'UserInfo',
//watchKey要监听的store里的属性名(相当于state.userinfo),属性监听钩子模式必填 string | Array<string>
watchKey: ['userinfo'],
//是否默认命中,非必填
hit: false,
//deep是否深度监听非必填
deep: true,
//onUpdate属性改变时执行的callback用来决定是否要命中此钩子非必填缺省值相当于返回了!!val
onUpdate(val){
//这里表示userinfo里含有nickName则命中此钩子。注意不可以异步return
return !!val.nickName;
}
},
//2.注册生命周期监听钩子
//注册的钩子单名,首字母大写
'BeforeMount':{
//name要监听的原生钩子名用来命中此钩子必填
name:'beforeMount',
//destroy相反的钩子名用来取消命中事件监听钩子必填
destroy:'destroyed',
//是否默认命中,非必填
hit: false,
//用来规定当和其他生命周期钩子同时满足条件时的执行顺序数值小的先执行可以参考hooks.js
weightValue: 4,
}
}
```
## <span id="head8"> 钩子使用规则</span>
```javascript
`on{UserInfo}{BeforeMount}{Login}{Position}...`; //所有注册好的钩子都可以随意搭配,排列顺序不影响钩子执行,都是 && 的关系
```
## <span id="head9"> 已经注册好的生命周期钩子</span>
- [已注册的钩子详细配置](https://github.com/1977474741/spa-custom-hooks/blob/main/lib/spa-custom-hooks/hooks.js)
- [ diyHooks 对象说明](#head7)
## <span id="head10"> Demo 二维码</span>
![left image description here](https://photo.zastatic.com/images/common-cms/it/20220531/1653983381580_599944_t.png?imageMogr2/thumbnail/200x200)
## <span id="head11"> 进群交流</span>
![left image description here](https://pubser-res.zhenai.com/other/temp/202103/20/17024414117439.png?imageMogr2/thumbnail/203x203)
如果有什么好的建议欢迎提 issues 或者提 pr
喜欢的话点个 star

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,178 @@
import { Hooks, nativeHooks, getDiyHooks } from './hooks.js';
import { getVal, getStore } from './utils.js';
/**
* @Author 鹅鹅鹅
* @DateTime 2023-12-01
* @description 自定义钩子
*/
export default class customHook {
constructor({ instance, options, pageHooks, instanceType, initHookName }) {
// 页面实例、原生钩子对象
this.instance = instance;
// 实例类型 app,page,component
this.instanceType = instanceType;
// 进行实例化的钩子名称
this.initHookName = initHookName;
// 是否是组件
this.isComponent = instanceType === 'component';
// 页面内需要处理的所有钩子构成和函数执行状态
this.customHooks = {};
// 页面内需要处理的所有钩子数组
this.customHookArr = [];
// 所有钩子对象注册的所有钩子的hookEntity类集合
this.hook = {};
// url里的参数
this.options = options || {};
// 钩子对象
this.pageHooks = pageHooks;
// 自定义的属性监听钩子
this.diyHooks = getDiyHooks();
this.init();
//检测是否有需要立即触发的钩子
this.triggerHook();
}
init() {
// 提出需要注入的自定义钩子
let hook = Hooks(this, this.initHookName);
// 当前实例支持的所有钩子
this.presetHooksName = Object.keys(hook);
this.hook = hook;
let pageHooks = this.pageHooks;
// 钩子对象在自身还是原型链
let oneself = pageHooks.hasOwnProperty('beforeCreate') || pageHooks.hasOwnProperty('onReady');
pageHooks = oneself ? pageHooks : pageHooks['__proto__'];
if (this.isComponent) {
// 把lifetimes和pageLifetimes合并过来一起处理
Object.assign(pageHooks, pageHooks.lifetimes, pageHooks.pageLifetimes);
}
// 过滤钩子对象、分析钩子构成
let { customHookArr, hookInscape } = this.filterHooks(pageHooks);
// 单独处理每个钩子
customHookArr.forEach((e) => {
this.customHooks[e] = {
// 此钩子实体函数
callback: pageHooks[e].bind(this.instance),
// 此钩子构成
inscape: hookInscape[e],
// 此钩子是否已执行
execute: false,
// 决定执行顺序的累加权重值,小的在前先执行
weightValue: hookInscape[e].reduce((total, hookKey) => {
return (total += this.hook[hookKey]?.weightValue || 0);
}, 0),
};
});
// 按weightValue进行排序
this.customHookArr = customHookArr.sort((a, b) => {
return this.customHooks[a].weightValue - this.customHooks[b].weightValue;
});
// await Promise.resolve();
Object.keys(this.hook).forEach((e) => this.hook[e].init());
}
/**
* 过滤出钩子对象
* @params Object<key,function> 页面所有钩子对象列表
* return {
* customHookArr Array<string> 自定义钩子数组
* hookInscape Object<key,Array<string>> 自定义钩子构成
* }
*/
filterHooks(option) {
// 各钩子和包含的所有已注册单独钩子构成
let hookInscape = {};
// 筛选出来用到的hook实例
const filterHook = {};
const customHookArr = Object.keys(option).filter((e) => {
// 不符合规则的钩子不予处理
let hookArr = this.getHookArr(e);
if (hookArr.length) {
//过滤掉未注册的钩子
hookInscape[e] = hookArr.filter((h) => {
if (this.hook[h]) {
filterHook[h] = this.hook[h];
return true;
}
console.warn(
`[custom-hook 错误声明警告] "${h}"钩子未注册,意味着"${e}"可能永远不会执行请先注册此钩子再使用文档https://github.com/1977474741/spa-custom-hooks#-diyhooks对象说明`
);
return false;
});
//格式 + 是否注册的效验
return e == 'on' + hookArr.join('') && hookInscape[e].length == hookArr.length;
}
return false;
});
// 只保留用到的hook实例
this.hook = filterHook;
return {
customHookArr,
hookInscape,
};
}
/**
* 检测是否有需要触发的钩子
* @params <string> 本次变化的钩子key
*/
triggerHook(hitKey) {
this.customHookArr.forEach((name) => {
let customHook = this.customHooks[name];
let meet = customHook.inscape.every((e) => this.hook[e] && this.checkHookHit(this.hook[e]));
if (meet && !customHook.execute) {
customHook.execute = true;
this.customHooks[name]['callback'](this.options);
}
});
}
/**
* 清除关于本次destroy钩子的所有包含此钩子的执行状态
* @params <string> 要清除的钩子名称
*/
resetExecute(destroyHookName) {
this.customHookArr.forEach((pageHookName) => {
let customHook = this.customHooks[pageHookName];
if (customHook.inscape.find((hookName) => destroyHookName === this.hook[hookName]?.destroy)) {
customHook.execute = false;
}
});
}
/**
* 检验此钩子是否满足命中条件
* @param hookEntity实例
* return <boolean> 是否满足
*/
checkHookHit(hookEntity) {
if (hookEntity.watchKey) {
//注意instance对象必须从传进来的实体里拿不能this.instance
let val = getVal(getStore(hookEntity.__customhook.instance).state, hookEntity.watchKey);
return hookEntity.onUpdate ? hookEntity.onUpdate(val) : val;
} else {
return hookEntity.hit;
}
}
/**
* 获取组合钩子包含的所有钩子(包含多于一个钩子 || 单个自定义属性钩子)、是的话返回钩子构成
* @param <string> 钩子名称
* return Array<string> 钩子构成
*/
getHookArr(name) {
if (name.indexOf('on') == -1) return [];
const hookArr = this.splitHook(name),
diyHooks = this.diyHooks;
return hookArr.length > 1 || diyHooks.indexOf(hookArr[0]) != -1 ? hookArr : [];
}
/**
* 分析组合钩子构成
* @param <string> 组合钩子名称
* return Array<string> 钩子构成数组
*/
splitHook(name) {
name = name.replace('on', '');
// 将钩子数组按照长度进行降序排序,以确保匹配最长的字符串优先
this.presetHooksName.sort((a, b) => b.length - a.length);
// 使用正则表达式将钩子数组中的每个元素都转换为对应的捕获组然后通过join函数连接起来
let regex = new RegExp('(' + this.presetHooksName.join('|') + ')', 'g');
// 使用split函数根据正则表达式对组合钩子进行分割并过滤掉无效的部分
return name.split(regex).filter((e) => this.hook[e]);
}
}

View File

@@ -0,0 +1,64 @@
/**
* @Author 鹅鹅鹅
* @description 单个hook处理工厂
*/
import { getStore, getVal } from './utils.js';
export default class hookEntity {
constructor({ customhook, name, destroy, hit = false, watchKey, onUpdate, type, weightValue }) {
// 钩子名
this.name = name;
// 相反钩子名
this.destroy = destroy;
// 钩子类型 app、page、component
this.type = type;
// hit是在是生命周期钩子的情况下才有用属性监听钩子是检测时triggerHook实时判断的没有用hit属性
this.hit = hit;
// 需要监听的key
this.watchKey = watchKey;
// 权重值,决定了和其他钩子同时满足条件时执行先后顺序
this.weightValue = weightValue;
// 是否已经初始化
this.initFlag = false;
// 属性监听回调
this.onUpdate = onUpdate;
this.__customhook = customhook;
}
init() {
if (this.initFlag) return;
if (this.watchKey) {
this.unwatchFn = this.watchAttr((success) => {
this[success ? 'cycleStart' : 'cycleEnd']();
});
}
this.initFlag = true;
}
cycleStart() {
if (this.hit) return;
this.hit = true;
this.__customhook && this.__customhook.triggerHook(this.name);
}
cycleEnd() {
if (!this.hit) return;
this.hit = false;
this.__customhook && this.destroy && this.__customhook.resetExecute(this.destroy);
}
watchAttr(cb) {
try {
const that = this;
const store = getStore(this.__customhook.instance);
const unwatchFn = store.watch(
(state) => {
return getVal(state, that.watchKey);
},
(val, oldval) => {
cb(that.onUpdate ? that.onUpdate(val, oldval) : val);
},
{
//兼容mini-polyfill
watchKey: that.watchKey,
}
);
return unwatchFn;
} catch (err) {}
}
}

View File

@@ -0,0 +1,232 @@
import hookEntity from './hook-entity.js';
// 注册的自定义钩子
let diyHooks = {};
// 需要mixin的原生钩子
const nativeHooks = [
// vue
'beforeCreate',
'created',
'onPageShow',
'beforeMount',
'mounted',
'activated',
'onPageHide',
'deactivated',
'beforeDestroy',
'destroyed',
// app和page
'onLaunch',
'onLoad',
'onShow',
'onReady',
'onHide',
'onUnload',
];
// 原生组件钩子
const componentHooks = ['onInit', 'created', 'attached', 'ready', 'didMount', 'detached', 'didUnmount'];
const lifetimesHooks = ['created', 'attached', 'ready', 'detached'];
const pageLifetimesHooks = ['show', 'hide', 'routeDone'];
// 原生所有钩子
const nativeAllHooks = [...new Set([...nativeHooks, ...componentHooks])];
/**
* 输入hooks配置项所有hook经过hookEntity处理输出hookEntity对象列表
* @param1 <customhook>实例化对象
* @param2 <string> 初始化的钩子名
* return Object<key,hookEntity> 所有注册的hook对应的hookEntity对象列表
*/
const Hooks = (customhook, initHookName) =>
Object.keys(diyHooks).reduce(
(hooks, key) => {
const diyHook = diyHooks[key];
diyHook.customhook = customhook;
return (hooks[key] = new hookEntity(diyHook)) && hooks;
},
{
// vue钩子
BeforeCreate: new hookEntity({
customhook,
name: 'BeforeCreate',
destroy: 'destroyed',
hit: true,
weightValue: 2,
}),
BeforeMount: new hookEntity({
customhook,
name: 'beforeMount',
destroy: 'destroyed',
weightValue: 4,
}),
PageShow: new hookEntity({
customhook,
name: 'onPageShow',
destroy: 'onPageHide',
weightValue: 4.1,
}),
Mounted: new hookEntity({
customhook,
name: 'mounted',
destroy: 'destroyed',
weightValue: 5,
}),
Activated: new hookEntity({
customhook,
name: 'activated',
destroy: 'deactivated',
weightValue: 6,
}),
PageHide: new hookEntity({
customhook,
name: 'onPageHide',
destroy: 'onPageShow',
weightValue: 6.1,
}),
Deactivated: new hookEntity({
customhook,
name: 'deactivated',
destroy: 'activated',
weightValue: 7,
}),
// 兼容页面的钩子和组件的钩子名相似的情况比如各种小程序的页面是onShow组件却是show但他们的key在这里都是Show为了防止key冲突故而加判断分开注册保证key的唯一性也统一了页面和组件钩子的声明方式
...(!customhook.isComponent
? {
Launch: new hookEntity({
customhook,
name: 'onLaunch',
destroy: 'onUnload',
hit: true,
weightValue: 3,
}),
Created: new hookEntity({
customhook,
name: 'created',
destroy: 'destroyed',
hit: initHookName === 'created',
weightValue: 3,
}),
Load: new hookEntity({
customhook,
name: 'onLoad',
destroy: 'onUnload',
hit: initHookName === 'onLoad',
weightValue: 4,
}),
Ready: new hookEntity({
customhook,
name: 'onReady',
destroy: 'onUnload',
weightValue: 5,
}),
Show: new hookEntity({
customhook,
name: 'onShow',
destroy: 'onHide',
weightValue: 6,
}),
Hide: new hookEntity({
customhook,
name: 'onHide',
destroy: 'onShow',
weightValue: 7,
}),
}
: {
// 原生小程序组件 包含的钩子
Init: new hookEntity({
customhook,
name: 'onInit',
destroy: 'didUnmount',
hit: initHookName === 'onInit',
weightValue: 2,
}),
Created: new hookEntity({
customhook,
name: 'created',
destroy: 'detached',
hit: initHookName === 'created',
weightValue: 3,
}),
Attached: new hookEntity({
customhook,
name: 'attached',
destroy: 'detached',
weightValue: 4,
}),
Show: new hookEntity({
customhook,
name: 'show',
destroy: 'hide',
weightValue: 5,
}),
DidMount: new hookEntity({
customhook,
name: 'didMount',
destroy: 'didUnmount',
hit: initHookName === 'didMount',
weightValue: 6,
}),
Ready: new hookEntity({
customhook,
name: 'ready',
destroy: 'detached',
weightValue: 7,
}),
// 微信小程序组件所在页面的生命周期
RouteDone: new hookEntity({
customhook,
name: 'routeDone',
destroy: 'detached',
weightValue: 8,
}),
Hide: new hookEntity({
customhook,
name: 'hide',
destroy: 'show',
weightValue: 9,
}),
Detached: new hookEntity({
customhook,
name: 'detached',
destroy: 'attached',
weightValue: 10,
}),
}),
}
);
const init = (hooks) => (diyHooks = hooks);
/**
* 获取所有自定义钩子
* return Array<string> diy钩子数组
*/
const getDiyHooks = () => Object.keys(diyHooks);
/**
* 手动命中某属性钩子-待开发
* @param1 <string> 单个钩子名
* @param2 <string> 要改变的状态
*/
const setHit = (name, state) => {
Hooks()[name][state ? 'cycleStart' : 'cycleEnd']();
};
/**
* 使用js监听属性钩子-待开发
* @param1 <string> 钩子名、这里只允许属性监听钩子
* @param2 <function> 回调函数
*/
const on = (name, cb) => { };
export {
Hooks,
getDiyHooks,
nativeAllHooks,
lifetimesHooks,
pageLifetimesHooks,
nativeHooks,
componentHooks,
init,
setHit,
on,
};

View File

@@ -0,0 +1,16 @@
import * as init from './init.js';
import { setHit } from './hooks.js';
import * as polyfill from './mini-polyfill.js';
const install = function () {
if (arguments.length < 3) {
// 小程序架构,使用垫片
init.install(polyfill.vue, arguments[0], polyfill.store, arguments[1] || 'globalData');
} else {
// vue架构
init.install(...arguments);
}
};
export default {
install,
setHit,
};

View File

@@ -0,0 +1,158 @@
import customHook from './custom-hook.js';
import * as hooks from './hooks.js';
import { getVal, setStore } from './utils.js';
const userAgentKeys = {
'vue-h5': {
// 组件的hook对象
hooksKey: '$options',
// 用来初始化的hook
initHook: 'beforeCreate',
// 是否支持组件、暂废弃
supportComponent: true,
isPage(pageHooks) {
return pageHooks._compiled && this.supportComponent;
},
},
'vue-miniprogram': {
hooksKey: '$options',
initHook: 'beforeCreate',
supportComponent: true,
isPage() {
return this.supportComponent;
},
},
miniprogram: {
name: 'miniprogram',
hooksKey: '',
initHook: 'onLoad',
initHookApp: 'onLaunch',
initHookComponentAlipay: 'didMount',
initHookComponentAlipayInit: 'onInit',
initHookComponentWx: 'created',
initHookComponentLifetimes: 'created',
supportComponent: true,
isPage() {
return this.supportComponent;
},
},
};
let BASE = userAgentKeys['vue-miniprogram'];
const install = (vue, params, store, storeKey) => {
//基于mpvue框架特殊处理避免created的bug
if (vue.mpvueVersion) {
BASE.initHook = 'onLoad';
} else if (vue.userAgentKey) {
// 垫片里指定userAgentKey,根据参数来判断vue架构||原生小程序
BASE = userAgentKeys[vue.userAgentKey];
}
setStore(store);
hooks.init(params);
vue.mixin({
// 监听所有原生钩子,改变对应状态
...hooksMutation(hooks.nativeAllHooks),
// vue初始化钩子beforeCreate
[BASE.initHook](options) {
hookInit.call(this, options, 'page', undefined, BASE.initHook);
},
...(BASE.name === 'miniprogram'
? {
// 所有小程序App初始化钩子onLaunch
[BASE.initHookApp](options) {
hookInit.call(this, options, 'app', undefined, BASE.initHookApp);
},
//微信小程序组件初始化钩子created
[BASE.initHookComponentWx](optionProperties, options) {
hookInit.call(this, options, 'component', optionProperties, BASE.initHookComponentWx);
},
//支付宝小程序组件初始化钩子onInit
[BASE.initHookComponentAlipayInit](optionProperties, options) {
hookInit.call(this, options, 'component', optionProperties, BASE.initHookComponentAlipayInit);
},
//支付宝小程序组件初始化钩子didMount
//兼容支付宝小程序普通和component2模式init、created、didMount都有可能做初始化
[BASE.initHookComponentAlipay](optionProperties, options) {
componentHookTriggered.call(this, optionProperties, options, BASE.initHookComponentAlipay);
},
// 小程序原生组件api混入
lifetimes: {
...hooksMutation(hooks.lifetimesHooks),
//支付宝小程序组件初始化钩子created、在lifetimes为true并且未开启component2时作为初始化
[BASE.initHookComponentLifetimes](optionProperties, options) {
componentHookTriggered.call(this, optionProperties, options, BASE.initHookComponentLifetimes);
},
},
pageLifetimes: {
...hooksMutation(hooks.pageLifetimesHooks),
},
}
: {}),
});
// 钩子执行后,初始化 || 改钩子状态
function componentHookTriggered(optionProperties, options, hookName) {
// 已经初始化的修改内部钩子状态,否则走初始化逻辑
if (this.customHook) {
updateHookState.call(this, options, hookName);
return;
}
hookInit.call(this, options, 'component', optionProperties, hookName);
}
// 初始化实例化custom-hook-spa
function hookInit(options, instanceType, optionProperties, initHookName) {
if (this.customHook) return;
// 入口文件特殊处理
let pageHooks = getVal(this, BASE['hooksKey']);
// 过滤掉非业务组件
if (BASE.isPage(pageHooks)) {
// 兼容非vuex环境
if (!store.state && storeKey) {
store.state = this[storeKey] || storeKey;
}
const isComponent = instanceType === 'component';
Object.defineProperty(this, 'customHook', {
value: new customHook({
instance: this,
options,
pageHooks: isComponent ? optionProperties : pageHooks,
instanceType,
initHookName: initHookName,
}),
configurable: true,
enumerable: false,
});
}
}
// 监听所有钩子并修改状态
function hooksMutation(hookNames) {
return hookNames.reduce(
(hooks, hookName) =>
(hooks[hookName] = function (options) {
updateHookState.call(this, options, hookName);
}) && hooks,
{}
);
}
// 更新已经存在的实例化custom-hook-spa对应钩子状态
function updateHookState(options, hookName) {
//没有创建customHook不作处理
if (typeof this.customHook != 'object' && typeof this.customHook != null) return;
//customHook里没有自定义钩子不作处理
if (!this.customHook.customHookArr.length) return;
if (options && Object.keys(options).length > 0) {
this.customHook.options = options;
}
const hooks = this.customHook.hook;
const isDestroy = ['beforeDestroy', 'destroyed', 'onUnload', 'didUnmount', 'detached'].includes(hookName);
for (let k in hooks) {
const hook = hooks[k];
if (hook.name == hookName) {
hook.cycleStart();
} else if (hook.destroy == hookName) {
hook.cycleEnd();
}
isDestroy && hook.unwatchFn?.();
}
isDestroy && delete this.customHook;
}
};
export { install, BASE };

View File

@@ -0,0 +1,180 @@
import { getStore, getVal } from './utils.js';
const vue = {
mixin(mixin) {
let app = App,
page = Page,
component = Component;
// 通过重写构造器实现mixin的生命周期部分
App = (options) => {
this.mergeHook(mixin, options);
app(options);
};
Page = (options) => {
this.mergeHook(mixin, options);
page(options);
};
Component = (options) => {
this.mergeComponentHook(mixin, options);
const newOptions = this.mixinLifetimes(mixin, options);
component(newOptions);
};
},
// mixin实现
mergeHook(mixin, options) {
for (let [key, value] of Object.entries(mixin)) {
const originFunc = options[key];
options[key] = function (...args) {
originFunc && originFunc.call(this, ...args);
value.call(this, ...args);
};
}
},
//由于小程序组件钩子的特殊性this上没有挂钩子引用所以需要特殊处理
mergeComponentHook(mixin, options) {
const hooksObjectKeys = ['lifetimes', 'pageLifetimes'];
for (let [key, value] of Object.entries(mixin).filter((e) => !hooksObjectKeys.includes(e[0]))) {
const originFunc = options[key];
options[key] = function (...args) {
originFunc && originFunc.call(this, ...args);
value.call(this, options, ...args);
};
}
},
// 使用小程序自带api混入pageLifetimes和lifetimes
mixinLifetimes(mixin, options) {
if (typeof Behavior === 'function') {
const orightMixin = options.behaviors || [];
// 由于lifetimes内的created优先级最高则在这里做组件的初始化传入构造参数用来替代组件的pageHooks对象
const originFunc = mixin.lifetimes.created;
mixin.lifetimes.created = function (...args) {
originFunc && originFunc.call(this, options, ...args);
};
orightMixin.push(
Behavior({
lifetimes: mixin.lifetimes,
pageLifetimes: mixin.pageLifetimes,
})
);
options.behaviors = orightMixin;
return options;
} else {
const orightMixin = options.mixins || [];
if (options.options && options.options.lifetimes) {
const originFunc = mixin.lifetimes.created;
mixin.lifetimes.created = function (...args) {
originFunc && originFunc.call(this, options, ...args);
};
}
orightMixin.push(
Mixin({
lifetimes: mixin.lifetimes,
pageLifetimes: mixin.pageLifetimes,
options: {
// 允许基础库识别 lifetimes 字段以支持 lifetimes 功能
lifetimes: true,
},
})
);
options.mixins = orightMixin;
return options;
}
},
userAgentKey: 'miniprogram',
};
const subscribers = {};
const store = {
// 基于es5实现不支持监听没有定义的属性
watch(keyFun, cb, options) {
// 获取数据仓库一般为globalData对象
const baseState = getStore().state;
// 具体要监听的属性
const watchKey = options.watchKey;
const base = getVal(baseState, watchKey, true);
let subscriber = {
callback: cb,
// 是否是卸载监听
unwatch: false,
};
if (subscribers[watchKey]) {
subscribers[watchKey].push(subscriber);
} else {
subscribers[watchKey] = [subscriber];
}
const unMonitorArrayFn = [];
deep(base.obj, base.key);
function deep(obj, key) {
// 监听基本数据类型
let name = obj[key];
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
set: function (v) {
name = v;
subscribers[watchKey].map((subscriber) => subscriber.callback(base.obj[base.key]));
},
get: function () {
return name;
},
});
// 额外添加对数组长度的监听
if (Array.isArray(obj[key])) {
// 监听数组长度
unMonitorArrayFn.push(
monitorArray(obj[key], () => {
cb(base.obj[base.key]);
})
);
}
// 递归监听引用数据类型
if (typeof obj[key] === 'object' && obj[key] != null) {
for (let [key_, value_] of Object.entries(obj[key])) {
deep(obj[key], key_);
}
}
}
return () => {
// 取消基本数据类型监听
subscriber.unwatch = true;
const unwatchIndex = subscribers[watchKey].findIndex((e) => e.unwatch);
subscribers[watchKey].splice(unwatchIndex, 1);
if (!subscribers[watchKey].length) {
delete subscribers[watchKey];
}
// 取消数组长度监听
unMonitorArrayFn.map((f) => f());
};
},
};
// 监听数组长度
function monitorArray(array, callback) {
// 获取Array原型
const arrayMethods = Object.create(array);
const unwatchFn = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].map((method) => {
// 原生Array的原型方法
let original = arrayMethods[method];
define(array, method, function () {
original.apply(this, arguments);
// 调用对应的原生方法并返回结果(新数组长度)
return callback.apply(this, arguments);
});
return () => {
define(array, method, original);
};
});
return () => {
unwatchFn.map((f) => f());
};
}
// 定义不可枚举的属性
function define(obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true,
});
}
export { vue, store };

View File

@@ -0,0 +1,64 @@
let store;
/**
* 根据动态字符串key获取属性相当于eval
* @param1 <object> 在此对象上查找
* @param2 <string> | Array<string> 要找的对象key支持字符串调用链
* @param3 <boolean> 是否保留最后一层
* return <any> 获取到的属性
*/
const getVal = (obj, keys, LastFloor) => {
try {
if (keys) {
let keyArr = keys;
if (typeof keys === 'string') {
keyArr = keys.split('.');
}
// 是否保留最后一层,用于做数据监听
const length = LastFloor ? keyArr.length - 1 : keyArr.length;
//兼容1.1.1以及之前的版本,结束
for (let i = 0; i < length; i++) {
obj = obj[keyArr[i]];
}
return LastFloor
? {
key: keyArr[length],
obj,
}
: obj;
} else {
return obj;
}
} catch (err) {
return;
}
};
/**
* 暂存store对象
* @param <object> 传入的store对象
* return <object> 传入的store对象
*/
const setStore = (_store) => {
store = _store;
return store;
};
/**
* 获取store对象
* @param <App> | <Page> | <Component> 组件实例
* return <object> store对象
*/
const getStore = (page) => {
//有显式传入的store
if (store && store.state) {
return store;
}
// 否则使用组件里的store
else if (page) {
return page.$store || {};
} else {
return {};
}
};
export { getVal, setStore, getStore };

45
node_modules/spa-custom-hooks/package.json generated vendored Normal file
View File

@@ -0,0 +1,45 @@
{
"name": "spa-custom-hooks",
"version": "1.4.1",
"description": "业务层的自定义钩子支持vue架构和各种小程序",
"author": "鹅鹅鹅 <1977474741@qq.com>",
"homepage": "https://github.com/1977474741/spa-custom-hooks.git",
"scripts": {
"serve": "rollup --config rollup.config.mjs && NODE_OPTIONS=--openssl-legacy-provider vue-cli-service serve",
"build": "rollup --config rollup.config.mjs && NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build",
"dev": "rollup --config rollup.config.mjs -w",
"prepublishOnly": "rollup --config rollup.config.mjs",
"semantic-release": "semantic-release"
},
"main": "./lib/spa-custom-hooks.js",
"module": "./lib/spa-custom-hooks.mjs",
"repository": {
"type": "git",
"url": "https://github.com/1977474741/spa-custom-hooks.git"
},
"npm": "https://www.npmjs.com/package/spa-custom-hooks",
"files": [
"/lib/",
"/package.json",
"/README.md"
],
"devDependencies": {
"@qiwi/semantic-release-gh-pages-plugin": "^5.2.0",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-terser": "^0.4.3",
"@semantic-release/changelog": "^6.0.1",
"@semantic-release/git": "^10.0.1",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"core-js": "^3.6.5",
"rollup": "^3.26.2",
"semantic-release": "^19.0.2",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vue-template-compiler": "^2.6.11",
"vuex": "^3.4.0"
},
"license": "MIT"
}