从一个方法开始:浅析页面渲染流程

渲染前的准备

渲染前的准备,在 Vue 渲染框架下,会先安装所有所支持的默认组件,比如 Mask,Header 等,这些组件支持 XML 默认模版的 Vue 框架下的渲染,详情可见 main.ts 中,maskInstall 与 install,这两个函数将平台内部支持的组件进行了注册,随后将整个 Vue 挂载为运行时 App,随后进行初始化。

渲染的起步

OioProvider 方法是整个应用的入口,我们忽略掉一些配置方
法,将注意力集中到 initializeServices 从名称中我们可以看出来内部保存的都是初始化服务,其中提供了渲染服务等,我们当前使用的是 Vue 框架,所以当前其渲染的 Root 节点为 Vue, 以此,我们视野可以暂时转移到 admin-base中的 Root.vue以及 RootWidget上, 其实现了整个 Vue 框架下的 Root 节点如何渲染,其中定义了多个 widget,比如登陆页,首页,忘记密码已经重置密码等页面, 在本文中我们着重关注渲染首页的能力,
RootWidgetDefaultMetadataMainViewWidget作为渲染 Props中的 page即首页提供给下层组件使用,

渐入佳境

DefaultMetadataMainViewWidget从名称中可以看到,其为元数据主视图,主要做两件事,
1:提供 Mask 的渲染
2:提供元数据上下文初始化

该组件主要通过观察路由变化触发上面两个动作,当路由发生变化,该组件 reloadPage将被调用,
reloadPage方法通过组装路由参数构成一个唯一 key 向后段查询当前路由所对应的渲染信息, 随后将获取到的信息进行处理,初始化,即 元数据上下文初始化
在初始化后,会将获取到的数据注入到 MetadataViewWidget中,

Mask 的渲染

关于 Mask 的渲染,在获取到数据后,将生成 maskTemplate,并将其赋值, DefaultMetadataMainView.vue文件将渲染该模板,并渲染到页面中

数据的变更

当上面两件任务完成后,将开始主视图的渲染,
上文提到,DefaultMetadataMainViewWidget只负责 mask 的渲染和上下文的初始化,所以 DefaultMetadataMainViewWidget通过触发事件的方式来实现主视图的渲染, DefaultMetadataMainViewWidget将必要信息作为事件参数触发,
MultiTabsContainerWidget接收到 reloadMainViewCallChaining事件后,开启主视图渲染,

MultiTabsContainerWidget会刷新运行时上下文,即 refreshRuntimeContext,该方法将尝试查询并通过 createOrUpdateEnterTab方法创建 Tab 页,
createOrUpdateEnterTab最终生成一个 MultiTabItem格式的对象,该对象描述了 Tab 的相关信息,随后调用 createTabContainerWidget创建 tab 的容器即新建了一个 MultiTabContainerWidget组件即单个 tab 的容器,随后调用 setActiveTabItem, 并获取其绑定的 Vue 组件,并将其组件放置在 KeepAlive内部,触发更新,

主视图的渲染

MultiTabContainerWidget继承自MetadataViewWidget
MetadataViewWidget数据发生变更, 其绑定的 Vue 组件将解析 viewTemplate, 获取到与该模板 dslNodeType想匹配的 Vue 组件,当前例子中为 View.vue
随后 View.vue开始渲染,View.vue文件只是一个纯粹的容器,
View.vue被挂载时,其内部的 template属性包含了整个页面的描述信息,
View.vue需要做的就是将这个 template翻译并渲染成 DOM 展现在浏览器上,

渲染整个页面

View.vue被挂载时,其内部的 template属性包含了整个页面的描述信息,
View.vue主要做了两个事情,一:将 template 中的 widget转换为组件,二:根据当前的 template信息生成 slot信息,

const currentSlots = computed<Slots | undefined>(
      () =>
        DslRender.fetchVNodeSlots(props.dslDefinition) ||
        (Object.keys(context.slots).length ? context.slots : undefined)
    );

const renderWidget = createCustomWidget(InternalWidget.View, {
      ...context.attrs,
      type: props.type || viewType.value,
      template: props.dslDefinition,
      metadataHandle: props.metadataHandle || metadataHandle.value,
      rootHandle: props.rootHandle || rootHandle.value,
      parentHandle: props.parentHandle || parentHandle.value,
      slotName: props.slotName,
      inline: inlineProp
    } as ViewWidgetProps);

生成这两部分信息后,View.vue会将这两部分挂载到页面上,这两部分从代码中可以看出,主要靠 fetchVNodeSlots,createCustomWidget两个函数,

export function createCustomWidget(
    widget: string,
    props: CustomWidgetProps
): RenderWidget | undefined

public static fetchVNodeSlots(dsl: DslDefinition | undefined, supportedSlotNames?: string[]): Slots | undefined

从函数名称中我们可以看出,这两个函数一个会去创建 widget,一个会创建 slot,这就不得不提到 dsl-render.ts

dsl-render

从名称中我们可以看出 dsl-render.ts是专门用来渲染 DSL 的,其内部通过 fetchComponent函数返回不同 dslType对应的 Vue 组件,通过 fetchVNodeSlots去构建 Vue 插槽,当组件开始渲染时,UseWidgetTagMixin将混入各布局组件,通过基类 VueWidget所提供的 render能力将自己渲染成为容器,并且将 dslDefinition转换为 slot,将两者整合并挂载。此来层层向下进行渲染。渲染完成后Vue组件将触发自身的生命周期钩子,在VueWidget中,其将Widget组件自身的生命周期混入Vue生命周期,在Vue组件触发钩子后,将带动Wiget生命周期钩子的触发。以表格页面为例,将获取 TableView组件,并渲染至 View.vue中,随后触发 $$mounted钩子。

从一个方法开始:浅析页面渲染流程

数据的获取

TableView组件被挂载到页面时,触发 mounted生命周期钩子,并在基类 BaseView中发出 currentMountedCallChaining事件, BaseElementViewWidget监听到后,调用 mountedProcess,最终调用到 BaseElementListViewWidget,开始进行数据源请求,fetchData, 获取到数据后,调用 reloadDataSource

数据的变更

当搜索组件内部数据发生变更,将触发 SearchWidget.ts中 onSearch 事件,该事件将组装请求体,并且将请求体作为事件参数传递,外层的 SearchView接收到该事件后会进一步向外传播事件,并最终传递到 TableWidget.ts,该组件开始 refreshProcess刷新进程,该方法实际动作基本由基类 BaseElementListViewWidget提供,
该功能会以新数据向后端进行请求,随后将数据进行存储,触发子组件的更新,其核心逻辑类似于上一点中提到的数据的获取,不同的点在于触发点的区别,一个由mounted钩子触发,一个则由onSearch事件触发从一个方法开始:浅析页面渲染流程

Oinone社区 作者:liji2原创文章,如若转载,请注明出处:https://doc.oinone.top/frontend/20732.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

(0)
liji2的头像liji2数式员工
上一篇 2025年3月19日 am10:54
下一篇 2025年3月21日 am10:28

相关推荐

  • oio-spin 加载中

    用于页面和区块的加载中状态。 何时使用 页面局部处于等待异步数据或正在渲染过程时,合适的加载动效会有效缓解用户的焦虑。 API 参数 说明 类型 默认值 版本 delay 延迟显示加载效果的时间(防止闪烁) number (毫秒) – loading 是否为加载中状态 boolean true wrapperClassName 包装器的类属性 string –

    2023年12月18日
    1.1K00
  • 前端自定义组件之多页面步骤条

    本文将讲解如何通过自定义,实现多页面的步骤条组件。其中每个步骤的元素里都对应界面设计器的一个页面。以下是代码实现和原理分析。 代码实现 NextStepWidget 多页面步骤条 ts 组件 import { CallChaining, createRuntimeContextByView, customMutation, customQuery, RuntimeView, SPI, ViewCache, Widget, DefaultTabsWidget, BasePackWidget } from '@oinone/kunlun-dependencies'; import { isEmpty } from 'lodash-es'; import { MyMetadataViewWidget } from './MyMetadataViewWidget'; import NextStep from './NextStep.vue'; import { IStepConfig, StepDirection } from './type'; @SPI.ClassFactory(BasePackWidget.Token({ widget: 'NextStep' })) export class NextStepWidget extends DefaultTabsWidget { public initialize(props) { this.titles = props.template?.widgets?.map((item) => item.title) || []; props.template && (props.template.widgets = []); super.initialize(props); this.setComponent(NextStep); return this; } @Widget.Reactive() public get invisible() { return false; } // 配置的每一步名称 @Widget.Reactive() public titles = []; // region 上一步下一步配置 // 步骤配置,切换的顺序就是数组的顺序,模型没有限制 @Widget.Reactive() public get stepJsonConfig() { let data = JSON.parse( this.getDsl().stepJsonConfig || '[{"model":"resource.ResourceCountry","viewName":"国家form"},{"model":"resource.ResourceProvince","viewName":"省form"},{"model":"resource.ResourceCity","viewName":"市form"}]' ); return data as IStepConfig[]; } // 切换上一步下一步 @Widget.Method() public async onStepChange(stepDirection: StepDirection) { // 没有激活的,说明是初始化,取第一步 if (!this.activeStepKey) { const step = this.stepJsonConfig[0]; if (step) { this.activeStepKey = `${step.model}_${step.viewName}`; await this.initStepView(step); } return; } // 获取当前步骤的索引 if (this.currentActiveKeyIndex > -1) { await this.onSave(); // 获取下一步索引 const nextStepIndex = stepDirection === StepDirection.NEXT ? this.currentActiveKeyIndex + 1 : this.currentActiveKeyIndex – 1; // 在索引范围内,则渲染视图 if (nextStepIndex >= 0 && nextStepIndex < this.stepJsonConfig.length) { const nextStep = this.stepJsonConfig[nextStepIndex];…

    2025年7月21日
    72600
  • 如何实现页面间的跳转

    介绍 在日常的业务中,我们经常需要在多个模型的页面之间跳转,例如从商品的行可以直接点击链接跳转到类目详情,还有查看该订单发起的售后单列表,这里将给大家展示如何在oinone中如何实现这些功能。 方法一、通过界面设计器的无代码能力配置 表格行跳转到表单页/详情页 拖入一个跳转动作到表格行,保存动作后,在左侧的动作属性面板底部有个请求配置,里面的上下文属性就是配置跳转参数的地方,点击添加按钮可以增加一行参数 点击添加按钮后,可以看到新增了一行,行内有2个输入框,左侧输入框为目标视图模型的字段,右侧输入框为当前视图模型的表达式 注意 表达式中activeRecord关键字代表当前行的数据对象 “上下文”相关知识点 当前页面的模型和跳转后的页面模型相同的情况下,会字段带上当前行数据的id作为路由参数 上下文是从当前页面跳转到下个页面带的自定义参数 上下文会作为跳转后的页面数据加载函数的入参,后端的该函数需要根据该条件查询到数据返回给前端,典型的例子就是编辑页,根据id查询对象的其他字段信息返回 跳转后页面的数据加载函数可以在动作场景的时候选择加载函数,也可以在页面的加载函数处设置 方法二、通过低代码方式在自定义代码中调用 oinone提供了内置函数executeViewAction实现该功能 import { DefaultComparisonOperator, executeViewAction, QueryExpression, RuntimeViewAction, ViewActionTarget, ViewType } from '@kunlun/dependencies'; export class JumpActionWidget { protected goToObjectView() { executeViewAction( { viewType: ViewType.Form, moduleName: 'resource', model: 'resource.ResourceCountry', name: 'redirectUpdatePage', target: ViewActionTarget.Router } as RuntimeViewAction, undefined, undefined, { // 此处为id参数,目前只有表单和详情页需要 id: '12223', // 此处为上下文参数,context内对象的key是目标页面需要传递的默认值 context: JSON.stringify({code: 'xxx'}), // 此处为跳转后左侧菜单展开选中的配置 menu: JSON.stringify({"selectedKeys":["国家"],"openKeys":["地址库","地区"]}) } ); } protected goToListView() { const searchConditions: QueryExpression[] = []; searchConditions.push({ leftValue: ['countryCode'], // 查询条件的字段 operator: DefaultComparisonOperator.EQUAL, right: 'CN' // 字段的值 }); executeViewAction( { viewType: ViewType.Table, moduleName: 'resource', model: 'resource.ResourceCity', name: 'resource#市', target: ViewActionTarget.OpenWindow } as RuntimeViewAction, undefined, undefined, { // searchConditions相当于domain,不会随页面搜索项重置动作一起被清空 searchConditions: encodeURIComponent(JSON.stringify(searchConditions)), // searchBody的字段会填充搜索区域的字段组件,会随页面搜索项重置动作一起被清空 searchBody: JSON.stringify({code: 'CN'}), menu: JSON.stringify({"selectedKeys":["国家"],"openKeys":["地址库","地区"]}) } ); } } 扩展知识点 为什么executeViewAction跳转到的新页面不是入参的moduleName属性对应的模块? 答:跳转后所在的模块优先级为: 第一个入参的resModuleName属性对应的模块 执行executeViewAction时所在的模块 第一个入参的moduleName属性对应的模块 如何快速获取选中菜单的值? 答:先通过页面菜单手动打开页面,然后在浏览器自带调试工具的控制台执行decodeURIComponent(location.href),其中的menu参数就是我们需要的值

    2024年5月13日
    2.8K00
  • 【界面设计器】树形表格

    阅读之前 你应该: 熟悉模型的增删改查相关内容。【界面设计器】模型增删改查基础

    2024年4月19日
    1.2K00
  • 多对多的表格 点击添加按钮打开一个表单弹窗

    多对多的表格 点击添加按钮打开一个表单弹窗 默认情况下,多对多的表格上方的添加按钮点击后,打开的是个表格 ,如果您期望点击添加按钮打开的是个表单页面,那么可以按照下方的操作来 1: 先从界面设计器拖一个多对多的字段进来 2: 将该字段切换成表格,并拖入一些字段到表格上 3: 选中添加按钮,将其隐藏 4: 从组件区域的动作分组中拖一个跳转动作,并且进行如下的配置 5: 属性填写好后进行保存,然后在设计弹窗 6: 拖入对应的字段到弹窗中, 当弹窗界面设计完成后,再把保存的按钮拖入进来 这样多对多的添加弹窗就变成了表单

    前端 2023年11月1日
    2.6K00

Leave a Reply

登录后才能评论