d40: vue杂项问题 - 详解
导航
- Vue 中使用 HTML 原生属性的问题
el-dialog与父组件交互的异步问题
- 动态路由机制中关于根路由的小问题
ref的dep特性
1. Vue 中使用 HTML 原生属性的问题
在 Vue 项目中,我们可能会不小心使用 HTML 原生事件属性,例如:
<div onclick="profileVisible = true">点击我</div>
这看起来没什么问题,但实际上,这种方式存在很大的隐患。当 onclick 被触发时,浏览器会在 全局作用域(即 window 对象)下执行这句 JS 代码。它会寻找一个名为 window.profileVisible 的变量并将其设置为 true,而完全不知道 Vue 组件实例的存在,更不知道你定义在组件 data 中的那个 profileVisible。
Vue 指令与原生 HTML 属性的对比
| 特性 | @click (Vue 指令) | onclick (原生 HTML 属性) |
|---|---|---|
| 执行上下文 | Vue 组件实例 (this) | 全局作用域 (window) |
| 响应式 | 能触发 Vue 的响应式更新 | 不能触发 Vue 的响应式更新 |
| 数据访问 | 可以访问 data, props, computed 等 | 只能访问全局变量 |
| 方法调用 | 可以调用 methods 中定义的方法 | 只能调用全局函数 |
| 推荐用法 | 在 Vue 项目中必须使用 | 在 Vue 项目中应避免使用 |
结论
在 Vue 项目中,始终 应该使用 Vue 的事件处理指令(如 @click, @input, @submit 等)来处理 DOM 事件,而不是原生的 HTML 事件属性。
2. el-dialog 与父组件交互的异步问题
在使用 el-dialog 组件时,我们可能会遇到子组件与父组件交互的异步问题。例如,我们有以下代码:
当我们试图向子组件传输 allPermissions 等数据,并在 handleOpen 方法中将 allPermissions 处理为树时,发现子组件生成的树还是上一次的数据。这是因为 el-dialog 的 open 事件在 DOM 更新前触发,而父组件异步拉取新权限后才将 props.permissions 改掉。因此,当 handleOpen 同步执行时,新数据还没下来,我们处理的其实是上一次的数据。
解决方案
此时,应该使用 watch 监听 allPermissions 的变化,并进行同步:
watch(
() => props.allPermissions,
() => {
tableData.value = handleTree(props.allPermissions);
},
{ immediate: true } // 立即执行一次
);
3. 动态路由机制中关于根路由的小问题
在动态路由机制中,我们可能会遇到一些关于根路由的小问题。例如,原来的动态路由机制会将无 component 的组件(即目录)用 LayoutView 来兜底。这样做的问题是,如果出现目录嵌套目录的情况,就会对 Layout 进行重复渲染。
解决方案
这个问题其实很好解决,只需要将目录的 component 置为 null,并整体上添加 LayoutView 作为根路由即可:
/**
* 获取路由列表
*/
export const buildRoutes = async (menus) => {
const children = buildNestedRoutes(menus);
const route = {
path: '/',
redirect: '/index',
component: LayoutView,
children: children,
meta: {}
};
return route;
};
在这个过程中,我们意识到了重复定义相同的根路由可能会带来一些问题。Vue Router 的路由匹配机制是通过路径 + 其他属性(如 name、component)来判断路由是否“重复”的。为了避免奇奇怪怪的隐式覆盖问题,最好统一一个根路由:
/**
* 加入到 Layout 中的静态路由
*/
export const staticChildren = [
{
path: 'index',
component: () => import('@/views/home/index.vue'),
name: 'Index',
meta: {
title: '首页',
hidden: false,
}
}
];
/**
* 获取路由列表
*/
export const buildRoutes = async (menus) => {
const dynamicChildren = buildNestedRoutes(menus);
// 合并静态路由和动态路由
const allChildren = mergeChildren(staticChildren, dynamicChildren);
// 创建根路由
const rootRoute = {
path: '/',
component: LayoutView,
name: 'Layout',
redirect: '/index',
children: allChildren,
meta: {}
};
// 防止重复添加
if (router.hasRoute('Layout')) router.removeRoute('Layout');
return rootRoute;
};
我们可以通过 map 来简单地去重,这里就不展示了。
4. ref 的 dep 特性
在使用 ref 时,我们可能会遇到一些关于 dep 特性的问题。例如,我们有以下代码:
export const AllPages = {
pageNum: 1,
pageSize: 1000000
};
现在有两段类似的代码:
queryParams.value = { pageNum: 1, pageSize: 10};
queryParams.value = AllPages;
我们将其用于对 createdAt 字段进行重置:
第一段代码顺利完成了,但是第二段代码还残留着之前的 createdAt 数据,这是为什么呢?
原因分析
在 queryParams 中,我们一开始没有 createdAt 这个字段。而在第一次添加 createdAt 后,dep 列表里会添加这个字段的槽位。我们对其重新赋值 { pageNum: 1, pageSize: 10} 后,Vue 的 proxy 会保留引用关系。因为 createdAt 实际上不存在,所以悄悄地对其赋值为 undefined。queryParams.value = AllPages; 这一步相当于把 ref 重新赋值了一个普通对象,这个对象里面又没有关于 createdAt 的信息,v-model 绑定的路径直接断了(变成访问一个非响应式的、根本不存在的属性),DatePicker 内部监听不到“从有到无”的变动,于是面板里依旧显示旧值。
解决方案
将 AllPages 对象展开 { ...AllPages },这样就和 { pageNum: 1, pageSize: 10} 等价了。
以上就是我们在使用 Vue.js 过程中遇到的一些常见问题及其解决方案。希望这些内容能对大家有所帮助。
注意事项
- 在 Vue 项目中,始终 使用 Vue 的事件处理指令(如
@click,@input,@submit等)来处理 DOM 事件,而不是原生的 HTML 事件属性。 - 使用
watch监听props的变化,以解决子组件与父组件交互的异步问题。 - 在动态路由机制中,避免重复定义相同的根路由,统一一个根路由可以避免很多问题。
- 注意
ref的dep特性,在重新赋值时要确保对象的结构一致,避免出现绑定路径断开的问题。
浙公网安备 33010602011771号