mirror of
https://github.com/WeBankFinTech/fes.js.git
synced 2025-10-13 18:22:13 +08:00
refactor(plugin-model): 使用@vueuse/core实现模型状态共享
重构模型插件,利用@vueuse/core的createSharedComposable实现模型状态共享 移除默认容器模板,改为使用JSX实现的getRootContainer组件 优化路由初始化逻辑,将beforeRender处理移至根容器组件
This commit is contained in:
parent
c1fa59fcf8
commit
6db74bfbbc
@ -30,6 +30,7 @@
|
||||
"@fesjs/plugin-sass": "workspace:*",
|
||||
"@fesjs/plugin-swc": "workspace:*",
|
||||
"@fesjs/plugin-watermark": "workspace:*",
|
||||
"@vueuse/core": "13.9.0",
|
||||
"core-js": "^3.45.1",
|
||||
"pinia": "^3.0.3",
|
||||
"vue": "^3.5.21"
|
||||
|
@ -3,6 +3,6 @@ import { ref } from 'vue';
|
||||
export default function user() {
|
||||
const count = ref(1);
|
||||
return {
|
||||
count
|
||||
count,
|
||||
};
|
||||
}
|
||||
|
@ -11,8 +11,8 @@
|
||||
</config>
|
||||
|
||||
<script>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { MonacoEditor, useLayout } from '@fesjs/fes';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -8,7 +8,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineRouteMeta, useRouter } from '@fesjs/fes';
|
||||
import { defineRouteMeta, useModel, useRouter } from '@fesjs/fes';
|
||||
import { FButton } from '@fesjs/fes-design';
|
||||
|
||||
defineRouteMeta({
|
||||
@ -16,7 +16,9 @@ defineRouteMeta({
|
||||
title: '$test.test',
|
||||
});
|
||||
|
||||
console.log('123123'.replaceAll('123', '234'));
|
||||
const initialState = useModel('@@initialState');
|
||||
|
||||
console.log(initialState);
|
||||
|
||||
const router = useRouter();
|
||||
function go() {
|
||||
|
@ -31,6 +31,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@fesjs/fes": "^3.1.12",
|
||||
"@vueuse/core": "^13.0.0",
|
||||
"vue": "^3.5.21"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -1,4 +1,6 @@
|
||||
|
||||
import { createSharedComposable } from '@vueuse/core';
|
||||
|
||||
{{{userImports}}}
|
||||
{{{extraImports}}}
|
||||
|
||||
@ -12,18 +14,40 @@ export const models = {
|
||||
};
|
||||
|
||||
|
||||
const cache = new Map();
|
||||
/**
|
||||
* 使用createSharedComposable包装后的共享模型对象
|
||||
* 每个模型都会被包装成可共享的组合式函数
|
||||
*/
|
||||
const sharedModels = {}
|
||||
|
||||
export const useModel = (name) => {
|
||||
const modelFunc = models[name];
|
||||
if (modelFunc === undefined) {
|
||||
throw new Error('[plugin-model]: useModel, name is undefined.');
|
||||
// 为每个模型创建共享的组合式函数
|
||||
Object.keys(models).forEach(key => {
|
||||
/**
|
||||
* 使用createSharedComposable包装模型
|
||||
* key作为唯一标识符,确保同一模型在不同组件间共享状态
|
||||
*/
|
||||
sharedModels[key] = createSharedComposable(models[key], `model:${key}`)
|
||||
})
|
||||
|
||||
/**
|
||||
* 使用模型的Hook函数
|
||||
* 提供统一的模型访问接口,支持状态共享和生命周期管理
|
||||
* @param {string} name - 模型名称,必须是已注册的模型
|
||||
* @returns {any} 共享的模型实例,多个组件调用同一模型时返回相同的状态实例
|
||||
*/
|
||||
export function useModel(name) {
|
||||
// 检查模型是否存在于注册列表中
|
||||
if (!(name in sharedModels)) {
|
||||
// 提供详细的错误信息,帮助开发者调试
|
||||
const availableModels = Object.keys(sharedModels).join(', ')
|
||||
throw new Error(
|
||||
`模型 "${name}" 不存在。\n` +
|
||||
`可用的模型有: ${availableModels}\n` +
|
||||
`请检查模型名称是否正确,或确认模型文件是否已正确导出。`
|
||||
)
|
||||
}
|
||||
if (typeof modelFunc !== 'function') {
|
||||
throw new Error('[plugin-model]: useModel is not a function.');
|
||||
}
|
||||
if (!cache.has(name)) {
|
||||
cache.set(name, modelFunc());
|
||||
}
|
||||
return cache.get(name);
|
||||
};
|
||||
|
||||
// 返回共享的模型实例
|
||||
// createSharedComposable确保同一模型在不同组件间共享状态
|
||||
return sharedModels[name]()
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
@ -6,7 +6,7 @@ import { plugin } from './core/plugin';
|
||||
import './core/pluginRegister';
|
||||
import { ApplyPluginsType } from '{{{ runtimePath }}}';
|
||||
import { getRoutes } from './core/routes/routes';
|
||||
import DefaultContainer from './defaultContainer.vue';
|
||||
import getRootContainer from './getRootContainer';
|
||||
|
||||
{{{ imports }}}
|
||||
|
||||
@ -16,17 +16,8 @@ import DefaultContainer from './defaultContainer.vue';
|
||||
|
||||
const renderClient = (opts = {}) => {
|
||||
const { plugin, routes, rootElement } = opts;
|
||||
const rootContainer = plugin.applyPlugins({
|
||||
type: ApplyPluginsType.modify,
|
||||
key: 'rootContainer',
|
||||
initialValue: DefaultContainer,
|
||||
args: {
|
||||
routes: routes,
|
||||
plugin: plugin
|
||||
}
|
||||
});
|
||||
|
||||
const app = createApp(rootContainer);
|
||||
|
||||
const app = createApp(getRootContainer(routes, plugin));
|
||||
|
||||
plugin.applyPlugins({
|
||||
key: 'onAppCreated',
|
||||
|
@ -0,0 +1,82 @@
|
||||
import { defineComponent, onBeforeMount, ref, provide, } from 'vue'
|
||||
import { useRouter, RouterView } from 'vue-router'
|
||||
import { plugin } from './core/plugin';
|
||||
import { updateInitialState } from './initialState';
|
||||
import { ApplyPluginsType } from '{{{ runtimePath }}}';
|
||||
|
||||
export const DefaultContainer = defineComponent({
|
||||
name: 'DefaultContainer',
|
||||
setup() {
|
||||
return () => {
|
||||
return <RouterView></RouterView>
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default function getRootContainer(_routes, _plugin) {
|
||||
return defineComponent({
|
||||
name: 'RootContainer',
|
||||
setup(props) {
|
||||
const RootContainer = plugin.applyPlugins({
|
||||
type: ApplyPluginsType.modify,
|
||||
key: 'rootContainer',
|
||||
initialValue: DefaultContainer,
|
||||
args: {
|
||||
routes: _routes,
|
||||
plugin: _plugin
|
||||
}
|
||||
});
|
||||
const beforeRenderConfig = plugin.applyPlugins({
|
||||
key: "beforeRender",
|
||||
type: ApplyPluginsType.modify,
|
||||
initialValue: {
|
||||
loading: null,
|
||||
action: null
|
||||
},
|
||||
});
|
||||
|
||||
if (typeof beforeRenderConfig.action !== "function") {
|
||||
return () => <RootContainer {...props} />
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
let isInit = false
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
if (isInit) {
|
||||
return next()
|
||||
}
|
||||
try {
|
||||
isInit = true
|
||||
isLoading.value = true;
|
||||
const _initialState = await beforeRenderConfig.action({ router });
|
||||
updateInitialState(_initialState);
|
||||
next();
|
||||
} catch (e) {
|
||||
console.error(`[fes] beforeRender执行出现异常:`);
|
||||
console.error(e);
|
||||
next(false);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
})
|
||||
plugin.applyPlugins({
|
||||
key: 'onRouterCreated',
|
||||
type: ApplyPluginsType.event,
|
||||
args: { router },
|
||||
});
|
||||
})
|
||||
|
||||
return () => {
|
||||
if (isLoading.value) {
|
||||
return <beforeRenderConfig.loading {...props} />
|
||||
}
|
||||
return <RootContainer {...props} />
|
||||
}
|
||||
},
|
||||
})
|
||||
};
|
||||
|
@ -73,15 +73,14 @@ export default function (api) {
|
||||
}),
|
||||
});
|
||||
|
||||
const defaultContainerName = 'defaultContainer';
|
||||
api.writeTmpFile({
|
||||
path: `${defaultContainerName}.vue`,
|
||||
content: readFileSync(join(__dirname, `./${defaultContainerName}.tpl`), 'utf-8'),
|
||||
});
|
||||
|
||||
api.writeTmpFile({
|
||||
path: `initialState.js`,
|
||||
content: Mustache.render(readFileSync(join(__dirname, `./initialState.tpl`), 'utf-8')),
|
||||
});
|
||||
|
||||
api.writeTmpFile({
|
||||
path: `getRootContainer.jsx`,
|
||||
content: Mustache.render(readFileSync(join(__dirname, `./getRootContainer.jsx.tpl`), 'utf-8'), { runtimePath }),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { createApp } from 'vue';
|
||||
import { createRouter as createVueRouter, {{{ CREATE_HISTORY }}}, ApplyPluginsType } from '{{{ runtimePath }}}';
|
||||
import { plugin } from '../plugin';
|
||||
import { updateInitialState } from '../../initialState';
|
||||
|
||||
const ROUTER_BASE = '{{{ routerBase }}}';
|
||||
let router = null;
|
||||
@ -35,48 +33,7 @@ export const createRouter = (routes) => {
|
||||
router = createVueRouter({
|
||||
history,
|
||||
routes: route.routes
|
||||
});
|
||||
|
||||
let isInit = false
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
if(isInit){
|
||||
return next()
|
||||
}
|
||||
isInit = true
|
||||
const beforeRenderConfig = plugin.applyPlugins({
|
||||
key: "beforeRender",
|
||||
type: ApplyPluginsType.modify,
|
||||
initialValue: {
|
||||
loading: null,
|
||||
action: null
|
||||
},
|
||||
});
|
||||
if (typeof beforeRenderConfig.action !== "function") {
|
||||
return next();
|
||||
}
|
||||
const rootElement = document.createElement('div');
|
||||
document.body.appendChild(rootElement)
|
||||
const app = createApp(beforeRenderConfig.loading);
|
||||
app.mount(rootElement);
|
||||
try {
|
||||
const initialState = await beforeRenderConfig.action({router, history});
|
||||
updateInitialState(initialState || {})
|
||||
next();
|
||||
} catch(e){
|
||||
next(false);
|
||||
console.error(`[fes] beforeRender执行出现异常:`);
|
||||
console.error(e);
|
||||
}
|
||||
app.unmount();
|
||||
app._container.innerHTML = '';
|
||||
document.body.removeChild(rootElement);
|
||||
})
|
||||
|
||||
plugin.applyPlugins({
|
||||
key: 'onRouterCreated',
|
||||
type: ApplyPluginsType.event,
|
||||
args: { router, history },
|
||||
});
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
|
37
pnpm-lock.yaml
generated
37
pnpm-lock.yaml
generated
@ -423,6 +423,9 @@ importers:
|
||||
'@fesjs/plugin-watermark':
|
||||
specifier: workspace:*
|
||||
version: link:../plugin-watermark
|
||||
'@vueuse/core':
|
||||
specifier: 13.9.0
|
||||
version: 13.9.0(vue@3.5.21(typescript@5.9.2))
|
||||
core-js:
|
||||
specifier: ^3.45.1
|
||||
version: 3.45.1
|
||||
@ -633,6 +636,9 @@ importers:
|
||||
'@fesjs/utils':
|
||||
specifier: workspace:*
|
||||
version: link:../utils
|
||||
'@vueuse/core':
|
||||
specifier: ^10.9.0
|
||||
version: 10.11.1(vue@3.5.21(typescript@5.9.2))
|
||||
vue:
|
||||
specifier: ^3.5.21
|
||||
version: 3.5.21(typescript@5.9.2)
|
||||
@ -3165,6 +3171,9 @@ packages:
|
||||
'@types/web-bluetooth@0.0.20':
|
||||
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
|
||||
|
||||
'@types/web-bluetooth@0.0.21':
|
||||
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
||||
|
||||
@ -3339,18 +3348,31 @@ packages:
|
||||
'@vueuse/core@10.11.1':
|
||||
resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==}
|
||||
|
||||
'@vueuse/core@13.9.0':
|
||||
resolution: {integrity: sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
'@vueuse/core@9.13.0':
|
||||
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
|
||||
|
||||
'@vueuse/metadata@10.11.1':
|
||||
resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==}
|
||||
|
||||
'@vueuse/metadata@13.9.0':
|
||||
resolution: {integrity: sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==}
|
||||
|
||||
'@vueuse/metadata@9.13.0':
|
||||
resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
|
||||
|
||||
'@vueuse/shared@10.11.1':
|
||||
resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
|
||||
|
||||
'@vueuse/shared@13.9.0':
|
||||
resolution: {integrity: sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
'@vueuse/shared@9.13.0':
|
||||
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
|
||||
|
||||
@ -10718,6 +10740,8 @@ snapshots:
|
||||
|
||||
'@types/web-bluetooth@0.0.20': {}
|
||||
|
||||
'@types/web-bluetooth@0.0.21': {}
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
dependencies:
|
||||
'@types/node': 24.3.0
|
||||
@ -10990,6 +11014,13 @@ snapshots:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/core@13.9.0(vue@3.5.21(typescript@5.9.2))':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.21
|
||||
'@vueuse/metadata': 13.9.0
|
||||
'@vueuse/shared': 13.9.0(vue@3.5.21(typescript@5.9.2))
|
||||
vue: 3.5.21(typescript@5.9.2)
|
||||
|
||||
'@vueuse/core@9.13.0(vue@3.5.21(typescript@5.9.2))':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.16
|
||||
@ -11002,6 +11033,8 @@ snapshots:
|
||||
|
||||
'@vueuse/metadata@10.11.1': {}
|
||||
|
||||
'@vueuse/metadata@13.9.0': {}
|
||||
|
||||
'@vueuse/metadata@9.13.0': {}
|
||||
|
||||
'@vueuse/shared@10.11.1(vue@3.5.21(typescript@5.9.2))':
|
||||
@ -11011,6 +11044,10 @@ snapshots:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/shared@13.9.0(vue@3.5.21(typescript@5.9.2))':
|
||||
dependencies:
|
||||
vue: 3.5.21(typescript@5.9.2)
|
||||
|
||||
'@vueuse/shared@9.13.0(vue@3.5.21(typescript@5.9.2))':
|
||||
dependencies:
|
||||
vue-demi: 0.14.10(vue@3.5.21(typescript@5.9.2))
|
||||
|
Loading…
x
Reference in New Issue
Block a user