Vue组件初始化前动态属性值初始化方案咨询,created阶段$refs为空
需要给Vue.js组件绑定动态属性,且需绑定复合值,但不清楚应在何时何处初始化这些属性的默认值。尝试在created()生命周期钩子中执行初始化操作,但此时this.$refs为空。
自定义的MyField类除值本身外,还包含enabled等扩展属性。项目不使用静态属性,模板中仅字段名是静态的,且字段名同时作为属性名。所有模板对应的script内容均为通用逻辑,可通过mixin等方式注入。
示例代码如下:
<template> <enhanced-field type='text' v-model="meta$['name'].value" ... ref="name" :disabled="!meta$['name'].enabled" /> </template> <script> export default { data() { return { meta$: new Map() // 可替换为vuex store } }, created() { // 此处尝试初始化默认值,但此时this.$refs为空 Object.keys(this.$refs).forEach((field) => { var v = this.meta$[field]; if (!v) this.meta$[field] = new MyField(this.$refs[field].nodeName); }); }, // .... } </script>
2021.11.25 更新
背景
应用有统一控制器处理表单提交,会根据组件事件处理的静态配置跳转新页面,事件处理数据不以JavaScript函数存储,而是作为页面的XML标签存在,因此数据不是页面加载后获取,而是在进入页面前就已完成获取。常规情况下每个页面无需编写自定义JavaScript函数,所有逻辑均在页面XML中配置,Vue的事件处理逻辑会将配置的处理数据转换为JavaScript执行,目标是将遗留系统迁移到基于Vue.js的版本。
临时解决方案
跳转新页面前,将页面数据存入名为pending的键值对变量并挂载到route对象上,在beforeRouteEnter钩子中将route上的pending数据移动到页面级数据容器(例如$thisPageVM.data$,键值对结构),之后所有字段的mounted钩子会尝试从该页面级容器获取数据,若不存在则初始化默认值并回存到页面级容器。数据本身存储在页面级,每个字段保留一份computed副本,表单提交时会发送该页面级容器的数据。
现存技术问题
字段的model是computed变量val,该computed变量在mounted钩子触发前就被调用,因此需要在mounted钩子中调用this.$recompute('val')重新初始化其值,$recompute是Vue2版本下的hack实现方案。
核心矛盾为created阶段DOM还未挂载,$refs必然为空,所有依赖DOM节点、子组件实例的初始化逻辑都不能放在这个阶段执行,结合项目场景,有三个优化方向可以完全规避当前需要hack$recompute的问题:
- 针对不需要依赖
$refs属性的初始化逻辑:页面数据在进入页面前就已经全部获取完成,完全可以在beforeRouteEnter钩子中一次性完成所有meta$字段的初始化,不需要等待子组件mounted触发再逐个初始化,此时初始化完成的数据在computed首次调用时就已经存在,不需要后续重新计算。 - 针对必须依赖
$refs的初始化逻辑:统一移到mounted钩子中执行,Vue2中mounted触发时所有子组件已经完成挂载,$refs可以正常访问。如果需要等待所有异步子组件全部挂载完成,可以在mounted中加一层$nextTick包裹初始化逻辑:
mounted() { this.$nextTick(() => { // 此处$refs已完全填充,执行初始化逻辑即可 Object.keys(this.$refs).forEach((field) => { if (!this.meta$[field]) { // 使用$set赋值保证Vue2的响应式触发 this.$set(this.meta$, field, new MyField(this.$refs[field].nodeName)) } }) }) }
- 如果要保留现有每个字段自己初始化的逻辑,不需要用hack的
$recompute:给val这个computed的依赖项设置一个初始的响应式标记位,初始化完成后修改标记位触发computed自动重新计算即可。
内容的提问来源于stack exchange,提问作者Daniel Yang

