feat: add inula-adapter/pinia 和 inula-adapter/vuex
This commit is contained in:
parent
0f75e48f79
commit
96c4f0c337
|
@ -0,0 +1,7 @@
|
||||||
|
## 适配Vue
|
||||||
|
|
||||||
|
### 生命周期
|
||||||
|
|
||||||
|
### pinia
|
||||||
|
|
||||||
|
### vuex
|
|
@ -0,0 +1,101 @@
|
||||||
|
## pinia接口差异
|
||||||
|
|
||||||
|
1、不支createPinia的相关函数,使用了是没有效果的
|
||||||
|
```js
|
||||||
|
it('not support createPinia', () => {
|
||||||
|
const pinia = createPinia(); // 接口可以调用,但是没有效果
|
||||||
|
const store = useStore(pinia); // 传入pinia也没有效果
|
||||||
|
|
||||||
|
expect(store.name).toBe('a');
|
||||||
|
expect(store.$state.name).toBe('a');
|
||||||
|
expect(pinia.state.value.main.name).toBe('a'); // 不能通过pinia.state.value.main获取store
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
2、因为不支持createPinia,同样也不支持setActivePinia、getActivePinia()
|
||||||
|
|
||||||
|
3、不支持store.$patch
|
||||||
|
```js
|
||||||
|
it('can not be set with patch', () => {
|
||||||
|
const pinia = createPinia();
|
||||||
|
const store = useStore(pinia);
|
||||||
|
|
||||||
|
store.$patch({ name: 'a' }); // 不支持
|
||||||
|
// 改成
|
||||||
|
store.$state.name = 'a'; // 可以改成直接赋值
|
||||||
|
|
||||||
|
expect(store.name).toBe('a');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
4、不支持store.$reset();
|
||||||
|
```ts
|
||||||
|
it('can not reset the state', () => {
|
||||||
|
const store = useStore();
|
||||||
|
store.name = 'Ed';
|
||||||
|
store.nested.n++;
|
||||||
|
store.$reset(); // 不支持
|
||||||
|
expect(store.$state).toEqual({
|
||||||
|
counter: 0,
|
||||||
|
name: 'Eduardo',
|
||||||
|
nested: {
|
||||||
|
n: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
5、不支持store.$dispose,可以用store.$unsubscribe代替
|
||||||
|
```js
|
||||||
|
it('can not be disposed', () => {
|
||||||
|
const useStore = defineStore({
|
||||||
|
id: 'main',
|
||||||
|
state: () => ({ n: 0 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = useStore();
|
||||||
|
const spy = vi.fn();
|
||||||
|
|
||||||
|
store.$subscribe(spy, { flush: 'sync' });
|
||||||
|
store.$state.n++;
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(useStore()).toBe(store);
|
||||||
|
|
||||||
|
// store.$dispose();
|
||||||
|
// 改成
|
||||||
|
store.$unsubscribe(spy);
|
||||||
|
|
||||||
|
store.$state.n++;
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
6、支持$subscribe,不需要flush,默认就是sync
|
||||||
|
```js
|
||||||
|
it('can be $unsubscribe', () => {
|
||||||
|
const useStore = defineStore({
|
||||||
|
id: 'main',
|
||||||
|
state: () => ({ n: 0 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = useStore();
|
||||||
|
const spy = vi.fn();
|
||||||
|
|
||||||
|
// store.$subscribe(spy, { flush: 'sync' });
|
||||||
|
// 不需要flush,默认就是sync
|
||||||
|
store.$subscribe(spy, { flush: 'sync' });
|
||||||
|
store.$state.n++;
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(useStore()).toBe(store);
|
||||||
|
store.$unsubscribe(spy);
|
||||||
|
store.$state.n++;
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
## 适配Vue
|
||||||
|
|
||||||
|
### 生命周期
|
||||||
|
onBeforeMount
|
||||||
|
onMounted
|
||||||
|
onBeforeUpdate
|
||||||
|
onUpdated
|
||||||
|
onBeforeUnmount
|
||||||
|
onUnmounted
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
## vuex接口差异
|
||||||
|
|
||||||
|
1、createStore接口中的,state、mutations、actions、getters不能用相同名字属性
|
||||||
|
```js
|
||||||
|
it('dispatching actions, sync', () => {
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST] (state, n) {
|
||||||
|
state.a += n
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
[TEST] ({ commit }, n) {
|
||||||
|
commit(TEST, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
store.dispatch(TEST, 2)
|
||||||
|
expect(store.state.a).toBe(3)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
2、createStore接口不支持strict属性
|
||||||
|
```js
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST] (state, n) {
|
||||||
|
state.a += n
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
[TEST] ({ commit }, n) {
|
||||||
|
commit(TEST, n)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
strict: true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
3、store.registerModule不支持传入数组,只支持一层的module注册
|
||||||
|
```js
|
||||||
|
it('dynamic module registration with namespace inheritance', () => {
|
||||||
|
const store = createStore({
|
||||||
|
modules: {
|
||||||
|
a: {
|
||||||
|
namespaced: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const actionSpy = vi.fn();
|
||||||
|
const mutationSpy = vi.fn();
|
||||||
|
store.registerModule(['a', 'b'], {
|
||||||
|
state: { value: 1 },
|
||||||
|
getters: { foo: state => state.value },
|
||||||
|
actions: { foo: actionSpy },
|
||||||
|
mutations: { foo: mutationSpy },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(store.state.a.b.value).toBe(1);
|
||||||
|
expect(store.getters['a/foo']).toBe(1);
|
||||||
|
|
||||||
|
store.dispatch('a/foo');
|
||||||
|
expect(actionSpy).toHaveBeenCalled();
|
||||||
|
|
||||||
|
store.commit('a/foo');
|
||||||
|
expect(mutationSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
4、createStore不支持多层mudule注册
|
||||||
|
```js
|
||||||
|
it('module: mutation', function () {
|
||||||
|
const mutations = {
|
||||||
|
[TEST](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
mutations,
|
||||||
|
modules: { // 第一层module,支持
|
||||||
|
nested: {
|
||||||
|
state: {a: 2},
|
||||||
|
mutations,
|
||||||
|
modules: { // 第二层module,不支持
|
||||||
|
one: {
|
||||||
|
state: {a: 3},
|
||||||
|
mutations,
|
||||||
|
},
|
||||||
|
nested: {
|
||||||
|
modules: { // 第三层module,不支持
|
||||||
|
two: {
|
||||||
|
state: {a: 4},
|
||||||
|
mutations,
|
||||||
|
},
|
||||||
|
three: {
|
||||||
|
state: {a: 5},
|
||||||
|
mutations,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
four: {
|
||||||
|
state: {a: 6},
|
||||||
|
mutations,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
4、不支持store.replaceState
|
||||||
|
```js
|
||||||
|
const store = createStore({});
|
||||||
|
store.replaceState({ a: { foo: 'state' } });
|
||||||
|
```
|
||||||
|
5、store.registerModule不支持传入options参数
|
||||||
|
```js
|
||||||
|
store.registerModule(
|
||||||
|
'a',
|
||||||
|
{
|
||||||
|
namespaced: true,
|
||||||
|
getters: { foo: state => state.foo },
|
||||||
|
actions: { foo: actionSpy },
|
||||||
|
mutations: { foo: mutationSpy },
|
||||||
|
},
|
||||||
|
{ preserveState: true }
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
5、dispatch不支持传入options参数
|
||||||
|
```js
|
||||||
|
dispatch('foo', null, {root: true});
|
||||||
|
```
|
||||||
|
|
||||||
|
6、不支持action中的root属性,action需要是个函数
|
||||||
|
```js
|
||||||
|
const store = createStore({
|
||||||
|
modules: {
|
||||||
|
a: {
|
||||||
|
namespaced: true,
|
||||||
|
actions: {
|
||||||
|
[TEST]: {
|
||||||
|
root: true,
|
||||||
|
handler() {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
7、createStore中不支持plugins
|
||||||
|
```js
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
[TEST]: actionSpy,
|
||||||
|
},
|
||||||
|
plugins: [ // 不支持plugins
|
||||||
|
store => {
|
||||||
|
initState = store.state;
|
||||||
|
store.subscribe((mut, state) => {
|
||||||
|
expect(state).toBe(state);
|
||||||
|
mutations.push(mut);
|
||||||
|
});
|
||||||
|
store.subscribeAction(subscribeActionSpy);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"module": "./esm/pinia-adapter.js",
|
||||||
|
"main": "./cjs/pinia-adapter.js",
|
||||||
|
"types": "./@types/index.d.ts"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"module": "./esm/vuex-adapter.js",
|
||||||
|
"main": "./cjs/vuex-adapter.js",
|
||||||
|
"types": "./@types/index.d.ts"
|
||||||
|
}
|
|
@ -2,17 +2,17 @@
|
||||||
"name": "@inula/vue-adapter",
|
"name": "@inula/vue-adapter",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "vue adapter",
|
"description": "vue adapter",
|
||||||
"main": "./build/cjs/vue-adapter.js",
|
"main": "./vue/cjs/vue-adapter.js",
|
||||||
"module": "./build/esm/vue-adapter.js",
|
"module": "./vue/esm/vue-adapter.js",
|
||||||
"types": "build/@types/index.d.ts",
|
"types": "./vue/@types/index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"/build",
|
"build/**/*",
|
||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest --ui",
|
"test": "vitest --ui",
|
||||||
"build": "rollup -c ./scripts/rollup.config.js && npm run build-types",
|
"build": "rollup -c ./scripts/rollup.config.js && npm run build-types",
|
||||||
"build-types": "tsc -p tsconfig.build.json && rollup -c ./scripts/build-types.js"
|
"build-types": "tsc -p tsconfig.vue.json && tsc -p tsconfig.pinia.json && tsc -p tsconfig.vuex.json && rollup -c ./scripts/build-types.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"openinula": "workspace:*"
|
"openinula": "workspace:*"
|
||||||
|
@ -53,7 +53,8 @@
|
||||||
"@vitest/ui": "^0.34.5",
|
"@vitest/ui": "^0.34.5",
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
"vitest": "^0.34.5",
|
"vitest": "^0.34.5",
|
||||||
"@vitejs/plugin-react": "^4.2.1"
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
|
"@testing-library/user-event": "^12.1.10"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"openinula": ">=0.1.1"
|
"openinula": ">=0.1.1"
|
|
@ -1,18 +0,0 @@
|
||||||
接口差异:
|
|
||||||
|
|
||||||
1、$patch不需要,可以直接赋值
|
|
||||||
```ts
|
|
||||||
actions: {
|
|
||||||
setFoo(foo) {
|
|
||||||
// not support
|
|
||||||
this.$patch({ nested: { foo } });
|
|
||||||
// support
|
|
||||||
this.nested = { foo };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
2、watch写法不同
|
|
||||||
|
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@inula/pinia-adapter",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"description": "pinia adapter",
|
|
||||||
"main": "./src/index.ts",
|
|
||||||
"scripts": {
|
|
||||||
"test": "vitest --ui"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/cli": "^7.23.9",
|
|
||||||
"@babel/core": "^7.23.9",
|
|
||||||
"@babel/plugin-syntax-typescript": "^7.23.3",
|
|
||||||
"openinula": "workspace:*"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@testing-library/user-event": "^12.1.10",
|
|
||||||
"@vitest/ui": "^0.34.5",
|
|
||||||
"jsdom": "^24.0.0",
|
|
||||||
"vitest": "^0.34.5"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
|
||||||
*
|
|
||||||
* openInula is licensed under Mulan PSL v2.
|
|
||||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
||||||
* You may obtain a copy of Mulan PSL v2 at:
|
|
||||||
*
|
|
||||||
* http://license.coscl.org.cn/MulanPSL2
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
||||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
||||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
||||||
* See the Mulan PSL v2 for more details.
|
|
||||||
*/
|
|
||||||
import { createStore, watch as wt, StoreObj } from 'openinula';
|
|
||||||
import { ref } from './ref';
|
|
||||||
|
|
||||||
const storeMap = new Map();
|
|
||||||
|
|
||||||
interface StoreDefinition {
|
|
||||||
id: string;
|
|
||||||
state: () => Record<string, any>;
|
|
||||||
actions?: Record<string, Function>;
|
|
||||||
getters?: Record<string, Function>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type StoreCallback = () => Record<string, any>;
|
|
||||||
|
|
||||||
export function defineStore(id: string | StoreDefinition, cbOrDef?: StoreCallback | StoreDefinition): Function {
|
|
||||||
if (!cbOrDef && typeof id === 'object') {
|
|
||||||
cbOrDef = id as StoreDefinition;
|
|
||||||
id = cbOrDef.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof cbOrDef === 'object') {
|
|
||||||
return defineOptionsStore(id as string, cbOrDef as StoreDefinition);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
const data = (cbOrDef as StoreCallback)();
|
|
||||||
|
|
||||||
if (storeMap.has(cbOrDef)) {
|
|
||||||
return storeMap.get(cbOrDef)();
|
|
||||||
}
|
|
||||||
|
|
||||||
const entries = Object.entries(data);
|
|
||||||
|
|
||||||
const state = Object.fromEntries(entries.filter(([key, val]) => val.isRef).map(([key, val]) => [key, val.value]));
|
|
||||||
const computed = Object.fromEntries(
|
|
||||||
entries.filter(([key, val]) => val.isComputed).map(([key, val]) => [key, val.raw])
|
|
||||||
);
|
|
||||||
const actions = Object.fromEntries(entries.filter(([key, val]) => !val.isRef && !val.isComputed));
|
|
||||||
|
|
||||||
const useStore = createStore({
|
|
||||||
id: id as string,
|
|
||||||
state,
|
|
||||||
computed,
|
|
||||||
actions: enhanceActions(actions),
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.entries(data)
|
|
||||||
.filter(([key, val]) => val.isRef)
|
|
||||||
.forEach(([key, val]) =>
|
|
||||||
val.watch(newVal => {
|
|
||||||
useStore().$s[key] = newVal;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
storeMap.set(cbOrDef, useStore);
|
|
||||||
|
|
||||||
return useStore();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function enhanceActions(actions) {
|
|
||||||
if (!actions) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(actions).map(([key, value]) => {
|
|
||||||
return [
|
|
||||||
key,
|
|
||||||
function (store, ...args) {
|
|
||||||
return value.bind(this)(...args);
|
|
||||||
},
|
|
||||||
];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function defineOptionsStore<S, A, C>(
|
|
||||||
id: string,
|
|
||||||
{ state, actions, getters }: StoreDefinition
|
|
||||||
): () => StoreObj<any, any, any> {
|
|
||||||
if (typeof state === 'function') {
|
|
||||||
state = state();
|
|
||||||
}
|
|
||||||
let useStore = null;
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (!useStore) {
|
|
||||||
useStore = createStore({
|
|
||||||
id,
|
|
||||||
state,
|
|
||||||
actions: enhanceActions(actions),
|
|
||||||
computed: getters,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return useStore();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mapStores(...stores) {
|
|
||||||
const result = {};
|
|
||||||
|
|
||||||
stores.forEach(store => {
|
|
||||||
const expandedStore = store();
|
|
||||||
result[`${expandedStore.id}Store`] = () => expandedStore;
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function storeToRefs(store) {
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(store.$s).map(([key, value]) => {
|
|
||||||
return [key, ref(value)];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createPinia() {
|
|
||||||
const result = {
|
|
||||||
install: app => {}, // do something?
|
|
||||||
use: plugin => result,
|
|
||||||
state: {}, //
|
|
||||||
};
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const watch = wt;
|
|
|
@ -1,68 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
|
||||||
*
|
|
||||||
* openInula is licensed under Mulan PSL v2.
|
|
||||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
||||||
* You may obtain a copy of Mulan PSL v2 at:
|
|
||||||
*
|
|
||||||
* http://license.coscl.org.cn/MulanPSL2
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
||||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
||||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
||||||
* See the Mulan PSL v2 for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useState, useRef } from 'openinula';
|
|
||||||
export function refBase(initialValue, update, state) {
|
|
||||||
let listeners = new Set();
|
|
||||||
|
|
||||||
return new Proxy(
|
|
||||||
{ value: initialValue },
|
|
||||||
{
|
|
||||||
get: (target, name) => {
|
|
||||||
if (name === 'value' || name === 'current') {
|
|
||||||
return state.current;
|
|
||||||
}
|
|
||||||
if (name === Symbol.toPrimitive) {
|
|
||||||
return () => state.current;
|
|
||||||
}
|
|
||||||
if (name === 'isRef') return true;
|
|
||||||
if (name === 'watch')
|
|
||||||
return cb => {
|
|
||||||
listeners.add(cb);
|
|
||||||
};
|
|
||||||
if (name === 'raw') return initialValue;
|
|
||||||
},
|
|
||||||
set: (target, name, value) => {
|
|
||||||
if (name === 'value') {
|
|
||||||
if (state.current === value) return true;
|
|
||||||
state.current = value;
|
|
||||||
update();
|
|
||||||
Array.from(listeners.values()).forEach(listener => {
|
|
||||||
listener(value);
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} else if (name === 'current') {
|
|
||||||
if (state.current === value) return true;
|
|
||||||
state.current = value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ref(initialValue) {
|
|
||||||
let [b, r] = useState(false);
|
|
||||||
let state = useRef(initialValue);
|
|
||||||
|
|
||||||
return refBase(initialValue, () => r(!b), state);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ref(initialValue) {
|
|
||||||
let [b, r] = useState(false);
|
|
||||||
let state = useRef(initialValue);
|
|
||||||
|
|
||||||
return refBase(initialValue, () => r(!b), state);
|
|
||||||
}
|
|
|
@ -1,153 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
|
||||||
*
|
|
||||||
* openInula is licensed under Mulan PSL v2.
|
|
||||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
||||||
* You may obtain a copy of Mulan PSL v2 at:
|
|
||||||
*
|
|
||||||
* http://license.coscl.org.cn/MulanPSL2
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
||||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
||||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
||||||
* See the Mulan PSL v2 for more details.
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-nocheck For the compiled code.
|
|
||||||
|
|
||||||
import { describe, it, vi, expect } from 'vitest';
|
|
||||||
import { defineStore, createPinia } from '../src/pinia';
|
|
||||||
import { ref } from '../src/ref';
|
|
||||||
|
|
||||||
function expectType<T>(_value: T): void {}
|
|
||||||
|
|
||||||
describe('pinia getters', () => {
|
|
||||||
const useStore = defineStore({
|
|
||||||
id: 'main',
|
|
||||||
state: () => ({
|
|
||||||
name: 'Eduardo',
|
|
||||||
}),
|
|
||||||
getters: {
|
|
||||||
upperCaseName(store) {
|
|
||||||
return store.name.toUpperCase();
|
|
||||||
},
|
|
||||||
doubleName(): string {
|
|
||||||
return this.upperCaseName;
|
|
||||||
},
|
|
||||||
composed(): string {
|
|
||||||
return this.upperCaseName + ': ok';
|
|
||||||
},
|
|
||||||
arrowUpper: state => {
|
|
||||||
// @ts-expect-error
|
|
||||||
state.nope;
|
|
||||||
state.name.toUpperCase();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
o() {
|
|
||||||
// @ts-expect-error it should type getters
|
|
||||||
this.arrowUpper.toUpperCase();
|
|
||||||
this.o().toUpperCase();
|
|
||||||
return 'a string';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const useB = defineStore({
|
|
||||||
id: 'B',
|
|
||||||
state: () => ({ b: 'b' }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const useA = defineStore({
|
|
||||||
id: 'A',
|
|
||||||
state: () => ({ a: 'a' }),
|
|
||||||
getters: {
|
|
||||||
fromB(): string {
|
|
||||||
const bStore = useB();
|
|
||||||
return this.a + ' ' + bStore.b;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds getters to the store', () => {
|
|
||||||
const store = useStore();
|
|
||||||
expect(store.upperCaseName).toBe('EDUARDO');
|
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
store.nope;
|
|
||||||
|
|
||||||
store.name = 'Ed';
|
|
||||||
expect(store.upperCaseName).toBe('ED');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updates the value', () => {
|
|
||||||
const store = useStore();
|
|
||||||
store.name = 'Ed';
|
|
||||||
expect(store.upperCaseName).toBe('ED');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can use other getters', () => {
|
|
||||||
const store = useStore();
|
|
||||||
expect(store.composed).toBe('EDUARDO: ok');
|
|
||||||
store.name = 'Ed';
|
|
||||||
expect(store.composed).toBe('ED: ok');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('cross used stores', () => {
|
|
||||||
const useA = defineStore('a', () => {
|
|
||||||
const n = ref(0);
|
|
||||||
const double = computed(() => n.value * 2);
|
|
||||||
const sum = computed(() => n.value + B.n);
|
|
||||||
|
|
||||||
function increment() {
|
|
||||||
return n.value++;
|
|
||||||
}
|
|
||||||
|
|
||||||
function incrementB() {
|
|
||||||
return B.increment();
|
|
||||||
}
|
|
||||||
|
|
||||||
return { n, double, sum, increment, incrementB };
|
|
||||||
});
|
|
||||||
|
|
||||||
const useB = defineStore('b', () => {
|
|
||||||
const n = ref(0);
|
|
||||||
const double = computed(() => n.value * 2);
|
|
||||||
|
|
||||||
function increment() {
|
|
||||||
return n.value++;
|
|
||||||
}
|
|
||||||
|
|
||||||
function incrementA() {
|
|
||||||
return A.increment();
|
|
||||||
}
|
|
||||||
|
|
||||||
return { n, double, incrementA, increment };
|
|
||||||
});
|
|
||||||
|
|
||||||
const A = useA();
|
|
||||||
const B = useB();
|
|
||||||
|
|
||||||
it('keeps getters reactive', () => {
|
|
||||||
const a = useA();
|
|
||||||
const b = useB();
|
|
||||||
|
|
||||||
expectType<() => number>(a.increment);
|
|
||||||
expectType<() => number>(b.increment);
|
|
||||||
expectType<() => number>(a.incrementB);
|
|
||||||
expectType<() => number>(b.incrementA);
|
|
||||||
|
|
||||||
expect(a.double).toBe(0);
|
|
||||||
b.incrementA();
|
|
||||||
expect(a.double).toBe(2);
|
|
||||||
a.increment();
|
|
||||||
expect(a.double).toBe(4);
|
|
||||||
|
|
||||||
expect(b.double).toBe(0);
|
|
||||||
a.incrementB();
|
|
||||||
expect(b.double).toBe(2);
|
|
||||||
b.increment();
|
|
||||||
expect(b.double).toBe(4);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,55 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
|
||||||
*
|
|
||||||
* openInula is licensed under Mulan PSL v2.
|
|
||||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
||||||
* You may obtain a copy of Mulan PSL v2 at:
|
|
||||||
*
|
|
||||||
* http://license.coscl.org.cn/MulanPSL2
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
||||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
||||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
||||||
* See the Mulan PSL v2 for more details.
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-nocheck For the compiled code.
|
|
||||||
|
|
||||||
import { beforeEach, describe, it, vi, expect } from 'vitest';
|
|
||||||
import { createPinia, defineStore, computed } from '../src/pinia';
|
|
||||||
|
|
||||||
describe('pinia state', () => {
|
|
||||||
beforeEach(() => {});
|
|
||||||
|
|
||||||
const useStore = defineStore('main', {
|
|
||||||
state: () => ({
|
|
||||||
name: 'Eduardo',
|
|
||||||
counter: 0,
|
|
||||||
nested: { n: 0 },
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can directly access state at the store level', () => {
|
|
||||||
const store = useStore();
|
|
||||||
expect(store.name).toBe('Eduardo');
|
|
||||||
store.name = 'Ed';
|
|
||||||
expect(store.name).toBe('Ed');
|
|
||||||
});
|
|
||||||
|
|
||||||
it.skip('state is reactive', () => {
|
|
||||||
const store = useStore();
|
|
||||||
const upperCased = computed(() => store.name.toUpperCase());
|
|
||||||
expect(upperCased.value).toBe('EDUARDO');
|
|
||||||
store.name = 'Ed';
|
|
||||||
expect(upperCased.value).toBe('ED');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can be nested set on store.$s, not $state', () => {
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
store.$s.nested.n = 3;
|
|
||||||
|
|
||||||
expect(store.nested.n).toBe(3);
|
|
||||||
expect(store.$s.nested.n).toBe(3);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,67 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
|
||||||
*
|
|
||||||
* openInula is licensed under Mulan PSL v2.
|
|
||||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
||||||
* You may obtain a copy of Mulan PSL v2 at:
|
|
||||||
*
|
|
||||||
* http://license.coscl.org.cn/MulanPSL2
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
||||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
||||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
||||||
* See the Mulan PSL v2 for more details.
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-nocheck For the compiled code.
|
|
||||||
|
|
||||||
import { beforeEach, describe, it, vi, expect } from 'vitest';
|
|
||||||
import { createPinia, defineStore, computed } from '../src/pinia';
|
|
||||||
|
|
||||||
describe('pinia state', () => {
|
|
||||||
beforeEach(() => {});
|
|
||||||
|
|
||||||
const useStore = defineStore({
|
|
||||||
id: 'main',
|
|
||||||
state: () => ({
|
|
||||||
a: true,
|
|
||||||
nested: {
|
|
||||||
foo: 'foo',
|
|
||||||
a: { b: 'string' },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
it.skip('sets the initial state', () => {
|
|
||||||
const store = useStore();
|
|
||||||
store.$s;
|
|
||||||
expect(store.$s).toEqual({
|
|
||||||
a: true,
|
|
||||||
nested: {
|
|
||||||
foo: 'foo',
|
|
||||||
a: { b: 'string' },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('reuses a store', () => {
|
|
||||||
const useStore = defineStore({ id: 'main' });
|
|
||||||
expect(useStore()).toBe(useStore());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can replace its state', () => {
|
|
||||||
const store = useStore();
|
|
||||||
const spy = vi.fn();
|
|
||||||
store.$s.watch('a', () => {
|
|
||||||
spy();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(store.a).toBe(true);
|
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledTimes(0);
|
|
||||||
|
|
||||||
store.a = false;
|
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ESNext",
|
|
||||||
"module": "ESNext",
|
|
||||||
"lib": ["ESNext", "DOM"],
|
|
||||||
"moduleResolution": "Node",
|
|
||||||
"strict": true,
|
|
||||||
"esModuleInterop": true
|
|
||||||
},
|
|
||||||
"ts-node": {
|
|
||||||
"esm": true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -51,14 +51,14 @@ export function cleanUp(folders) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTypeConfig() {
|
function buildTypeConfig(name) {
|
||||||
return {
|
return {
|
||||||
input: ['./build/@types/index.d.ts'],
|
input: [`./build/${name}/@types/${name}/index.d.ts`],
|
||||||
output: {
|
output: {
|
||||||
file: './build/@types/index.d.ts',
|
file: `./build/${name}/@types/index.d.ts`,
|
||||||
},
|
},
|
||||||
plugins: [dts(), cleanUp(['./build/@types/'])],
|
plugins: [dts(), cleanUp([`./build/${name}/@types/`])],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default [buildTypeConfig()];
|
export default [buildTypeConfig('vue'), buildTypeConfig('pinia'), buildTypeConfig('vuex')];
|
|
@ -15,7 +15,6 @@
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
import babel from '@rollup/plugin-babel';
|
import babel from '@rollup/plugin-babel';
|
||||||
import nodeResolve from '@rollup/plugin-node-resolve';
|
import nodeResolve from '@rollup/plugin-node-resolve';
|
||||||
import { terser } from 'rollup-plugin-terser';
|
import { terser } from 'rollup-plugin-terser';
|
||||||
|
@ -29,16 +28,16 @@ if (!fs.existsSync(outDir)) {
|
||||||
fs.mkdirSync(outDir, { recursive: true });
|
fs.mkdirSync(outDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const getConfig = mode => {
|
const getConfig = (mode, name) => {
|
||||||
const prod = mode.startsWith('prod');
|
const prod = mode.startsWith('prod');
|
||||||
const outputList = [
|
const outputList = [
|
||||||
{
|
{
|
||||||
file: path.join(outDir, `cjs/vue-adapter.${prod ? 'min.' : ''}js`),
|
file: path.join(outDir, `${name}/cjs/${name}-adapter.${prod ? 'min.' : ''}js`),
|
||||||
sourcemap: 'true',
|
sourcemap: 'true',
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
file: path.join(outDir, `umd/vue-adapter.${prod ? 'min.' : ''}js`),
|
file: path.join(outDir, `${name}/umd/${name}-adapter.${prod ? 'min.' : ''}js`),
|
||||||
name: 'VueAdapter',
|
name: 'VueAdapter',
|
||||||
sourcemap: 'true',
|
sourcemap: 'true',
|
||||||
format: 'umd',
|
format: 'umd',
|
||||||
|
@ -46,13 +45,13 @@ const getConfig = mode => {
|
||||||
];
|
];
|
||||||
if (!prod) {
|
if (!prod) {
|
||||||
outputList.push({
|
outputList.push({
|
||||||
file: path.join(outDir, 'esm/vue-adapter.js'),
|
file: path.join(outDir, `${name}/esm/${name}-adapter.js`),
|
||||||
sourcemap: 'true',
|
sourcemap: 'true',
|
||||||
format: 'esm',
|
format: 'esm',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
input: path.join(rootDir, '/src/index.ts'),
|
input: path.join(rootDir, `/src/${name}/index.ts`),
|
||||||
output: outputList,
|
output: outputList,
|
||||||
plugins: [
|
plugins: [
|
||||||
nodeResolve({
|
nodeResolve({
|
||||||
|
@ -66,8 +65,49 @@ const getConfig = mode => {
|
||||||
extensions,
|
extensions,
|
||||||
}),
|
}),
|
||||||
prod && terser(),
|
prod && terser(),
|
||||||
|
name === 'vue'
|
||||||
|
? copyFiles([
|
||||||
|
{
|
||||||
|
from: path.join(rootDir, 'package.json'),
|
||||||
|
to: path.join(outDir, 'package.json'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: path.join(rootDir, 'README.md'),
|
||||||
|
to: path.join(outDir, 'README.md'),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
: copyFiles([
|
||||||
|
{
|
||||||
|
from: path.join(rootDir, `npm/${name}/package.json`),
|
||||||
|
to: path.join(outDir, `${name}/package.json`),
|
||||||
|
},
|
||||||
|
]),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default [getConfig('dev'), getConfig('prod')];
|
function copyFiles(copyPairs) {
|
||||||
|
return {
|
||||||
|
name: 'copy-files',
|
||||||
|
generateBundle() {
|
||||||
|
copyPairs.forEach(({ from, to }) => {
|
||||||
|
const destDir = path.dirname(to);
|
||||||
|
// 判断目标文件夹是否存在
|
||||||
|
if (!fs.existsSync(destDir)) {
|
||||||
|
// 目标文件夹不存在,创建它
|
||||||
|
fs.mkdirSync(destDir, { recursive: true });
|
||||||
|
}
|
||||||
|
fs.copyFileSync(from, to);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default [
|
||||||
|
getConfig('dev', 'vue'),
|
||||||
|
getConfig('prod', 'vue'),
|
||||||
|
getConfig('dev', 'pinia'),
|
||||||
|
getConfig('prod', 'pinia'),
|
||||||
|
getConfig('dev', 'vuex'),
|
||||||
|
getConfig('prod', 'vuex'),
|
||||||
|
];
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
*
|
*
|
||||||
* openInula is licensed under Mulan PSL v2.
|
* openInula is licensed under Mulan PSL v2.
|
||||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
@ -0,0 +1,208 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
import { createStore, StoreObj, vueReactive } from 'openinula';
|
||||||
|
import {
|
||||||
|
FilterAction,
|
||||||
|
FilterComputed,
|
||||||
|
FilterState,
|
||||||
|
StoreDefinition,
|
||||||
|
StoreSetup,
|
||||||
|
Store,
|
||||||
|
AnyFunction,
|
||||||
|
ActionType,
|
||||||
|
StoreToRefsReturn,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
const { ref, isRef, toRef, isReactive, isReadonly } = vueReactive;
|
||||||
|
|
||||||
|
const storeMap = new Map<string, any>();
|
||||||
|
|
||||||
|
export function defineStore<
|
||||||
|
Id extends string,
|
||||||
|
S extends Record<string, unknown>,
|
||||||
|
A extends Record<string, AnyFunction>,
|
||||||
|
C extends Record<string, AnyFunction>,
|
||||||
|
>(definition: StoreDefinition<Id, S, A, C>): (pinia?: any) => Store<S, A, C>;
|
||||||
|
|
||||||
|
export function defineStore<
|
||||||
|
Id extends string,
|
||||||
|
S extends Record<string, unknown>,
|
||||||
|
A extends Record<string, AnyFunction>,
|
||||||
|
C extends Record<string, AnyFunction>,
|
||||||
|
>(id: Id, definition: Omit<StoreDefinition<Id, S, A, C>, 'id'>): (pinia?: any) => Store<S, A, C>;
|
||||||
|
|
||||||
|
export function defineStore<Id extends string, SS extends Record<any, unknown>>(
|
||||||
|
id: Id,
|
||||||
|
setup: StoreSetup<SS>
|
||||||
|
): (pinia?: any) => Store<FilterState<SS>, FilterAction<SS>, FilterComputed<SS>>;
|
||||||
|
|
||||||
|
export function defineStore(idOrDef: any, setupOrDef?: any) {
|
||||||
|
let id: string;
|
||||||
|
let definition: StoreDefinition | StoreSetup;
|
||||||
|
let isSetup = false;
|
||||||
|
|
||||||
|
if (typeof idOrDef === 'string') {
|
||||||
|
isSetup = typeof setupOrDef === 'function';
|
||||||
|
id = idOrDef;
|
||||||
|
definition = setupOrDef;
|
||||||
|
} else {
|
||||||
|
id = idOrDef.id;
|
||||||
|
definition = idOrDef;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSetup) {
|
||||||
|
return defineSetupStore(id, definition as StoreSetup);
|
||||||
|
} else {
|
||||||
|
return defineOptionsStore(id, definition as StoreDefinition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* createStore实现中会给actions增加第一个参数store,pinia不需要,所以需要去掉
|
||||||
|
* @param actions
|
||||||
|
*/
|
||||||
|
function enhanceActions(
|
||||||
|
actions?: ActionType<Record<string, AnyFunction>, Record<string, unknown>, Record<string, AnyFunction>>
|
||||||
|
) {
|
||||||
|
if (!actions) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(actions).map(([key, value]) => {
|
||||||
|
return [
|
||||||
|
key,
|
||||||
|
function (this: StoreObj, state: Record<string, unknown>, ...args: any[]) {
|
||||||
|
return value.bind(this)(...args);
|
||||||
|
},
|
||||||
|
];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function defineOptionsStore(id: string, definition: StoreDefinition) {
|
||||||
|
const state = definition.state ? definition.state() : {};
|
||||||
|
const computed = definition.getters || {};
|
||||||
|
const actions = enhanceActions(definition.actions) || {};
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (storeMap.has(id)) {
|
||||||
|
return storeMap.get(id)!();
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStore = createStore({
|
||||||
|
id,
|
||||||
|
state,
|
||||||
|
actions,
|
||||||
|
computed,
|
||||||
|
});
|
||||||
|
|
||||||
|
storeMap.set(id, useStore);
|
||||||
|
|
||||||
|
return useStore();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function defineSetupStore<SS extends Record<string, unknown>>(id: string, storeSetup: StoreSetup<SS>) {
|
||||||
|
return () => {
|
||||||
|
const data = storeSetup();
|
||||||
|
if (!data) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storeMap.has(id)) {
|
||||||
|
return storeMap.get(id)!();
|
||||||
|
}
|
||||||
|
|
||||||
|
const state: Record<string, unknown> = {};
|
||||||
|
const actions: Record<string, AnyFunction> = {};
|
||||||
|
const getters: Record<string, AnyFunction> = {};
|
||||||
|
for (const key in data) {
|
||||||
|
const prop = data[key];
|
||||||
|
|
||||||
|
if ((isRef(prop) && !isReadonly(prop)) || isReactive(prop)) {
|
||||||
|
// state
|
||||||
|
state[key] = prop;
|
||||||
|
} else if (typeof prop === 'function') {
|
||||||
|
// action
|
||||||
|
actions[key] = prop as AnyFunction;
|
||||||
|
} else if (isRef(prop) && isReadonly(prop)) {
|
||||||
|
// getters
|
||||||
|
getters[key] = (prop as any).fn as AnyFunction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStore = createStore({
|
||||||
|
id,
|
||||||
|
state,
|
||||||
|
computed: getters,
|
||||||
|
actions: enhanceActions(actions),
|
||||||
|
});
|
||||||
|
|
||||||
|
storeMap.set(id, useStore);
|
||||||
|
|
||||||
|
return useStore();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapStores<
|
||||||
|
S extends Record<string, unknown>,
|
||||||
|
A extends Record<string, AnyFunction>,
|
||||||
|
C extends Record<string, AnyFunction>,
|
||||||
|
>(...stores: (() => Store<S, A, C>)[]): { [key: string]: () => Store<S, A, C> } {
|
||||||
|
const result: { [key: string]: () => Store<S, A, C> } = {};
|
||||||
|
|
||||||
|
stores.forEach((store: () => Store<S, A, C>) => {
|
||||||
|
const expandedStore = store();
|
||||||
|
result[`${expandedStore.id}Store`] = () => expandedStore;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function storeToRefs<
|
||||||
|
S extends Record<string, unknown>,
|
||||||
|
A extends Record<string, AnyFunction>,
|
||||||
|
C extends Record<string, AnyFunction>,
|
||||||
|
>(store: Store<S, A, C>): StoreToRefsReturn<S, C> {
|
||||||
|
const stateRefs = Object.fromEntries(
|
||||||
|
Object.entries(store.$s || {}).map(([key, value]) => {
|
||||||
|
return [key, ref(value)];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const getterRefs = Object.fromEntries(
|
||||||
|
Object.entries(store.$config.computed || {}).map(([key, value]) => {
|
||||||
|
const computeFn = (value as () => any).bind(store, store.$s);
|
||||||
|
return [key, toRef(computeFn)];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return { ...stateRefs, ...getterRefs } as StoreToRefsReturn<S, C>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPinia() {
|
||||||
|
console.warn(
|
||||||
|
`The pinia-adapter in Horizon does not support the createPinia interface. Please modify your code accordingly.`
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
install: (app: any) => {},
|
||||||
|
use: (plugin: any) => result,
|
||||||
|
state: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// import type {RefType, StoreObj, UnwrapRef, UserActions, UserComputedValues, ComputedImpl} from 'openinula';
|
||||||
|
import type { RefType, UnwrapRef, ComputedImpl } from 'openinula';
|
||||||
|
|
||||||
|
export type StoreSetup<R = Record<string, unknown>> = () => R;
|
||||||
|
|
||||||
|
export type AnyFunction = (...args: any[]) => any;
|
||||||
|
|
||||||
|
// defineStore init type
|
||||||
|
export interface StoreDefinition<
|
||||||
|
Id extends string = string,
|
||||||
|
S extends Record<string, unknown> = Record<string, unknown>,
|
||||||
|
A extends Record<string, AnyFunction> = Record<string, AnyFunction>,
|
||||||
|
C extends Record<string, AnyFunction> = Record<string, AnyFunction>,
|
||||||
|
> {
|
||||||
|
id?: Id;
|
||||||
|
state?: () => S;
|
||||||
|
actions?: ActionType<A, S, C>;
|
||||||
|
getters?: ComputedType<C, S>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// defineStore return type
|
||||||
|
export type Store<
|
||||||
|
S extends Record<string, unknown>,
|
||||||
|
A extends Record<string, AnyFunction>,
|
||||||
|
C extends Record<string, AnyFunction>,
|
||||||
|
> = {
|
||||||
|
$s: S;
|
||||||
|
$state: S;
|
||||||
|
$a: ActionType<A, S, C>;
|
||||||
|
$c: ComputedType<C, S>;
|
||||||
|
$subscribe: (listener: Listener) => void;
|
||||||
|
$unsubscribe: (listener: Listener) => void;
|
||||||
|
} & { [K in keyof S]: S[K] } & { [K in keyof ActionType<A, S, C>]: ActionType<A, S, C>[K] } & {
|
||||||
|
[K in keyof ComputedType<C, S>]: ReturnType<ComputedType<C, S>[K]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ActionType<A, S, C> = A & ThisType<A & UnwrapRef<S> & WithGetters<C>>;
|
||||||
|
|
||||||
|
type ComputedType<C, S> = {
|
||||||
|
[K in keyof C]: AddFirstArg<C[K], S>;
|
||||||
|
} & ThisType<UnwrapRef<S> & WithGetters<C>>;
|
||||||
|
type AddFirstArg<T, S> = T extends (...args: infer A) => infer R
|
||||||
|
? (state: S, ...args: A) => R
|
||||||
|
: T extends () => infer R
|
||||||
|
? (state: S) => R
|
||||||
|
: T;
|
||||||
|
|
||||||
|
// In Getter function, make this.xx can refer to other getters
|
||||||
|
export type WithGetters<G> = {
|
||||||
|
readonly [k in keyof G]: G[k] extends (...args: any[]) => infer R ? R : UnwrapRef<G[k]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Listener = (change: any) => void;
|
||||||
|
|
||||||
|
// Filter state properties
|
||||||
|
export type FilterState<T extends Record<string, unknown>> = {
|
||||||
|
[K in FilterStateProperties<T>]: UnwrapRef<T[K]>;
|
||||||
|
};
|
||||||
|
type FilterStateProperties<T extends Record<string, unknown>> = {
|
||||||
|
[K in keyof T]: T[K] extends ComputedImpl
|
||||||
|
? never
|
||||||
|
: T[K] extends RefType
|
||||||
|
? K
|
||||||
|
: T[K] extends Record<any, unknown> // Reactive类型
|
||||||
|
? K
|
||||||
|
: never;
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
// Filter action properties
|
||||||
|
export type FilterAction<T extends Record<string, unknown>> = {
|
||||||
|
[K in FilterFunctionProperties<T>]: T[K] extends AnyFunction ? T[K] : never;
|
||||||
|
};
|
||||||
|
type FilterFunctionProperties<T extends Record<string, unknown>> = {
|
||||||
|
[K in keyof T]: T[K] extends AnyFunction ? K : never;
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
// Filter computed properties
|
||||||
|
export type FilterComputed<T extends Record<string, unknown>> = {
|
||||||
|
[K in FilterComputedProperties<T>]: T[K] extends ComputedImpl<infer T> ? (T extends AnyFunction ? T : never) : never;
|
||||||
|
};
|
||||||
|
type FilterComputedProperties<T extends Record<string, unknown>> = {
|
||||||
|
[K in keyof T]: T[K] extends ComputedImpl ? K : never;
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
export type StoreToRefsReturn<S extends Record<string, unknown>, C extends Record<string, AnyFunction>> = {
|
||||||
|
[K in keyof S]: RefType<S[K]>;
|
||||||
|
} & {
|
||||||
|
[K in keyof ComputedType<C, S>]: Readonly<RefType<ReturnType<ComputedType<C, S>[K]>>>;
|
||||||
|
};
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
*
|
*
|
||||||
* openInula is licensed under Mulan PSL v2.
|
* openInula is licensed under Mulan PSL v2.
|
||||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
import { useEffect, useLayoutEffect, useRef } from 'openinula';
|
import { useEffect, useLayoutEffect, useRef } from 'openinula';
|
||||||
|
import { FN } from './types';
|
||||||
|
|
||||||
// 用于存储组件是否已挂载的状态
|
// 用于存储组件是否已挂载的状态
|
||||||
const useIsMounted = () => {
|
const useIsMounted = () => {
|
||||||
|
@ -26,32 +27,32 @@ const useIsMounted = () => {
|
||||||
return isMounted.current;
|
return isMounted.current;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onBeforeMount = (fn: () => void) => {
|
export const onBeforeMount = (fn: FN) => {
|
||||||
const isMounted = useIsMounted();
|
const isMounted = useIsMounted();
|
||||||
if (!isMounted) {
|
if (!isMounted) {
|
||||||
fn?.();
|
fn?.();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function onMounted(fn: () => void) {
|
export function onMounted(fn: FN) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fn?.();
|
fn?.();
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onBeforeUpdated(fn: () => void) {
|
export function onBeforeUpdated(fn: FN) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fn?.();
|
fn?.();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onUpdated(fn: () => void) {
|
export function onUpdated(fn: FN) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fn?.();
|
fn?.();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const onBeforeUnmount = (fn: () => void) => {
|
export const onBeforeUnmount = (fn: FN) => {
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
fn?.();
|
fn?.();
|
||||||
|
@ -59,7 +60,7 @@ export const onBeforeUnmount = (fn: () => void) => {
|
||||||
}, []);
|
}, []);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function onUnmounted(fn: () => void) {
|
export function onUnmounted(fn: FN) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
fn?.();
|
fn?.();
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
*
|
*
|
||||||
* openInula is licensed under Mulan PSL v2.
|
* openInula is licensed under Mulan PSL v2.
|
||||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
@ -13,8 +13,4 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default {
|
export type FN = () => void;
|
||||||
test: {
|
|
||||||
environment: 'jsdom',
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './vuex';
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type AnyFunction = (...args: any[]) => any;
|
||||||
|
|
||||||
|
export interface VuexStoreOptions<
|
||||||
|
State extends Record<string, unknown> = Record<string, unknown>,
|
||||||
|
Mutations extends Record<string, AnyFunction> = Record<string, AnyFunction>,
|
||||||
|
Actions extends Record<string, AnyFunction> = Record<string, AnyFunction>,
|
||||||
|
Getters extends Record<string, AnyFunction> = Record<string, AnyFunction>,
|
||||||
|
RootState extends Record<string, unknown> = Record<string, unknown>,
|
||||||
|
RootGetters extends Record<string, AnyFunction> = Record<string, AnyFunction>,
|
||||||
|
Modules extends Record<string, Record<string, unknown>> = Record<string, Record<string, unknown>>,
|
||||||
|
> {
|
||||||
|
namespaced?: boolean;
|
||||||
|
state?: State | (() => State);
|
||||||
|
mutations?: MutationsType<Mutations, State>;
|
||||||
|
actions?: ActionsType<Actions, State, Getters, RootState, RootGetters>;
|
||||||
|
getters?: GettersType<State, Getters, State, Getters>;
|
||||||
|
modules?: {
|
||||||
|
[k in keyof Modules]: VuexStoreOptions<Modules[k]>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type MutationsType<Mutations, State> = {
|
||||||
|
[K in keyof Mutations]: AddFirstArg<Mutations[K], State>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ActionsType<Actions, State, Getters, RootState, RootGetters> = {
|
||||||
|
[K in keyof Actions]: AddFirstArg<
|
||||||
|
Actions[K],
|
||||||
|
{
|
||||||
|
commit: CommitType;
|
||||||
|
dispatch: DispatchType;
|
||||||
|
state: State;
|
||||||
|
getters: Getters;
|
||||||
|
rootState: RootState;
|
||||||
|
rootGetters: RootGetters;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AddFirstArg<T, S> = T extends (arg1: any, ...args: infer A) => infer R
|
||||||
|
? (state: S, ...args: A) => R
|
||||||
|
: T extends () => infer R
|
||||||
|
? (state: S) => R
|
||||||
|
: T;
|
||||||
|
|
||||||
|
type GettersType<State, Getters, RootState, RootGetters> = {
|
||||||
|
[K in keyof Getters]: AddArgs<Getters[K], [State, Getters, RootState, RootGetters]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AddArgs<T, Args extends any[]> = T extends (...args: infer A) => infer R
|
||||||
|
? (...args: [...Args, ...A]) => R
|
||||||
|
: T extends () => infer R
|
||||||
|
? (...args: Args) => R
|
||||||
|
: T;
|
||||||
|
|
||||||
|
export type CommitType = (
|
||||||
|
type: string | (Record<string, unknown> & { type: string }),
|
||||||
|
payload?: any,
|
||||||
|
options?: Record<string, unknown>,
|
||||||
|
moduleName?: string
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export type DispatchType = (
|
||||||
|
type: string | (Record<string, unknown> & { type: string }),
|
||||||
|
payload?: any,
|
||||||
|
options?: Record<string, unknown>,
|
||||||
|
moduleName?: string
|
||||||
|
) => any;
|
||||||
|
|
||||||
|
export type VuexStore<
|
||||||
|
State extends Record<string, unknown> = Record<string, unknown>,
|
||||||
|
Getters extends Record<string, AnyFunction> = Record<string, AnyFunction>,
|
||||||
|
Modules extends Record<string, Record<string, unknown>> = Record<string, Record<string, unknown>>,
|
||||||
|
> = {
|
||||||
|
state: State & {
|
||||||
|
[K in keyof Modules]: Modules[K] extends { state: infer ModuleState } ? ModuleState : Modules[K];
|
||||||
|
};
|
||||||
|
getters: {
|
||||||
|
[K in keyof Getters]: ReturnType<Getters[K]>;
|
||||||
|
};
|
||||||
|
commit: CommitType;
|
||||||
|
dispatch: DispatchType;
|
||||||
|
subscribe: AnyFunction;
|
||||||
|
subscribeAction: AnyFunction;
|
||||||
|
watch: (fn: (state: State, getters: Getters) => void, cb: AnyFunction) => void;
|
||||||
|
registerModule: (moduleName: string, module: VuexStoreOptions) => void;
|
||||||
|
unregisterModule: (moduleName: string) => void;
|
||||||
|
hasModule: (moduleName: string) => boolean;
|
||||||
|
};
|
|
@ -0,0 +1,315 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createStore as createStoreX, StoreObj, vueReactive } from 'openinula';
|
||||||
|
import { VuexStore, VuexStoreOptions } from './types';
|
||||||
|
import { AnyFunction } from '../pinia/types';
|
||||||
|
|
||||||
|
const { watch } = vueReactive;
|
||||||
|
|
||||||
|
const MUTATION_PREFIX = 'm_';
|
||||||
|
const GETTER_PREFIX = 'g_';
|
||||||
|
|
||||||
|
type GettersMap<T extends StoreObj = StoreObj> = {
|
||||||
|
[K in keyof T['$c']]: ReturnType<T['$c'][K]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createStore<
|
||||||
|
State extends Record<string, unknown> = Record<string, unknown>,
|
||||||
|
Mutations extends Record<string, AnyFunction> = Record<string, AnyFunction>,
|
||||||
|
Actions extends Record<string, AnyFunction> = Record<string, AnyFunction>,
|
||||||
|
Getters extends Record<string, AnyFunction> = Record<string, AnyFunction>,
|
||||||
|
RootState extends Record<string, unknown> = Record<string, unknown>,
|
||||||
|
RootGetters extends Record<string, AnyFunction> = Record<string, AnyFunction>,
|
||||||
|
Modules extends Record<string, Record<string, unknown>> = Record<string, Record<string, unknown>>,
|
||||||
|
>(
|
||||||
|
options: VuexStoreOptions<State, Mutations, Actions, Getters, RootState, RootGetters, Modules>
|
||||||
|
): VuexStore<State, Getters, Modules> {
|
||||||
|
const modules = options.modules || {};
|
||||||
|
|
||||||
|
const _modules: Record<string, { storeX: StoreObj; namespaced: boolean }> = {};
|
||||||
|
|
||||||
|
const _getters: GettersMap = {};
|
||||||
|
|
||||||
|
const vuexStore: VuexStore = {
|
||||||
|
state: new Proxy(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: (_, key) => {
|
||||||
|
if (key in _modules) {
|
||||||
|
return _modules[key as string].storeX;
|
||||||
|
} else {
|
||||||
|
return rootStoreX[key as string];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
getters: new Proxy(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: (_, key) => {
|
||||||
|
if (typeof key === 'string') {
|
||||||
|
// 如果key包含/,说明是访问模块的getters,进行split
|
||||||
|
if (key.includes('/')) {
|
||||||
|
const [moduleName, getterKey] = key.split('/');
|
||||||
|
return _modules[moduleName].storeX[`${GETTER_PREFIX}${getterKey}`];
|
||||||
|
} else {
|
||||||
|
return _getters[`${GETTER_PREFIX}${key}`];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
commit: (_type, _payload, _options, moduleName) => {
|
||||||
|
const { type, payload, options } = prepareTypeParams(_type, _payload, _options);
|
||||||
|
// 如果options.root为true,调用根store的action
|
||||||
|
if (options?.root) {
|
||||||
|
return rootStoreX[`${MUTATION_PREFIX}${type}`](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 包含/,说明是访问模块的mutation
|
||||||
|
if (type.includes('/')) {
|
||||||
|
const [moduleName, key] = type.split('/');
|
||||||
|
return _modules[moduleName].storeX[`${MUTATION_PREFIX}${key}`](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moduleName != undefined) {
|
||||||
|
// dispatch到指定的module
|
||||||
|
return _modules[moduleName].storeX[`${MUTATION_PREFIX}${type}`](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用所有非namespaced的modules的mutation
|
||||||
|
Object.values(_modules).forEach(module => {
|
||||||
|
if (!module.namespaced) {
|
||||||
|
const mutation = module.storeX[`${MUTATION_PREFIX}${type}`];
|
||||||
|
if (typeof mutation === 'function') {
|
||||||
|
mutation(payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 调用storeX对象上的方法
|
||||||
|
if (rootStoreX[`${MUTATION_PREFIX}${type}`]) {
|
||||||
|
rootStoreX[`${MUTATION_PREFIX}${type}`](payload);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dispatch: (_type, _payload, _options, moduleName) => {
|
||||||
|
const { type, payload, options } = prepareTypeParams(_type, _payload, _options);
|
||||||
|
// 如果options.root为true,调用根store的action
|
||||||
|
if (options?.root) {
|
||||||
|
return rootStoreX[type](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 包含/,说明是访问模块的action
|
||||||
|
if (type.includes('/')) {
|
||||||
|
const [moduleName, key] = type.split('/');
|
||||||
|
return _modules[moduleName].storeX[key](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moduleName != undefined) {
|
||||||
|
// dispatch到指定的module
|
||||||
|
return _modules[moduleName].storeX[type](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把每个action的返回值合并起来,支持then链式调用
|
||||||
|
const results: any[] = [];
|
||||||
|
|
||||||
|
// 调用所有非namespaced的modules的action
|
||||||
|
Object.values(_modules).forEach(module => {
|
||||||
|
if (!module.namespaced) {
|
||||||
|
const action = module.storeX[type];
|
||||||
|
if (typeof action === 'function') {
|
||||||
|
results.push(action(payload));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 调用storeX对象上的方法
|
||||||
|
if (typeof rootStoreX[type] === 'function') {
|
||||||
|
results.push(rootStoreX[type](payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回一个Promise,内容是results,支持then链式调用
|
||||||
|
return Promise.all(results);
|
||||||
|
},
|
||||||
|
subscribe(fn) {
|
||||||
|
return rootStoreX.$subscribe(fn);
|
||||||
|
},
|
||||||
|
subscribeAction(fn) {
|
||||||
|
return rootStoreX.$subscribe(fn);
|
||||||
|
},
|
||||||
|
watch(fn, cb) {
|
||||||
|
watch(() => fn(vuexStore.state, vuexStore.getters), cb);
|
||||||
|
},
|
||||||
|
// 动态注册模块
|
||||||
|
registerModule(key, module) {
|
||||||
|
_modules[key] = { storeX: _createStoreX(key, module, vuexStore, rootStoreX), namespaced: !!module.namespaced };
|
||||||
|
collectGetters(_modules[key].storeX, _getters);
|
||||||
|
},
|
||||||
|
// 动态注销模块
|
||||||
|
unregisterModule(key) {
|
||||||
|
deleteGetters(_modules[key].storeX, _getters);
|
||||||
|
delete _modules[key];
|
||||||
|
},
|
||||||
|
hasModule(path) {
|
||||||
|
return path in _modules;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const rootStoreX = _createStoreX(undefined, options as VuexStoreOptions, vuexStore);
|
||||||
|
collectGetters(rootStoreX, _getters);
|
||||||
|
|
||||||
|
// 递归创建子模块
|
||||||
|
for (const [moduleName, moduleOptions] of Object.entries(modules)) {
|
||||||
|
_modules[moduleName] = {
|
||||||
|
storeX: _createStoreX(moduleName, moduleOptions as VuexStoreOptions, vuexStore, rootStoreX),
|
||||||
|
namespaced: !!(moduleOptions as VuexStoreOptions).namespaced,
|
||||||
|
};
|
||||||
|
collectGetters(_modules[moduleName].storeX, _getters);
|
||||||
|
}
|
||||||
|
|
||||||
|
return vuexStore as VuexStore<State, Getters, Modules>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareTypeParams(
|
||||||
|
type: string | (Record<string, unknown> & { type: string }),
|
||||||
|
payload?: any,
|
||||||
|
options?: Record<string, unknown>
|
||||||
|
) {
|
||||||
|
if (typeof type === 'object' && type.type) {
|
||||||
|
options = payload;
|
||||||
|
payload = type;
|
||||||
|
type = type.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type, payload, options } as {
|
||||||
|
type: string;
|
||||||
|
payload: any;
|
||||||
|
options: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _createStoreX(
|
||||||
|
moduleName: string | undefined,
|
||||||
|
options: VuexStoreOptions,
|
||||||
|
store: VuexStore,
|
||||||
|
rootStoreX?: any
|
||||||
|
): StoreObj {
|
||||||
|
const { mutations = {}, actions = {}, getters = {} } = options;
|
||||||
|
const state = typeof options.state === 'function' ? options.state() : options.state;
|
||||||
|
|
||||||
|
const storeX: StoreObj = createStoreX({
|
||||||
|
id: moduleName,
|
||||||
|
state: state,
|
||||||
|
actions: {
|
||||||
|
// 给mutations的key增加一个前缀,避免和actions的key冲突
|
||||||
|
...Object.fromEntries(
|
||||||
|
Object.entries(mutations).map(([key, mutation]) => {
|
||||||
|
return [`${MUTATION_PREFIX}${key}`, mutation];
|
||||||
|
})
|
||||||
|
),
|
||||||
|
// 重新定义action的方法,绑定this,修改第一参数
|
||||||
|
...Object.fromEntries(
|
||||||
|
Object.entries(actions).map(([key, action]) => [
|
||||||
|
key,
|
||||||
|
function (this: StoreObj, state: Record<string, unknown>, payload) {
|
||||||
|
rootStoreX = rootStoreX || storeX;
|
||||||
|
const argFirst = {
|
||||||
|
...store,
|
||||||
|
// 覆盖commit方法,多传一个参数moduleName
|
||||||
|
commit: (
|
||||||
|
type: string | (Record<string, unknown> & { type: string }),
|
||||||
|
payload?: any,
|
||||||
|
options?: Record<string, unknown>
|
||||||
|
) => {
|
||||||
|
store.commit(type, payload, options, moduleName);
|
||||||
|
},
|
||||||
|
// 覆盖dispatch方法,多传一个参数moduleName
|
||||||
|
dispatch: (
|
||||||
|
type: string | (Record<string, unknown> & { type: string }),
|
||||||
|
payload?: any,
|
||||||
|
options?: Record<string, unknown>
|
||||||
|
) => {
|
||||||
|
return store.dispatch(type, payload, options, moduleName);
|
||||||
|
},
|
||||||
|
state: storeX.$state,
|
||||||
|
rootState: store.state,
|
||||||
|
getter: store.getters,
|
||||||
|
rootGetters: moduleGettersProxy(rootStoreX),
|
||||||
|
};
|
||||||
|
|
||||||
|
return action.call(storeX, argFirst, payload);
|
||||||
|
},
|
||||||
|
])
|
||||||
|
),
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...Object.fromEntries(
|
||||||
|
Object.entries(getters).map(([key, getter]) => {
|
||||||
|
return [
|
||||||
|
// 给getters的key增加一个前缀,避免和actions, mutations的key冲突
|
||||||
|
`${GETTER_PREFIX}${key}`,
|
||||||
|
// 重新定义getter的方法,绑定this,修改参数: state, getters, rootState, rootGetters
|
||||||
|
function (state: Record<string, unknown>) {
|
||||||
|
rootStoreX = rootStoreX || storeX;
|
||||||
|
return getter.call(
|
||||||
|
storeX,
|
||||||
|
storeX.$state,
|
||||||
|
store.getters,
|
||||||
|
rootStoreX.$state,
|
||||||
|
moduleGettersProxy(rootStoreX)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
];
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})();
|
||||||
|
|
||||||
|
return storeX;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectGetters(storeX: StoreObj, gettersMap: GettersMap): void {
|
||||||
|
Object.keys(storeX.$config.computed).forEach(type => {
|
||||||
|
Object.defineProperty(gettersMap, type, {
|
||||||
|
get: () => storeX.$c[type],
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteGetters(storeX: StoreObj, gettersMap: GettersMap): void {
|
||||||
|
Object.keys(storeX.$config.computed).forEach(type => {
|
||||||
|
// 删除Object.defineProperty定义的属性
|
||||||
|
Object.defineProperty(gettersMap, type, {
|
||||||
|
value: undefined,
|
||||||
|
writable: true,
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
delete gettersMap[type];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function moduleGettersProxy(storeX: StoreObj) {
|
||||||
|
return new Proxy(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: (_, key) => {
|
||||||
|
return storeX[`${GETTER_PREFIX}${key as string}`];
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
|
@ -12,80 +12,66 @@
|
||||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-nocheck For the compiled code.
|
|
||||||
|
|
||||||
import { describe, it, vi, expect } from 'vitest';
|
import { describe, it, vi, expect, beforeEach } from 'vitest';
|
||||||
import { defineStore } from '../src/pinia';
|
import { defineStore } from '../../src/pinia/pinia';
|
||||||
|
|
||||||
describe('pinia state', () => {
|
let id = 0;
|
||||||
const useStore = () => {
|
function createStore() {
|
||||||
return defineStore({
|
return defineStore({
|
||||||
id: 'main',
|
id: String(id++),
|
||||||
state: () => ({
|
state: () => ({
|
||||||
a: true,
|
a: true,
|
||||||
nested: {
|
nested: {
|
||||||
foo: 'foo',
|
foo: 'foo',
|
||||||
a: { b: 'string' },
|
a: { b: 'string' },
|
||||||
},
|
|
||||||
}),
|
|
||||||
getters: {
|
|
||||||
nonA(): boolean {
|
|
||||||
return !this.a;
|
|
||||||
},
|
|
||||||
otherComputed() {
|
|
||||||
return this.nonA;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
actions: {
|
}),
|
||||||
async getNonA() {
|
getters: {
|
||||||
return this.nonA;
|
nonA(): boolean {
|
||||||
},
|
return !this.a;
|
||||||
simple() {
|
|
||||||
this.toggle();
|
|
||||||
return 'simple';
|
|
||||||
},
|
|
||||||
|
|
||||||
toggle() {
|
|
||||||
return (this.a = !this.a);
|
|
||||||
},
|
|
||||||
|
|
||||||
setFoo(foo: string) {
|
|
||||||
this.nested = { foo };
|
|
||||||
},
|
|
||||||
|
|
||||||
combined() {
|
|
||||||
this.toggle();
|
|
||||||
this.setFoo('bar');
|
|
||||||
},
|
|
||||||
|
|
||||||
throws() {
|
|
||||||
throw new Error('fail');
|
|
||||||
},
|
|
||||||
|
|
||||||
async rejects() {
|
|
||||||
throw 'fail';
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})();
|
otherComputed() {
|
||||||
};
|
return this.nonA;
|
||||||
|
|
||||||
const useB = defineStore({
|
|
||||||
id: 'B',
|
|
||||||
state: () => ({ b: 'b' }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const useA = defineStore({
|
|
||||||
id: 'A',
|
|
||||||
state: () => ({ a: 'a' }),
|
|
||||||
actions: {
|
|
||||||
swap() {
|
|
||||||
const bStore = useB();
|
|
||||||
const b = bStore.$state.b;
|
|
||||||
bStore.$state.b = this.$state.a;
|
|
||||||
this.$state.a = b;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
actions: {
|
||||||
|
async getNonA() {
|
||||||
|
return this.nonA;
|
||||||
|
},
|
||||||
|
simple() {
|
||||||
|
this.toggle();
|
||||||
|
return 'simple';
|
||||||
|
},
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
return (this.a = !this.a);
|
||||||
|
},
|
||||||
|
|
||||||
|
setFoo(foo: string) {
|
||||||
|
this.nested.foo = foo;
|
||||||
|
},
|
||||||
|
|
||||||
|
combined() {
|
||||||
|
this.toggle();
|
||||||
|
this.setFoo('bar');
|
||||||
|
},
|
||||||
|
|
||||||
|
throws() {
|
||||||
|
throw new Error('fail');
|
||||||
|
},
|
||||||
|
|
||||||
|
async rejects() {
|
||||||
|
throw 'fail';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
describe('pinia state', () => {
|
||||||
|
let useStore = createStore();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
useStore = createStore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can use the store as this', () => {
|
it('can use the store as this', () => {
|
||||||
|
@ -144,7 +130,6 @@ describe('pinia state', () => {
|
||||||
// override the function like devtools do
|
// override the function like devtools do
|
||||||
expect(
|
expect(
|
||||||
{
|
{
|
||||||
$id: store.$id,
|
|
||||||
simple,
|
simple,
|
||||||
// otherwise it would fail
|
// otherwise it would fail
|
||||||
toggle() {},
|
toggle() {},
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import { defineStore } from '../../src/pinia/pinia';
|
||||||
|
|
||||||
|
let id = 0;
|
||||||
|
function createStore() {
|
||||||
|
return defineStore({
|
||||||
|
id: String(id++),
|
||||||
|
state: () => ({
|
||||||
|
name: 'Eduardo',
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
upperCaseName(store) {
|
||||||
|
return store.name.toUpperCase();
|
||||||
|
},
|
||||||
|
doubleName(): string {
|
||||||
|
return this.upperCaseName;
|
||||||
|
},
|
||||||
|
composed(): string {
|
||||||
|
return this.upperCaseName + ': ok';
|
||||||
|
},
|
||||||
|
arrowUpper: state => {
|
||||||
|
// @ts-expect-error
|
||||||
|
state.nope;
|
||||||
|
return state.name.toUpperCase();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
o() {
|
||||||
|
this.arrowUpper.toUpperCase();
|
||||||
|
this.o().toUpperCase();
|
||||||
|
return 'a string';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('pinia getters', () => {
|
||||||
|
let useStore = createStore();
|
||||||
|
let useB;
|
||||||
|
let useA;
|
||||||
|
beforeEach(() => {
|
||||||
|
useStore = createStore();
|
||||||
|
|
||||||
|
useB = defineStore({
|
||||||
|
id: 'B',
|
||||||
|
state: () => ({ b: 'b' }),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds getters to the store', () => {
|
||||||
|
const store = useStore();
|
||||||
|
expect(store.upperCaseName).toBe('EDUARDO');
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
store.nope;
|
||||||
|
|
||||||
|
store.name = 'Ed';
|
||||||
|
expect(store.upperCaseName).toBe('ED');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates the value', () => {
|
||||||
|
const store = useStore();
|
||||||
|
store.name = 'Ed';
|
||||||
|
expect(store.upperCaseName).toBe('ED');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can use other getters', () => {
|
||||||
|
const store = useStore();
|
||||||
|
expect(store.composed).toBe('EDUARDO: ok');
|
||||||
|
store.name = 'Ed';
|
||||||
|
expect(store.composed).toBe('ED: ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps getters reactive when hydrating', () => {
|
||||||
|
const store = useStore();
|
||||||
|
store.name = 'Jack';
|
||||||
|
expect(store.name).toBe('Jack');
|
||||||
|
expect(store.upperCaseName).toBe('JACK');
|
||||||
|
store.name = 'Ed';
|
||||||
|
expect(store.upperCaseName).toBe('ED');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { beforeEach, describe, it, vi, expect } from 'vitest';
|
||||||
|
import { createPinia, defineStore } from '../../src/pinia/pinia';
|
||||||
|
import { vueReactive } from 'openinula';
|
||||||
|
|
||||||
|
const { watch, computed, ref, reactive } = vueReactive;
|
||||||
|
|
||||||
|
let id = 0;
|
||||||
|
function createStore() {
|
||||||
|
return defineStore(String(id++), {
|
||||||
|
state: () => ({
|
||||||
|
name: 'Eduardo',
|
||||||
|
counter: 0,
|
||||||
|
nested: { n: 0 },
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
increment(state, amount) {
|
||||||
|
this.counter += amount;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
upperCased() {
|
||||||
|
return this.name.toUpperCase();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
describe('pinia state', () => {
|
||||||
|
let useStore = createStore();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
useStore = createStore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can directly access state at the store level', () => {
|
||||||
|
const store = useStore();
|
||||||
|
expect(store.name).toBe('Eduardo');
|
||||||
|
store.name = 'Ed';
|
||||||
|
expect(store.name).toBe('Ed');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('state is reactive', () => {
|
||||||
|
const store = useStore();
|
||||||
|
const upperCased = computed(() => store.name.toUpperCase());
|
||||||
|
expect(upperCased.value).toBe('EDUARDO');
|
||||||
|
store.name = 'Ed';
|
||||||
|
expect(upperCased.value).toBe('ED');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be set on store', () => {
|
||||||
|
const pinia = createPinia();
|
||||||
|
const store = useStore(pinia);
|
||||||
|
|
||||||
|
store.name = 'a';
|
||||||
|
|
||||||
|
expect(store.name).toBe('a');
|
||||||
|
expect(store.$state.name).toBe('a');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be set on store.$state', () => {
|
||||||
|
const pinia = createPinia();
|
||||||
|
const store = useStore(pinia);
|
||||||
|
|
||||||
|
store.$state.name = 'a';
|
||||||
|
|
||||||
|
expect(store.name).toBe('a');
|
||||||
|
expect(store.$state.name).toBe('a');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be nested set on store', () => {
|
||||||
|
const pinia = createPinia();
|
||||||
|
const store = useStore(pinia);
|
||||||
|
|
||||||
|
store.nested.n = 3;
|
||||||
|
|
||||||
|
expect(store.nested.n).toBe(3);
|
||||||
|
expect(store.$state.nested.n).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be nested set on store.$state', () => {
|
||||||
|
const pinia = createPinia();
|
||||||
|
const store = useStore(pinia);
|
||||||
|
|
||||||
|
store.$state.nested.n = 3;
|
||||||
|
|
||||||
|
expect(store.nested.n).toBe(3);
|
||||||
|
expect(store.$state.nested.n).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('state can be watched', async () => {
|
||||||
|
const store = useStore();
|
||||||
|
const spy = vi.fn();
|
||||||
|
watch(() => store.name, spy);
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
store.name = 'Ed';
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('state can be watched when a ref is given', async () => {
|
||||||
|
const store = useStore();
|
||||||
|
const spy = vi.fn();
|
||||||
|
watch(() => store.name, spy);
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
const nameRef = ref('Ed');
|
||||||
|
// @ts-expect-error
|
||||||
|
store.$state.name = nameRef;
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be given a ref', () => {
|
||||||
|
const pinia = createPinia();
|
||||||
|
const store = useStore(pinia);
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
store.$state.name = ref('Ed');
|
||||||
|
|
||||||
|
expect(store.name).toBe('Ed');
|
||||||
|
expect(store.$state.name).toBe('Ed');
|
||||||
|
|
||||||
|
store.name = 'Other';
|
||||||
|
expect(store.name).toBe('Other');
|
||||||
|
expect(store.$state.name).toBe('Other');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { beforeEach, describe, it, vi, expect } from 'vitest';
|
||||||
|
import { defineStore } from '../../src/pinia/pinia';
|
||||||
|
import { vueReactive } from 'openinula';
|
||||||
|
|
||||||
|
const { watch } = vueReactive;
|
||||||
|
|
||||||
|
let id = 0;
|
||||||
|
function createStore() {
|
||||||
|
return defineStore({
|
||||||
|
id: String(id++),
|
||||||
|
state: () => ({
|
||||||
|
a: true,
|
||||||
|
nested: {
|
||||||
|
foo: 'foo',
|
||||||
|
a: { b: 'string' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('pinia state', () => {
|
||||||
|
let useStore = createStore();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
useStore = createStore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reuses a store', () => {
|
||||||
|
const useStore = defineStore({ id: String(id++) });
|
||||||
|
expect(useStore()).toBe(useStore());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with id as first argument', () => {
|
||||||
|
const useStore = defineStore(String(id++), {
|
||||||
|
state: () => ({
|
||||||
|
a: true,
|
||||||
|
nested: {
|
||||||
|
foo: 'foo',
|
||||||
|
a: { b: 'string' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
expect(useStore()).toBe(useStore());
|
||||||
|
const useStoreEmpty = defineStore(String(id++), {});
|
||||||
|
expect(useStoreEmpty()).toBe(useStoreEmpty());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the initial state', () => {
|
||||||
|
const store = useStore();
|
||||||
|
expect(store.$state).toEqual({
|
||||||
|
a: true,
|
||||||
|
nested: {
|
||||||
|
foo: 'foo',
|
||||||
|
a: { b: 'string' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('can replace its state', () => {
|
||||||
|
const store = useStore();
|
||||||
|
const spy = vi.fn();
|
||||||
|
watch(() => store.a, spy);
|
||||||
|
expect(store.a).toBe(true);
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalledTimes(0);
|
||||||
|
store.$state = {
|
||||||
|
a: false,
|
||||||
|
nested: {
|
||||||
|
foo: 'bar',
|
||||||
|
a: {
|
||||||
|
b: 'hey',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(store.$state).toEqual({
|
||||||
|
a: false,
|
||||||
|
nested: {
|
||||||
|
foo: 'bar',
|
||||||
|
a: { b: 'hey' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be $unsubscribe', () => {
|
||||||
|
const useStore = defineStore({
|
||||||
|
id: 'main',
|
||||||
|
state: () => ({ n: 0 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = useStore();
|
||||||
|
const spy = vi.fn();
|
||||||
|
|
||||||
|
store.$subscribe(spy);
|
||||||
|
store.$state.n++;
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(useStore()).toBe(store);
|
||||||
|
store.$unsubscribe(spy);
|
||||||
|
store.$state.n++;
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,152 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { beforeEach, describe, it, vi, expect } from 'vitest';
|
||||||
|
import { defineStore } from '../../src/pinia/pinia';
|
||||||
|
import { vueReactive } from 'openinula';
|
||||||
|
|
||||||
|
const { ref, watch, computed } = vueReactive;
|
||||||
|
|
||||||
|
function expectType<T>(_value: T): void {}
|
||||||
|
describe('store with setup syntax', () => {
|
||||||
|
function mainFn() {
|
||||||
|
const name = ref('Eduardo');
|
||||||
|
const counter = ref(0);
|
||||||
|
|
||||||
|
function increment(amount = 1) {
|
||||||
|
counter.value += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double = computed(() => counter.value * 2);
|
||||||
|
|
||||||
|
return { name, counter, increment, double };
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = 0;
|
||||||
|
function createStore() {
|
||||||
|
return defineStore(String(id++), mainFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
let useStore = createStore();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
useStore = createStore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract the $state', () => {
|
||||||
|
const store = useStore();
|
||||||
|
expectType<{ name: string; counter: number }>(store.$state);
|
||||||
|
expect(store.$state).toEqual({ name: 'Eduardo', counter: 0 });
|
||||||
|
expect(store.name).toBe('Eduardo');
|
||||||
|
expect(store.counter).toBe(0);
|
||||||
|
expect(store.double).toBe(0);
|
||||||
|
store.increment();
|
||||||
|
expect(store.counter).toBe(1);
|
||||||
|
expect(store.double).toBe(2);
|
||||||
|
expect(store.$state).toEqual({ name: 'Eduardo', counter: 1 });
|
||||||
|
expect(store.$state).not.toHaveProperty('double');
|
||||||
|
expect(store.$state).not.toHaveProperty('increment');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can store a function', () => {
|
||||||
|
const store = defineStore(String(id++), () => {
|
||||||
|
const fn = ref(() => {});
|
||||||
|
|
||||||
|
function action() {}
|
||||||
|
|
||||||
|
return { fn, action };
|
||||||
|
})();
|
||||||
|
expectType<{ fn: () => void }>(store.$state);
|
||||||
|
expect(store.$state).toEqual({ fn: expect.any(Function) });
|
||||||
|
expect(store.fn).toEqual(expect.any(Function));
|
||||||
|
store.action();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can directly access state at the store level', () => {
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
expect(store.name).toBe('Eduardo');
|
||||||
|
store.name = 'Ed';
|
||||||
|
expect(store.name).toBe('Ed');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('state is reactive', () => {
|
||||||
|
const store = useStore();
|
||||||
|
const upperCased = computed(() => store.name.toUpperCase());
|
||||||
|
expect(upperCased.value).toBe('EDUARDO');
|
||||||
|
store.name = 'Ed';
|
||||||
|
expect(upperCased.value).toBe('ED');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('state can be watched', async () => {
|
||||||
|
const store = useStore();
|
||||||
|
const spy = vi.fn();
|
||||||
|
watch(() => store.name, spy);
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
store.name = 'Ed';
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('state refs can be watched', async () => {
|
||||||
|
const store = useStore();
|
||||||
|
const spy = vi.fn();
|
||||||
|
watch(() => store.name, spy);
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
const nameRef = ref('Ed');
|
||||||
|
store.name = nameRef;
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unwraps refs', () => {
|
||||||
|
const name = ref('Eduardo');
|
||||||
|
const counter = ref(0);
|
||||||
|
const double = computed(() => {
|
||||||
|
return counter.value * 2;
|
||||||
|
});
|
||||||
|
|
||||||
|
// const pinia = createPinia();
|
||||||
|
// setActivePinia(pinia);
|
||||||
|
const useStore = defineStore({
|
||||||
|
id: String(id++),
|
||||||
|
state: () => ({
|
||||||
|
name,
|
||||||
|
counter,
|
||||||
|
double,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
expect(store.name).toBe('Eduardo');
|
||||||
|
expect(store.$state.name).toBe('Eduardo');
|
||||||
|
expect(store.$state).toEqual({
|
||||||
|
name: 'Eduardo',
|
||||||
|
counter: 0,
|
||||||
|
double: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
name.value = 'Ed';
|
||||||
|
expect(store.name).toBe('Ed');
|
||||||
|
expect(store.$state.name).toBe('Ed');
|
||||||
|
|
||||||
|
store.$state.name = 'Edu';
|
||||||
|
expect(store.name).toBe('Edu');
|
||||||
|
|
||||||
|
// store.$patch({ counter: 2 });
|
||||||
|
store.counter = 2;
|
||||||
|
expect(store.counter).toBe(2);
|
||||||
|
expect(counter.value).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,141 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { beforeEach, describe, it, vi, expect } from 'vitest';
|
||||||
|
import { defineStore, storeToRefs } from '../../src/pinia/pinia';
|
||||||
|
import { vueReactive } from 'openinula';
|
||||||
|
|
||||||
|
const { ref, computed, reactive } = vueReactive;
|
||||||
|
|
||||||
|
let id = 0;
|
||||||
|
describe('storeToRefs', () => {
|
||||||
|
beforeEach(() => {});
|
||||||
|
|
||||||
|
function objectOfRefs<O extends Record<any, any>>(o: O) {
|
||||||
|
return Object.keys(o).reduce((newO, key) => {
|
||||||
|
// @ts-expect-error: we only need to match
|
||||||
|
newO[key] = expect.objectContaining({ value: o[key] });
|
||||||
|
return newO;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('empty state', () => {
|
||||||
|
expect(storeToRefs(defineStore(String(id++), {})())).toEqual({});
|
||||||
|
expect(storeToRefs(defineStore({ id: String(id++) })())).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('plain values', () => {
|
||||||
|
const store = defineStore(String(id++), {
|
||||||
|
state: () => ({ a: null as null | undefined, b: false, c: 1, d: 'd' }),
|
||||||
|
})();
|
||||||
|
|
||||||
|
const { a, b, c, d } = storeToRefs(store);
|
||||||
|
|
||||||
|
expect(a.value).toBe(null);
|
||||||
|
expect(b.value).toBe(false);
|
||||||
|
expect(c.value).toBe(1);
|
||||||
|
expect(d.value).toBe('d');
|
||||||
|
|
||||||
|
a.value = undefined;
|
||||||
|
expect(a.value).toBe(undefined);
|
||||||
|
|
||||||
|
b.value = true;
|
||||||
|
expect(b.value).toBe(true);
|
||||||
|
|
||||||
|
c.value = 2;
|
||||||
|
expect(c.value).toBe(2);
|
||||||
|
|
||||||
|
d.value = 'e';
|
||||||
|
expect(d.value).toBe('e');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setup store', () => {
|
||||||
|
const store = defineStore(String(id++), () => {
|
||||||
|
return {
|
||||||
|
a: ref<null | undefined>(null),
|
||||||
|
b: ref(false),
|
||||||
|
c: ref(1),
|
||||||
|
d: ref('d'),
|
||||||
|
r: reactive({ n: 1 }),
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
const { a, b, c, d, r } = storeToRefs(store);
|
||||||
|
|
||||||
|
expect(a.value).toBe(null);
|
||||||
|
expect(b.value).toBe(false);
|
||||||
|
expect(c.value).toBe(1);
|
||||||
|
expect(d.value).toBe('d');
|
||||||
|
expect(r.value).toEqual({ n: 1 });
|
||||||
|
|
||||||
|
a.value = undefined;
|
||||||
|
expect(a.value).toBe(undefined);
|
||||||
|
|
||||||
|
b.value = true;
|
||||||
|
expect(b.value).toBe(true);
|
||||||
|
|
||||||
|
c.value = 2;
|
||||||
|
expect(c.value).toBe(2);
|
||||||
|
|
||||||
|
d.value = 'e';
|
||||||
|
expect(d.value).toBe('e');
|
||||||
|
|
||||||
|
r.value.n++;
|
||||||
|
expect(r.value).toEqual({ n: 2 });
|
||||||
|
expect(store.r).toEqual({ n: 2 });
|
||||||
|
store.r.n++;
|
||||||
|
expect(r.value).toEqual({ n: 3 });
|
||||||
|
expect(store.r).toEqual({ n: 3 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('empty getters', () => {
|
||||||
|
expect(
|
||||||
|
storeToRefs(
|
||||||
|
defineStore(String(id++), {
|
||||||
|
state: () => ({ n: 0 }),
|
||||||
|
})()
|
||||||
|
)
|
||||||
|
).toEqual(objectOfRefs({ n: 0 }));
|
||||||
|
expect(
|
||||||
|
storeToRefs(
|
||||||
|
defineStore(String(id++), () => {
|
||||||
|
return { n: ref(0) };
|
||||||
|
})()
|
||||||
|
)
|
||||||
|
).toEqual(objectOfRefs({ n: 0 }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains getters', () => {
|
||||||
|
const refs = storeToRefs(
|
||||||
|
defineStore(String(id++), {
|
||||||
|
state: () => ({ n: 1 }),
|
||||||
|
getters: {
|
||||||
|
double: state => state.n * 2,
|
||||||
|
},
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
expect(refs).toEqual(objectOfRefs({ n: 1, double: 2 }));
|
||||||
|
|
||||||
|
const setupRefs = storeToRefs(
|
||||||
|
defineStore(String(id++), () => {
|
||||||
|
const n = ref(1);
|
||||||
|
const double = computed(() => n.value * 2);
|
||||||
|
return { n, double };
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(setupRefs).toEqual(objectOfRefs({ n: 1, double: 2 }));
|
||||||
|
});
|
||||||
|
});
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
import { describe, it, vi, expect } from 'vitest';
|
import { describe, it, vi, expect } from 'vitest';
|
||||||
import { render, act, useState } from 'openinula';
|
import { render, act, useState } from 'openinula';
|
||||||
import { onBeforeUnmount, onUnmounted, onMounted, onBeforeMount, onUpdated } from '../src';
|
import { onBeforeUnmount, onUnmounted, onMounted, onBeforeMount, onUpdated } from '../../src/vue/lifecycle';
|
||||||
|
|
||||||
describe('lifecycle', () => {
|
describe('lifecycle', () => {
|
||||||
it('should call the onBeforeMount', () => {
|
it('should call the onBeforeMount', () => {
|
|
@ -0,0 +1,480 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
|
import { createStore } from '../../src/vuex/vuex';
|
||||||
|
|
||||||
|
const TEST = 'TEST';
|
||||||
|
|
||||||
|
describe('Modules', () => {
|
||||||
|
it('dynamic module registration', () => {
|
||||||
|
const store = createStore({
|
||||||
|
modules: {
|
||||||
|
foo: {
|
||||||
|
state: { bar: 1 },
|
||||||
|
mutations: { inc: state => state.bar++ },
|
||||||
|
actions: { inc: ({ commit }) => commit('inc') },
|
||||||
|
getters: { fooBar: state => state.bar },
|
||||||
|
},
|
||||||
|
one: {
|
||||||
|
state: { a: 0 },
|
||||||
|
mutations: {
|
||||||
|
aaa(state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
store.registerModule('hi', {
|
||||||
|
state: { a: 1 },
|
||||||
|
mutations: { inc: state => state.a++ },
|
||||||
|
actions: { incHi: ({ commit }) => commit('inc') },
|
||||||
|
getters: { ga: state => state.a },
|
||||||
|
});
|
||||||
|
|
||||||
|
// expect(store._mutations.inc.length).toBe(2);
|
||||||
|
expect(store.state.hi.a).toBe(1);
|
||||||
|
expect(store.getters.ga).toBe(1);
|
||||||
|
|
||||||
|
// assert initial modules work as expected after dynamic registration
|
||||||
|
expect(store.state.foo.bar).toBe(1);
|
||||||
|
expect(store.getters.fooBar).toBe(1);
|
||||||
|
|
||||||
|
// test dispatching actions defined in dynamic module
|
||||||
|
store.dispatch('incHi');
|
||||||
|
expect(store.state.hi.a).toBe(2);
|
||||||
|
expect(store.getters.ga).toBe(2);
|
||||||
|
|
||||||
|
expect(store.state.foo.bar).toBe(1);
|
||||||
|
expect(store.getters.fooBar).toBe(1);
|
||||||
|
|
||||||
|
// unregister
|
||||||
|
store.unregisterModule('hi');
|
||||||
|
expect(store.state.hi).toBeUndefined();
|
||||||
|
expect(store.getters.ga).toBeUndefined();
|
||||||
|
|
||||||
|
// assert initial modules still work as expected after unregister
|
||||||
|
store.dispatch('inc');
|
||||||
|
expect(store.state.foo.bar).toBe(2);
|
||||||
|
expect(store.getters.fooBar).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dynamic module registration with namespace inheritance', () => {
|
||||||
|
const store = createStore({
|
||||||
|
modules: {
|
||||||
|
a: {
|
||||||
|
namespaced: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const actionSpy = vi.fn();
|
||||||
|
const mutationSpy = vi.fn();
|
||||||
|
store.registerModule('b', {
|
||||||
|
state: { value: 1 },
|
||||||
|
getters: { foo: state => state.value },
|
||||||
|
actions: { foo: actionSpy },
|
||||||
|
mutations: { foo: mutationSpy },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(store.state.b.value).toBe(1);
|
||||||
|
expect(store.getters['foo']).toBe(1);
|
||||||
|
|
||||||
|
store.dispatch('foo');
|
||||||
|
expect(actionSpy).toHaveBeenCalled();
|
||||||
|
|
||||||
|
store.commit('foo');
|
||||||
|
expect(mutationSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dynamic module existance test', () => {
|
||||||
|
const store = createStore({});
|
||||||
|
|
||||||
|
store.registerModule('bonjour', {});
|
||||||
|
|
||||||
|
expect(store.hasModule('bonjour')).toBe(true);
|
||||||
|
store.unregisterModule('bonjour');
|
||||||
|
expect(store.hasModule('bonjour')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep getters when component gets destroyed', async () => {
|
||||||
|
const store = createStore({});
|
||||||
|
|
||||||
|
const spy = vi.fn();
|
||||||
|
|
||||||
|
const moduleA = {
|
||||||
|
namespaced: true,
|
||||||
|
state: () => ({ value: 1 }),
|
||||||
|
getters: {
|
||||||
|
getState(state) {
|
||||||
|
spy();
|
||||||
|
return state.value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
increment: state => {
|
||||||
|
state.value++;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
store.registerModule('moduleA', moduleA);
|
||||||
|
|
||||||
|
expect(store.getters['moduleA/getState']).toBe(1);
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
store.commit('moduleA/increment');
|
||||||
|
|
||||||
|
expect(store.getters['moduleA/getState']).toBe(2);
|
||||||
|
expect(spy).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fire an unrelated watcher', () => {
|
||||||
|
const spy = vi.fn();
|
||||||
|
const store = createStore({
|
||||||
|
modules: {
|
||||||
|
a: {
|
||||||
|
state: { value: 1 },
|
||||||
|
},
|
||||||
|
b: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
store.watch(state => state.a, spy);
|
||||||
|
store.registerModule('c', {
|
||||||
|
state: { value: 2 },
|
||||||
|
});
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('state as function (multiple module in same store)', () => {
|
||||||
|
const store = createStore({
|
||||||
|
modules: {
|
||||||
|
one: {
|
||||||
|
state: { a: 0 },
|
||||||
|
mutations: {
|
||||||
|
[TEST](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
two: {
|
||||||
|
state() {
|
||||||
|
return { a: 0 };
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(store.state.one.a).toBe(0);
|
||||||
|
expect(store.state.two.a).toBe(0);
|
||||||
|
|
||||||
|
store.commit(TEST, 1);
|
||||||
|
expect(store.state.one.a).toBe(1);
|
||||||
|
expect(store.state.two.a).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('state as function (same module in multiple stores)', () => {
|
||||||
|
const storeA = createStore({
|
||||||
|
modules: {
|
||||||
|
foo: {
|
||||||
|
state() {
|
||||||
|
return { a: 0 };
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const storeB = createStore({
|
||||||
|
modules: {
|
||||||
|
bar: {
|
||||||
|
state() {
|
||||||
|
return { a: 0 };
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(storeA.state.foo.a).toBe(0);
|
||||||
|
expect(storeB.state.bar.a).toBe(0);
|
||||||
|
|
||||||
|
storeA.commit(TEST, 1);
|
||||||
|
expect(storeA.state.foo.a).toBe(1);
|
||||||
|
expect(storeB.state.bar.a).toBe(0);
|
||||||
|
|
||||||
|
storeB.commit(TEST, 2);
|
||||||
|
expect(storeA.state.foo.a).toBe(1);
|
||||||
|
expect(storeB.state.bar.a).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('module: mutation', function () {
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
nested: {
|
||||||
|
state: { a: 2 },
|
||||||
|
mutations: {
|
||||||
|
[TEST](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
four: {
|
||||||
|
state: { a: 6 },
|
||||||
|
mutations: {
|
||||||
|
[TEST](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
store.commit(TEST, 1);
|
||||||
|
expect(store.state.a).toBe(2);
|
||||||
|
expect(store.state.nested.a).toBe(3);
|
||||||
|
expect(store.state.four.a).toBe(7);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('module: action', function () {
|
||||||
|
let calls = 0;
|
||||||
|
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
[TEST]({ state, rootState }) {
|
||||||
|
calls++;
|
||||||
|
expect(state.a).toBe(1);
|
||||||
|
expect(rootState).toBe(store.state);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
nested: {
|
||||||
|
state: { a: 2 },
|
||||||
|
actions: {
|
||||||
|
[TEST]({ state, rootState }) {
|
||||||
|
calls++;
|
||||||
|
expect(state.a).toBe(2);
|
||||||
|
expect(rootState).toBe(store.state);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
four: {
|
||||||
|
state: { a: 6 },
|
||||||
|
actions: {
|
||||||
|
[TEST]({ state, rootState }) {
|
||||||
|
calls++;
|
||||||
|
expect(state.a).toBe(6);
|
||||||
|
expect(rootState).toBe(store.state);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
store.dispatch(TEST);
|
||||||
|
expect(calls).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('module: getters', function () {
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
constant: () => 0,
|
||||||
|
[`getter1`]: (state, getters, rootState) => {
|
||||||
|
expect(getters.constant).toBe(0);
|
||||||
|
expect(rootState.a).toBe(store.state.a);
|
||||||
|
return state.a;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
nested: {
|
||||||
|
state: { a: 2 },
|
||||||
|
getters: {
|
||||||
|
[`getter2`]: (state, getters, rootState) => {
|
||||||
|
expect(getters.constant).toBe(0);
|
||||||
|
expect(rootState.a).toBe(store.state.a);
|
||||||
|
return state.a;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
four: {
|
||||||
|
state: { a: 6 },
|
||||||
|
getters: {
|
||||||
|
[`getter6`]: (state, getters, rootState) => {
|
||||||
|
expect(getters.constant).toBe(0);
|
||||||
|
expect(rootState.a).toBe(store.state.a);
|
||||||
|
return state.a;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
[1, 2, 6].forEach(n => {
|
||||||
|
expect(store.getters[`getter${n}`]).toBe(n);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('module: namespace', () => {
|
||||||
|
const actionSpy = vi.fn();
|
||||||
|
const mutationSpy = vi.fn();
|
||||||
|
|
||||||
|
const store = createStore({
|
||||||
|
modules: {
|
||||||
|
a: {
|
||||||
|
namespaced: true,
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
b: () => 2,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
[TEST]: actionSpy,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST]: mutationSpy,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(store.state.a.a).toBe(1);
|
||||||
|
expect(store.getters['a/b']).toBe(2);
|
||||||
|
store.dispatch('a/' + TEST);
|
||||||
|
expect(actionSpy).toHaveBeenCalled();
|
||||||
|
store.commit('a/' + TEST);
|
||||||
|
expect(mutationSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('module: getters are namespaced in namespaced module', () => {
|
||||||
|
const store = createStore({
|
||||||
|
state: { value: 'root' },
|
||||||
|
getters: {
|
||||||
|
foo: state => state.value,
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
a: {
|
||||||
|
namespaced: true,
|
||||||
|
state: { value: 'module' },
|
||||||
|
getters: {
|
||||||
|
foo: state => {
|
||||||
|
return state.value;
|
||||||
|
},
|
||||||
|
bar: (state, getters) => {
|
||||||
|
return getters.foo;
|
||||||
|
},
|
||||||
|
baz: (state, getters, rootState, rootGetters) => rootGetters.foo,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(store.getters['a/foo']).toBe('module');
|
||||||
|
expect(store.getters['a/bar']).toBe('module');
|
||||||
|
expect(store.getters['a/baz']).toBe('root');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('module: action context is namespaced in namespaced module', () => {
|
||||||
|
const rootActionSpy = vi.fn();
|
||||||
|
const rootMutationSpy = vi.fn();
|
||||||
|
const moduleActionSpy = vi.fn();
|
||||||
|
const moduleMutationSpy = vi.fn();
|
||||||
|
|
||||||
|
const store = createStore({
|
||||||
|
state: { value: 'root' },
|
||||||
|
getters: { foo: state => state.value },
|
||||||
|
actions: { foo: rootActionSpy },
|
||||||
|
mutations: { foo: rootMutationSpy },
|
||||||
|
modules: {
|
||||||
|
a: {
|
||||||
|
namespaced: true,
|
||||||
|
state: { value: 'module' },
|
||||||
|
getters: { foo: state => state.value },
|
||||||
|
actions: {
|
||||||
|
foo: moduleActionSpy,
|
||||||
|
test({ dispatch, commit, getters, rootGetters }) {
|
||||||
|
expect(getters.foo).toBe('module');
|
||||||
|
expect(rootGetters.foo).toBe('root');
|
||||||
|
|
||||||
|
dispatch('foo');
|
||||||
|
expect(moduleActionSpy).toHaveBeenCalledTimes(1);
|
||||||
|
dispatch('foo', null, { root: true });
|
||||||
|
expect(rootActionSpy).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
commit('foo');
|
||||||
|
expect(moduleMutationSpy).toHaveBeenCalledTimes(1);
|
||||||
|
commit('foo', null, { root: true });
|
||||||
|
expect(rootMutationSpy).toHaveBeenCalledTimes(1);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutations: { foo: moduleMutationSpy },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
store.dispatch('a/test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatching multiple actions in different modules', () => {
|
||||||
|
const store = createStore({
|
||||||
|
modules: {
|
||||||
|
a: {
|
||||||
|
actions: {
|
||||||
|
[TEST]() {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
b: {
|
||||||
|
actions: {
|
||||||
|
[TEST]() {
|
||||||
|
return new Promise(r => r(2));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
store.dispatch(TEST).then(res => {
|
||||||
|
expect(res[0]).toBe(1);
|
||||||
|
expect(res[1]).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,335 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { createStore } from '../../src/vuex/vuex';
|
||||||
|
|
||||||
|
const TEST_M = 'TEST_M';
|
||||||
|
const TEST_A = 'TEST_A';
|
||||||
|
|
||||||
|
describe('vuex Store', () => {
|
||||||
|
it('committing mutations', () => {
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST_M](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
store.commit(TEST_M, 2);
|
||||||
|
expect(store.state.a).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('committing with object style', () => {
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST_M](state, payload) {
|
||||||
|
state.a += payload.amount;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
store.commit({
|
||||||
|
type: TEST_M,
|
||||||
|
amount: 2,
|
||||||
|
});
|
||||||
|
expect(store.state.a).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatching actions, sync', () => {
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST_M](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
[TEST_A]({ commit }, n) {
|
||||||
|
commit(TEST_M, n);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
store.dispatch(TEST_A, 2);
|
||||||
|
expect(store.state.a).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatching with object style', () => {
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST_M](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
[TEST_A]({ commit }, payload) {
|
||||||
|
commit(TEST_M, payload.amount);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
store.dispatch({
|
||||||
|
type: TEST_A,
|
||||||
|
amount: 2,
|
||||||
|
});
|
||||||
|
expect(store.state.a).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatching actions, with returned Promise', () => {
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST_M](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
[TEST_A]({ commit }, n) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
commit(TEST_M, n);
|
||||||
|
resolve('');
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(store.state.a).toBe(1);
|
||||||
|
store.dispatch(TEST_A, 2).then(() => {
|
||||||
|
expect(store.state.a).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('composing actions with async/await', () => {
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST_M](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
[TEST_A]({ commit }, n) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
commit(TEST_M, n);
|
||||||
|
resolve('');
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
two: async ({ commit, dispatch }, n) => {
|
||||||
|
await dispatch(TEST_A, 1);
|
||||||
|
expect(store.state.a).toBe(2);
|
||||||
|
commit(TEST_M, n);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(store.state.a).toBe(1);
|
||||||
|
store.dispatch('two', 3).then(() => {
|
||||||
|
expect(store.state.a).toBe(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detecting action Promise errors', () => {
|
||||||
|
const store = createStore({
|
||||||
|
actions: {
|
||||||
|
[TEST_A]() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
reject('no');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const thenSpy = vi.fn();
|
||||||
|
store
|
||||||
|
.dispatch(TEST_A)
|
||||||
|
.then(thenSpy)
|
||||||
|
.catch((err: string) => {
|
||||||
|
expect(thenSpy).not.toHaveBeenCalled();
|
||||||
|
expect(err).toBe('no');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getters', () => {
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 0,
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
state: state => (state.a > 0 ? 'hasAny' : 'none'),
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST_M](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
check({ getters }, value) {
|
||||||
|
// check for exposing getters into actions
|
||||||
|
expect(getters.state).toBe(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(store.getters.state).toBe('none');
|
||||||
|
store.dispatch('check', 'none');
|
||||||
|
|
||||||
|
store.commit(TEST_M, 1);
|
||||||
|
|
||||||
|
expect(store.getters.state).toBe('hasAny');
|
||||||
|
store.dispatch('check', 'hasAny');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept state as function', () => {
|
||||||
|
const store = createStore({
|
||||||
|
state: () => ({
|
||||||
|
a: 1,
|
||||||
|
}),
|
||||||
|
mutations: {
|
||||||
|
[TEST_M](state, n) {
|
||||||
|
state.a += n;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(store.state.a).toBe(1);
|
||||||
|
store.commit(TEST_M, 2);
|
||||||
|
expect(store.state.a).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('subscribe: should handle subscriptions / unsubscriptions', () => {
|
||||||
|
const subscribeSpy = vi.fn();
|
||||||
|
const secondSubscribeSpy = vi.fn();
|
||||||
|
const testPayload = 2;
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST_M](state) {
|
||||||
|
state.a++;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const unsubscribe = store.subscribe(subscribeSpy);
|
||||||
|
store.subscribe(secondSubscribeSpy);
|
||||||
|
store.commit(TEST_M, testPayload);
|
||||||
|
unsubscribe();
|
||||||
|
store.commit(TEST_M, testPayload);
|
||||||
|
|
||||||
|
expect(subscribeSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(secondSubscribeSpy).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('subscribe: should handle subscriptions with synchronous unsubscriptions', () => {
|
||||||
|
const subscribeSpy = vi.fn();
|
||||||
|
const testPayload = 2;
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST_M](state) {
|
||||||
|
state.a++;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const unsubscribe = store.subscribe(() => unsubscribe());
|
||||||
|
store.subscribe(subscribeSpy);
|
||||||
|
store.commit(TEST_M, testPayload);
|
||||||
|
|
||||||
|
expect(subscribeSpy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('subscribeAction: should handle subscriptions with synchronous unsubscriptions', () => {
|
||||||
|
const subscribeSpy = vi.fn();
|
||||||
|
const testPayload = 2;
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
[TEST_A]({ state }) {
|
||||||
|
state.a++;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const unsubscribe = store.subscribeAction(() => unsubscribe());
|
||||||
|
store.subscribeAction(subscribeSpy);
|
||||||
|
store.dispatch(TEST_A, testPayload);
|
||||||
|
|
||||||
|
expect(subscribeSpy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('watch: with resetting vm', () => {
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
count: 0,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST_M]: state => state.count++,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const spy = vi.fn();
|
||||||
|
store.watch(state => state.count, spy);
|
||||||
|
|
||||||
|
store.commit(TEST_M);
|
||||||
|
expect(store.state.count).toBe(1);
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("watch: getter function has access to store's getters object", () => {
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
count: 0,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
[TEST_M]: state => state.count++,
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
getCount: state => state.count,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const getter = function getter(state: any) {
|
||||||
|
return state.count;
|
||||||
|
};
|
||||||
|
const spy = vi.spyOn({ getter }, 'getter');
|
||||||
|
const spyCb = vi.fn();
|
||||||
|
|
||||||
|
store.watch(spy as any, spyCb);
|
||||||
|
|
||||||
|
store.commit(TEST_M);
|
||||||
|
expect(store.state.count).toBe(1);
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalledWith(store.state, store.getters);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"src/pinia/index.ts"
|
||||||
|
],
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"declarationDir": "./build/pinia/@types"
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"files": [
|
"files": [
|
||||||
"src/index.ts"
|
"src/vue/index.ts"
|
||||||
],
|
],
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
"declarationDir": "./build/@types"
|
"declarationDir": "./build/vue/@types"
|
||||||
},
|
},
|
||||||
}
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"src/vuex/index.ts"
|
||||||
|
],
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"declarationDir": "./build/vuex/@types"
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
*
|
*
|
||||||
* openInula is licensed under Mulan PSL v2.
|
* openInula is licensed under Mulan PSL v2.
|
||||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
let alias = {
|
const alias = {
|
||||||
react: 'openinula', // 新增
|
react: 'openinula', // 新增
|
||||||
'react-dom': 'openinula', // 新增
|
'react-dom': 'openinula', // 新增
|
||||||
'react/jsx-dev-runtime': 'openinula/jsx-dev-runtime',
|
'react/jsx-dev-runtime': 'openinula/jsx-dev-runtime',
|
|
@ -1,2 +0,0 @@
|
||||||
## 适配Vue
|
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||||
|
*
|
||||||
|
* openInula is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
*
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { vueReactive } from '../../../src';
|
||||||
|
|
||||||
|
const { ref, isRef, reactive, unref, toRef, toRefs, isReadonly, watchEffect } = vueReactive;
|
||||||
|
|
||||||
|
describe('test toRef, toRefs', () => {
|
||||||
|
it('toRef', () => {
|
||||||
|
const a = reactive({
|
||||||
|
x: 1,
|
||||||
|
});
|
||||||
|
const x = toRef(a, 'x');
|
||||||
|
expect(isRef(x)).toBe(true);
|
||||||
|
expect(x.value).toBe(1);
|
||||||
|
|
||||||
|
// source -> proxy
|
||||||
|
a.x = 2;
|
||||||
|
expect(x.value).toBe(2);
|
||||||
|
|
||||||
|
// proxy -> source
|
||||||
|
x.value = 3;
|
||||||
|
expect(a.x).toBe(3);
|
||||||
|
|
||||||
|
// reactivity
|
||||||
|
let dummyX;
|
||||||
|
watchEffect(() => {
|
||||||
|
dummyX = x.value;
|
||||||
|
});
|
||||||
|
expect(dummyX).toBe(x.value);
|
||||||
|
|
||||||
|
// mutating source should trigger effect using the proxy refs
|
||||||
|
a.x = 4;
|
||||||
|
expect(dummyX).toBe(4);
|
||||||
|
|
||||||
|
// should keep ref
|
||||||
|
const r = { x: ref(1) };
|
||||||
|
expect(toRef(r, 'x')).toBe(r.x);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toRef on array', () => {
|
||||||
|
const a = reactive(['a', 'b']);
|
||||||
|
const r = toRef(a, 1);
|
||||||
|
expect(r.value).toBe('b');
|
||||||
|
r.value = 'c';
|
||||||
|
expect(r.value).toBe('c');
|
||||||
|
expect(a[1]).toBe('c');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toRef default value', () => {
|
||||||
|
const a: { x: number | undefined } = { x: undefined };
|
||||||
|
const x = toRef(a, 'x', 1);
|
||||||
|
expect(x.value).toBe(1);
|
||||||
|
|
||||||
|
a.x = 2;
|
||||||
|
expect(x.value).toBe(2);
|
||||||
|
|
||||||
|
a.x = undefined;
|
||||||
|
expect(x.value).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toRef getter', () => {
|
||||||
|
const x = toRef(() => 1);
|
||||||
|
expect(x.value).toBe(1);
|
||||||
|
expect(isRef(x)).toBe(true);
|
||||||
|
expect(unref(x)).toBe(1);
|
||||||
|
//@ts-expect-error
|
||||||
|
expect(() => (x.value = 123)).toThrow();
|
||||||
|
|
||||||
|
expect(isReadonly(x)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toRefs', () => {
|
||||||
|
const a = reactive({
|
||||||
|
x: 1,
|
||||||
|
y: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { x, y } = toRefs(a);
|
||||||
|
|
||||||
|
expect(isRef(x)).toBe(true);
|
||||||
|
expect(isRef(y)).toBe(true);
|
||||||
|
expect(x.value).toBe(1);
|
||||||
|
expect(y.value).toBe(2);
|
||||||
|
|
||||||
|
// source -> proxy
|
||||||
|
a.x = 2;
|
||||||
|
a.y = 3;
|
||||||
|
expect(x.value).toBe(2);
|
||||||
|
expect(y.value).toBe(3);
|
||||||
|
|
||||||
|
// proxy -> source
|
||||||
|
x.value = 3;
|
||||||
|
y.value = 4;
|
||||||
|
expect(a.x).toBe(3);
|
||||||
|
expect(a.y).toBe(4);
|
||||||
|
|
||||||
|
// reactivity
|
||||||
|
let dummyX, dummyY;
|
||||||
|
watchEffect(() => {
|
||||||
|
dummyX = x.value;
|
||||||
|
dummyY = y.value;
|
||||||
|
});
|
||||||
|
expect(dummyX).toBe(x.value);
|
||||||
|
expect(dummyY).toBe(y.value);
|
||||||
|
|
||||||
|
// mutating source should trigger effect using the proxy refs
|
||||||
|
a.x = 4;
|
||||||
|
a.y = 5;
|
||||||
|
expect(dummyX).toBe(4);
|
||||||
|
expect(dummyY).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toRefs reactive array', () => {
|
||||||
|
const arr = reactive(['a', 'b', 'c']);
|
||||||
|
const refs = toRefs(arr);
|
||||||
|
|
||||||
|
expect(Array.isArray(refs)).toBe(true);
|
||||||
|
|
||||||
|
refs[0].value = '1';
|
||||||
|
expect(arr[0]).toBe('1');
|
||||||
|
|
||||||
|
arr[1] = '2';
|
||||||
|
expect(refs[1].value).toBe('2');
|
||||||
|
});
|
||||||
|
});
|
|
@ -42,7 +42,7 @@ describe('Dollar store access', () => {
|
||||||
function App() {
|
function App() {
|
||||||
const logStore = useLogStore();
|
const logStore = useLogStore();
|
||||||
|
|
||||||
return <div id={RESULT_ID}>{logStore.$c.length()}</div>;
|
return <div id={RESULT_ID}>{logStore.$c.length}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
Inula.render(<App />, container);
|
Inula.render(<App />, container);
|
||||||
|
@ -64,7 +64,7 @@ describe('Dollar store access', () => {
|
||||||
>
|
>
|
||||||
add
|
add
|
||||||
</button>
|
</button>
|
||||||
<p id={RESULT_ID}>{logStore.$c.length()}</p>
|
<p id={RESULT_ID}>{logStore.$c.length}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"homepage": "",
|
"homepage": "",
|
||||||
"bugs": "",
|
"bugs": "",
|
||||||
"license": "MulanPSL2",
|
"license": "MulanPSL2",
|
||||||
"main": "./build/index.js",
|
"main": "./src/index.ts",
|
||||||
"repository": {},
|
"repository": {},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
|
|
@ -57,7 +57,7 @@ import {
|
||||||
} from './external/InulaIs';
|
} from './external/InulaIs';
|
||||||
import { createStore, useStore, clearStore } from './inulax/store/StoreHandler';
|
import { createStore, useStore, clearStore } from './inulax/store/StoreHandler';
|
||||||
import { reactive, useReactive, toRaw } from './inulax/reactive/Reactive';
|
import { reactive, useReactive, toRaw } from './inulax/reactive/Reactive';
|
||||||
import { ref, useReference, isRef, unref, shallowRef } from './inulax/reactive/Ref';
|
import { ref, useReference, isRef, unref, shallowRef, toRef, toRefs } from './inulax/reactive/Ref';
|
||||||
import * as reduxAdapter from './inulax/adapters/redux';
|
import * as reduxAdapter from './inulax/adapters/redux';
|
||||||
import { watch, watchEffect, useWatch } from './inulax/reactive/Watch';
|
import { watch, watchEffect, useWatch } from './inulax/reactive/Watch';
|
||||||
import { computed, useComputed } from './inulax/reactive/Computed';
|
import { computed, useComputed } from './inulax/reactive/Computed';
|
||||||
|
@ -73,7 +73,7 @@ import {
|
||||||
} from './dom/DOMExternal';
|
} from './dom/DOMExternal';
|
||||||
|
|
||||||
import { syncUpdates as flushSync } from './renderer/TreeBuilder';
|
import { syncUpdates as flushSync } from './renderer/TreeBuilder';
|
||||||
import { isReactive, isShallow } from './inulax/CommonUtils';
|
import { isReactive, isShallow, isReadonly } from './inulax/CommonUtils';
|
||||||
|
|
||||||
const vueReactive = {
|
const vueReactive = {
|
||||||
ref,
|
ref,
|
||||||
|
@ -81,10 +81,13 @@ const vueReactive = {
|
||||||
isRef,
|
isRef,
|
||||||
unref,
|
unref,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
|
toRef,
|
||||||
|
toRefs,
|
||||||
reactive,
|
reactive,
|
||||||
useReactive,
|
useReactive,
|
||||||
isReactive,
|
isReactive,
|
||||||
isShallow,
|
isShallow,
|
||||||
|
isReadonly,
|
||||||
computed,
|
computed,
|
||||||
useComputed,
|
useComputed,
|
||||||
watchEffect,
|
watchEffect,
|
||||||
|
@ -208,5 +211,8 @@ export {
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './inulax/types/ReactiveTypes';
|
export * from './inulax/types/ReactiveTypes';
|
||||||
export * from './inulax/types/ProxyTypes';
|
export * from './inulax/types/ProxyTypes';
|
||||||
|
export * from './inulax/types/StoreTypes';
|
||||||
|
export * from './inulax/types/StoreTypes';
|
||||||
|
export { ComputedImpl } from './inulax/reactive/Computed';
|
||||||
|
|
||||||
export default Inula;
|
export default Inula;
|
||||||
|
|
|
@ -218,3 +218,7 @@ export function isShallow(value: unknown): boolean {
|
||||||
export function isReactive(value: unknown) {
|
export function isReactive(value: unknown) {
|
||||||
return !!(value && !!value[KeyTypes.RAW_VALUE]);
|
return !!(value && !!value[KeyTypes.RAW_VALUE]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isReadonly(value: unknown): boolean {
|
||||||
|
return !!(value && value[ReactiveFlags.IS_READONLY]);
|
||||||
|
}
|
||||||
|
|
|
@ -32,4 +32,6 @@ export enum KeyTypes {
|
||||||
|
|
||||||
export enum ReactiveFlags {
|
export enum ReactiveFlags {
|
||||||
IS_SHALLOW = '_isShallow',
|
IS_SHALLOW = '_isShallow',
|
||||||
|
IS_READONLY = '_isReadonly',
|
||||||
|
IS_REF = '_isRef',
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,13 +161,6 @@ const c = computed(() => 1, {
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
5. computed中不支持第二个参数debugOptions。
|
|
||||||
```js
|
|
||||||
const c = computed(() => 1, {
|
|
||||||
onTrack, // 不支持
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
6. computed.effect.stop 改为 computed.stop。
|
6. computed.effect.stop 改为 computed.stop。
|
||||||
```js
|
```js
|
||||||
it('should no longer update when stopped', () => {
|
it('should no longer update when stopped', () => {
|
||||||
|
|
|
@ -34,7 +34,7 @@ export function getObserver(rawObj: any): IObserver {
|
||||||
return rawObserverMap.get(rawObj) as IObserver;
|
return rawObserverMap.get(rawObj) as IObserver;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setObserverKey(rawObj: any, observer: IObserver): void {
|
function setObserver(rawObj: any, observer: IObserver): void {
|
||||||
rawObserverMap.set(rawObj, observer);
|
rawObserverMap.set(rawObj, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ export function createProxy(rawObj: any, listener?: CurrentListener, isDeepProxy
|
||||||
let observer = getObserver(rawObj);
|
let observer = getObserver(rawObj);
|
||||||
if (!observer) {
|
if (!observer) {
|
||||||
observer = (isDeepProxy ? new Observer() : new HooklessObserver()) as IObserver;
|
observer = (isDeepProxy ? new Observer() : new HooklessObserver()) as IObserver;
|
||||||
setObserverKey(rawObj, observer);
|
setObserver(rawObj, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
deepProxyMap.set(rawObj, isDeepProxy);
|
deepProxyMap.set(rawObj, isDeepProxy);
|
||||||
|
|
|
@ -83,10 +83,6 @@ export function baseGetFun<T extends Record<string | symbol, any> | any[]>(
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === KeyTypes.VALUE) {
|
|
||||||
return receiver;
|
|
||||||
}
|
|
||||||
|
|
||||||
const observer = getObserver(rawObj);
|
const observer = getObserver(rawObj);
|
||||||
|
|
||||||
if (key === KeyTypes.WATCH) {
|
if (key === KeyTypes.WATCH) {
|
||||||
|
|
|
@ -43,6 +43,7 @@ export class ComputedImpl<T = any> {
|
||||||
|
|
||||||
private readonly observer: Observer = new Observer();
|
private readonly observer: Observer = new Observer();
|
||||||
readonly _isRef = true;
|
readonly _isRef = true;
|
||||||
|
readonly _isReadonly = true;
|
||||||
|
|
||||||
constructor(fn: ComputedFN<T>) {
|
constructor(fn: ComputedFN<T>) {
|
||||||
this.fn = fn;
|
this.fn = fn;
|
||||||
|
|
|
@ -13,14 +13,12 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isObject, isSame, isShallow } from '../CommonUtils';
|
import { isArray, isObject, isSame, isShallow } from '../CommonUtils';
|
||||||
import { reactive, toRaw } from './Reactive';
|
import { toRaw } from './Reactive';
|
||||||
import { Observer } from '../proxy/Observer';
|
import { Observer } from '../proxy/Observer';
|
||||||
import { KeyTypes, OBSERVER_KEY } from '../Constants';
|
import { KeyTypes, OBSERVER_KEY, ReactiveFlags } from '../Constants';
|
||||||
import { MaybeRef, RefType, UnwrapRef } from '../types/ReactiveTypes';
|
import { IfAny, MaybeRef, RefType, ToRef, ToRefs, UnwrapRef } from '../types/ReactiveTypes';
|
||||||
import { registerDestroyFunction } from '../store/StoreHandler';
|
import { registerDestroyFunction } from '../store/StoreHandler';
|
||||||
import { getProcessingVNode } from '../../renderer/GlobalVar';
|
|
||||||
import { FunctionComponent } from '../../renderer/vnode/VNodeTags';
|
|
||||||
import { useRef } from '../../renderer/hooks/HookExternal';
|
import { useRef } from '../../renderer/hooks/HookExternal';
|
||||||
import { createProxy } from '../proxy/ProxyHandler';
|
import { createProxy } from '../proxy/ProxyHandler';
|
||||||
import { Listener } from '../types/ProxyTypes';
|
import { Listener } from '../types/ProxyTypes';
|
||||||
|
@ -97,7 +95,7 @@ class RefImpl<T> {
|
||||||
|
|
||||||
export function isRef<T>(ref: MaybeRef<T>): ref is RefType<T>;
|
export function isRef<T>(ref: MaybeRef<T>): ref is RefType<T>;
|
||||||
export function isRef(ref: any): ref is RefType {
|
export function isRef(ref: any): ref is RefType {
|
||||||
return Boolean(ref && ref._isRef);
|
return Boolean(ref && ref[ReactiveFlags.IS_REF]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toReactive<T extends unknown>(value: T): T {
|
export function toReactive<T extends unknown>(value: T): T {
|
||||||
|
@ -110,7 +108,6 @@ export function unref<T>(ref: MaybeRef<T>): T {
|
||||||
|
|
||||||
declare const ShallowRefMarker: unique symbol;
|
declare const ShallowRefMarker: unique symbol;
|
||||||
export type ShallowRef<T = any> = RefType<T> & { [ShallowRefMarker]?: true };
|
export type ShallowRef<T = any> = RefType<T> & { [ShallowRefMarker]?: true };
|
||||||
export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
|
|
||||||
|
|
||||||
export function shallowRef<T>(
|
export function shallowRef<T>(
|
||||||
value: T
|
value: T
|
||||||
|
@ -119,3 +116,73 @@ export function shallowRef<T = any>(): ShallowRef<T | undefined>;
|
||||||
export function shallowRef(value?: unknown) {
|
export function shallowRef(value?: unknown) {
|
||||||
return createRef(value, true);
|
return createRef(value, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toRef<T>(
|
||||||
|
value: T
|
||||||
|
): T extends () => infer R ? Readonly<RefType<R>> : T extends RefType ? T : RefType<UnwrapRef<T>>;
|
||||||
|
export function toRef<T extends Record<string, any>, K extends keyof T>(object: T, key: K): ToRef<T[K]>;
|
||||||
|
export function toRef<T extends Record<string, any>, K extends keyof T>(
|
||||||
|
object: T,
|
||||||
|
key: K,
|
||||||
|
defaultValue: T[K]
|
||||||
|
): ToRef<Exclude<T[K], undefined>>;
|
||||||
|
export function toRef(source: Record<string, any> | MaybeRef, key?: string, defaultValue?: unknown): RefType {
|
||||||
|
if (isRef(source)) {
|
||||||
|
return source;
|
||||||
|
} else if (typeof source === 'function') {
|
||||||
|
return new GetterRefImpl(source) as any;
|
||||||
|
} else if (isObject(source) && arguments.length > 1) {
|
||||||
|
return propertyToRef(source, key!, defaultValue);
|
||||||
|
} else {
|
||||||
|
return ref(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetterRefImpl<T> {
|
||||||
|
public readonly _isRef = true;
|
||||||
|
public readonly _isReadonly = true;
|
||||||
|
private readonly _getter: () => T;
|
||||||
|
|
||||||
|
constructor(getter: () => T) {
|
||||||
|
this._getter = getter;
|
||||||
|
}
|
||||||
|
|
||||||
|
get value() {
|
||||||
|
return this._getter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function propertyToRef(source: Record<string, any>, key: string, defaultValue?: unknown) {
|
||||||
|
const val = source[key];
|
||||||
|
return isRef(val) ? val : (new ObjectRefImpl(source, key, defaultValue) as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObjectRefImpl<T extends Record<string, any>, K extends keyof T> {
|
||||||
|
public readonly _isRef = true;
|
||||||
|
private readonly _object: T;
|
||||||
|
private readonly _key: K;
|
||||||
|
private readonly _defaultValue?: T[K];
|
||||||
|
|
||||||
|
constructor(object: T, key: K, defaultValue?: T[K]) {
|
||||||
|
this._object = object;
|
||||||
|
this._key = key;
|
||||||
|
this._defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
get value() {
|
||||||
|
const val = this._object[this._key];
|
||||||
|
return val === undefined ? this._defaultValue! : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
set value(newVal) {
|
||||||
|
this._object[this._key] = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toRefs<T extends Record<string, any>>(object: T): ToRefs<T> {
|
||||||
|
const ret: any = isArray(object) ? new Array(object.length) : {};
|
||||||
|
for (const key in object) {
|
||||||
|
ret[key] = propertyToRef(object, key);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
|
@ -53,14 +53,15 @@ const idGenerator = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const storeMap = new Map<string, StoreObj<any, any, any>>();
|
const storeMap = new Map<string, StoreObj<any, any, any>>();
|
||||||
|
const pendingMap = new WeakMap<any, boolean | number>();
|
||||||
|
|
||||||
// 通过该方法执行store.$queue中的action
|
// 通过该方法执行store.$queue中的action
|
||||||
function tryNextAction(storeObj, proxyObj, config, plannedActions) {
|
function tryNextAction(storeObj, proxyObj, config, plannedActions) {
|
||||||
if (!plannedActions.length) {
|
if (!plannedActions.length) {
|
||||||
if (proxyObj.$pending) {
|
if (pendingMap.get(proxyObj)) {
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
const duration = timestamp - proxyObj.$pending;
|
const duration = timestamp - (pendingMap.get(proxyObj) as number);
|
||||||
proxyObj.$pending = false;
|
pendingMap.set(proxyObj, false);
|
||||||
devtools.emit(QUEUE_FINISHED, {
|
devtools.emit(QUEUE_FINISHED, {
|
||||||
store: storeObj,
|
store: storeObj,
|
||||||
endedAt: timestamp,
|
endedAt: timestamp,
|
||||||
|
@ -165,7 +166,7 @@ export function createStore<S extends Record<string, any>, A extends UserActions
|
||||||
const proxyObj = createProxy(config.state, listener, !config.options?.isReduxAdapter);
|
const proxyObj = createProxy(config.state, listener, !config.options?.isReduxAdapter);
|
||||||
|
|
||||||
if (proxyObj !== undefined) {
|
if (proxyObj !== undefined) {
|
||||||
proxyObj.$pending = false;
|
pendingMap.set(proxyObj, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const $a: Partial<StoreActions<S, A>> = {};
|
const $a: Partial<StoreActions<S, A>> = {};
|
||||||
|
@ -173,12 +174,13 @@ export function createStore<S extends Record<string, any>, A extends UserActions
|
||||||
const $c: Partial<ComputedValues<S, C>> = {};
|
const $c: Partial<ComputedValues<S, C>> = {};
|
||||||
const storeObj = {
|
const storeObj = {
|
||||||
id,
|
id,
|
||||||
|
$state: proxyObj,
|
||||||
$s: proxyObj,
|
$s: proxyObj,
|
||||||
$a: $a as StoreActions<S, A>,
|
$a: $a as StoreActions<S, A>,
|
||||||
$c: $c as ComputedValues<S, C>,
|
$c: $c as ComputedValues<S, C>,
|
||||||
$queue: $queue as QueuedStoreActions<S, A>,
|
$queue: $queue as QueuedStoreActions<S, A>,
|
||||||
$config: config,
|
$config: config,
|
||||||
$listeners: [
|
$subscriptions: [
|
||||||
change => {
|
change => {
|
||||||
devtools.emit(STATE_CHANGE, {
|
devtools.emit(STATE_CHANGE, {
|
||||||
store: storeObj,
|
store: storeObj,
|
||||||
|
@ -188,16 +190,19 @@ export function createStore<S extends Record<string, any>, A extends UserActions
|
||||||
],
|
],
|
||||||
$subscribe: listener => {
|
$subscribe: listener => {
|
||||||
devtools.emit(SUBSCRIBED, { store: storeObj, listener });
|
devtools.emit(SUBSCRIBED, { store: storeObj, listener });
|
||||||
storeObj.$listeners.push(listener);
|
storeObj.$subscriptions.push(listener);
|
||||||
|
return () => {
|
||||||
|
storeObj.$unsubscribe(listener);
|
||||||
|
};
|
||||||
},
|
},
|
||||||
$unsubscribe: listener => {
|
$unsubscribe: listener => {
|
||||||
devtools.emit(UNSUBSCRIBED, { store: storeObj });
|
devtools.emit(UNSUBSCRIBED, { store: storeObj });
|
||||||
storeObj.$listeners = storeObj.$listeners.filter(item => item != listener);
|
storeObj.$subscriptions = storeObj.$subscriptions.filter(item => item != listener);
|
||||||
},
|
},
|
||||||
} as unknown as StoreObj<S, A, C>;
|
} as unknown as StoreObj<S, A, C>;
|
||||||
|
|
||||||
listener.current = (...args) => {
|
listener.current = (...args) => {
|
||||||
storeObj.$listeners.forEach(listener => listener(...args));
|
storeObj.$subscriptions.forEach(listener => listener(...args));
|
||||||
};
|
};
|
||||||
|
|
||||||
const plannedActions: PlannedAction<S, ActionFunction<S>>[] = [];
|
const plannedActions: PlannedAction<S, ActionFunction<S>>[] = [];
|
||||||
|
@ -217,11 +222,11 @@ export function createStore<S extends Record<string, any>, A extends UserActions
|
||||||
fromQueue: true,
|
fromQueue: true,
|
||||||
});
|
});
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
if (!proxyObj.$pending) {
|
if (!pendingMap.get(proxyObj)) {
|
||||||
proxyObj.$pending = Date.now();
|
pendingMap.set(proxyObj, Date.now());
|
||||||
devtools.emit(QUEUE_PENDING, {
|
devtools.emit(QUEUE_PENDING, {
|
||||||
store: storeObj,
|
store: storeObj,
|
||||||
startedAt: proxyObj.$pending,
|
startedAt: pendingMap.get(proxyObj),
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = config.actions![action].bind(storeObj, proxyObj)(...payload);
|
const result = config.actions![action].bind(storeObj, proxyObj)(...payload);
|
||||||
|
@ -279,12 +284,15 @@ export function createStore<S extends Record<string, any>, A extends UserActions
|
||||||
|
|
||||||
if (config.computed) {
|
if (config.computed) {
|
||||||
Object.keys(config.computed).forEach(computeKey => {
|
Object.keys(config.computed).forEach(computeKey => {
|
||||||
// 让store.$c[computeKey]可以访问到computed方法
|
const computeFn = config.computed![computeKey].bind(storeObj, readonlyProxy(proxyObj));
|
||||||
($c as any)[computeKey] = config.computed![computeKey].bind(storeObj, readonlyProxy(proxyObj));
|
// 让store.$c[computeKey]可以访问到computed的值
|
||||||
|
Object.defineProperty($c, computeKey, {
|
||||||
|
get: computeFn as () => any,
|
||||||
|
});
|
||||||
|
|
||||||
// 让store[computeKey]可以访问到computed的值
|
// 让store[computeKey]可以访问到computed的值
|
||||||
Object.defineProperty(storeObj, computeKey, {
|
Object.defineProperty(storeObj, computeKey, {
|
||||||
get: $c[computeKey] as () => any,
|
get: computeFn as () => any,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,3 +44,10 @@ export type ReactiveRet<T> = T extends FnType | BaseTypes | RefType
|
||||||
[P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;
|
[P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;
|
||||||
}
|
}
|
||||||
: T;
|
: T;
|
||||||
|
|
||||||
|
export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
|
||||||
|
|
||||||
|
export type ToRef<T> = IfAny<T, RefType<T>, [T] extends [RefType] ? T : RefType<T>>;
|
||||||
|
export type ToRefs<T = any> = {
|
||||||
|
[K in keyof T]: ToRef<T[K]>;
|
||||||
|
};
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AddWatchProp } from './ProxyTypes';
|
import { AddWatchProp, Listener } from './ProxyTypes';
|
||||||
|
|
||||||
export type StoreConfig<
|
export type StoreConfig<
|
||||||
S extends Record<string, unknown>,
|
S extends Record<string, unknown>,
|
||||||
|
@ -48,17 +48,23 @@ type Action<T extends ActionFunction<any>, S extends Record<string, unknown>> =
|
||||||
...args: RemoveFirstFromTuple<Parameters<T>>
|
...args: RemoveFirstFromTuple<Parameters<T>>
|
||||||
) => ReturnType<T>;
|
) => ReturnType<T>;
|
||||||
|
|
||||||
export type StoreObj<S extends Record<string, unknown>, A extends UserActions<S>, C extends UserComputedValues<S>> = {
|
export type StoreComputed<S extends Record<string, unknown>, C extends UserComputedValues<S>> = {
|
||||||
|
[K in keyof C]: ReturnType<C[K]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StoreObj<
|
||||||
|
S extends Record<string, unknown> = Record<string, unknown>,
|
||||||
|
A extends UserActions<S> = UserActions<S>,
|
||||||
|
C extends UserComputedValues<S> = UserComputedValues<S>,
|
||||||
|
> = {
|
||||||
$s: AddWatchProp<S>;
|
$s: AddWatchProp<S>;
|
||||||
$state: AddWatchProp<S>;
|
$state: AddWatchProp<S>;
|
||||||
// $s: S;
|
|
||||||
// $state: S;
|
|
||||||
$a: StoreActions<S, A>;
|
$a: StoreActions<S, A>;
|
||||||
$c: UserComputedValues<S>;
|
$c: StoreComputed<S, C>;
|
||||||
$queue: QueuedStoreActions<S, A>;
|
$queue: QueuedStoreActions<S, A>;
|
||||||
$listeners;
|
$subscriptions: Array<Listener>;
|
||||||
$subscribe: (listener: (mutation) => void) => void;
|
$subscribe: (listener: Listener) => void;
|
||||||
$unsubscribe: (listener: (mutation) => void) => void;
|
$unsubscribe: (listener: Listener) => void;
|
||||||
} & { [K in keyof AddWatchProp<S>]: AddWatchProp<S>[K] } & { [K in keyof A]: Action<A[K], S> } & {
|
} & { [K in keyof AddWatchProp<S>]: AddWatchProp<S>[K] } & { [K in keyof A]: Action<A[K], S> } & {
|
||||||
[K in keyof C]: ReturnType<C[K]>;
|
[K in keyof C]: ReturnType<C[K]>;
|
||||||
};
|
};
|
||||||
|
@ -76,11 +82,9 @@ type RemoveFirstFromTuple<T extends any[]> = T['length'] extends 0
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
export type UserComputedValues<S extends Record<string, unknown>> = {
|
export type UserComputedValues<S extends Record<string, unknown>> = {
|
||||||
[K: string]: ComputedFunction<S>;
|
[K: string]: (state: S) => any;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ComputedFunction<S extends Record<string, unknown>> = (state: S) => any;
|
|
||||||
|
|
||||||
export type AsyncAction<T extends ActionFunction<any>, S extends Record<string, unknown>> = (
|
export type AsyncAction<T extends ActionFunction<any>, S extends Record<string, unknown>> = (
|
||||||
this: StoreObj<S, any, any>,
|
this: StoreObj<S, any, any>,
|
||||||
...args: RemoveFirstFromTuple<Parameters<T>>
|
...args: RemoveFirstFromTuple<Parameters<T>>
|
||||||
|
|
Loading…
Reference in New Issue