需求点:我需要根据某些条件下去异步请求判断某个计算属性返回值

最终进行拆分
js
export default {
data(){
// 放置需要异步赋值的变量
formId: null,
allPerms: [],
userDept: null,
isLoading: true,
},
computed: {
rootNode(){
// 初始化:isLoading true
if(this.isLoading) return []
// 计算属性只做同步逻辑
const { hierarchicalByCurrentDepart } = this.SdFormItemContext?.field?.attr || {}
if(hierarchicalByCurrentDepart === 'true'){
const authorityIds = (this.$attrs.authorityIds || '').split(',').map(item => item.trim())
const hasMatch = authorityIds.some(item => this.allPerms.includes(item))
if(hasMatch){
return []
}else{
return this.userDept ? [this.userDept] : []
}
}else{
return []
}
}
},
mounted(){
// 同步 初始化用户部门信息
const t = getUserInfo()
this.userDept = {
code: t.deptId.toString(),
id: t.deptId.toString(),
name: t.deptName
}
// 处理异步逻辑
this.initFormIdAndPerms()
},
methods: {
async initFormIdAndPerms(){
try{
// 等待DOM执行完毕
await this.$nextTick()
this.initFormId()
if(this.formId && this.$attrs.authority === '1'){
await this.fetchPerms()
}
}catch(error){
console.error(error)
}finally{
this.isLoading = false
}
},
initFormId(){
if(/某条件/){
this.formId = 'XXXXX'
}
},
async fetchPerms(){
const res = await axios({
url: '',
method: 'get',
params: {formId: this.formId}
})
this.allPerms = res.data
}
}
}
Vue组件初始化&&异步执行
- 组件初始化:创建 data 和 computed 依赖关系
- data 被初始化响应式对象:isLoading:true, allPerms: true
- computed计算属性 rootNode 执行第一次
- 读取 this. isLoading 为true 返回 []
- Vue 记录依赖 :rootNode 依赖 isLoading 和 allPerms
- 执行mounted钩子
- 进入 initFormIdAndPerms() 异步流程
- await this.$nextTick() → 加入微任务队列,等待 DOM 更新
- 同步代码执行完毕,事件循环开始处理微任务
- $nextTick 回调执行 → 调用 initFormId() 获取 formId
- 如果有权限且 formId 存在,调用 fetchPerms()
- await axios.get() → 发送请求,加入宏任务队列
- 网络请求完成,axios 回调执行
- 拿到后端返回的权限数据,赋值给 this.allPerms
- Vue 检测到 allPerms 变化 → 触发 computed 重新执行
- 此时 isLoading 仍为 true,所以计算属性还是返回 []
- 进入 finally 块
- 执行 this.isLoading = false
- Vue 检测到 isLoading 变化 → 再次触发 computed 重新执行
关键点
- 初始计算属性执行:组件初始化就执行了,此时还没有异步数据
- 事件循环的异步执行:$nextTick 和 axios,等同步代码执行后再处理异步操作
- 响应式更新触发:当 isLoading 或 allPerms 变化时,Vue 会自动重新执行计算属性,这就是 computed 能拿到最新值的核心原理。
- finally 的保障作用:无论异步操作成功还是失败,finally 块都会执行,确保 isLoading 被设置为 false,避免界面一直处于加载状态。
bash
async initFormIdAndPerms() {
await this.$nextTick() // 等待 DOM 更新完成,再执行后面的代码
this.initFormId()
if (this.$attrs.authority === '1' && this.formId) {
await this.fetchPerms() // 等待接口请求完成,再执行后续逻辑
}
}
- async/await:让异步代码 "变同步",
- async :加在函数前面,标记这个函数是异步函数 ,它的返回值会自动变成一个 Promise。
- await :只能用在 async 函数里,作用是等待后面的 Promise 执行完成,再继续往下走。
- 如果没有await,initFormId可能会在DOM没更新时执行,导致拿不到实例对象
- await this.fetchPerms()确保接口请求返回数据后,再执行下一步,避免数据没拿到就执行计算属性
javascript事件循环
JavaScript 是单线程语言 (同一时间只能做一件事),但为了处理异步操作 (如网络请求、定时器、DOM 事件),设计了事件循环机制。
- 执行栈:同步代码执行的地方,遵循"先进后出"
- 任务队列:存放异步任务的回调函数
- 宏任务:script 整体代码、setTimeout/setInterval、AJAX 请求、DOM 事件、setImmediate(Node 环境)。
- 微任务:Promise.then/catch/finally、async/await(本质是 Promise 语法糖)、queueMicrotask、process.nextTick(Node 环境,优先级最高)。
- 事件循环规则
- 先执行执行栈的同步代码
- 同步代码执行完后,清空所有微任务队列,按顺序执行
- 执行一个宏任务,执行完后再次清空所有微任务队列
- 重复以上步骤
try catch异常捕获
- try catch用于捕获代码执行过程中的同步异常避免程序崩溃
- try catch无法捕获异步异常,比如setTimeout,需在异步回调内部单独捕获
async await
- async/await 是 ES2017 推出的Promise 语法糖 ,让异步代码看起来像同步代码,更易读、易维护。
- async:修饰函数,使函数返回一个 Promise 对象(即使函数内部返回普通值,也会被包装成 Promise.resolve (值))
- await:只能在 async 函数内部使用,等待 Promise 状态变为 resolved/rejected:
- 若 Promise 成功(resolved),await 返回 Promise 的结果。
- 若 Promise 失败(rejected),await 会抛出异常,需用 try/catch 捕获。
事件循环与 Vue 的 computed/watch 结合的核心规则:
更新时机:computed 重新计算、watch 回调执行都属于微任务,在同步代码执行完毕后才会触发。
执行顺序:同步代码 → 普通微任务(Promise.then) → Vue 微任务(computed/watch) → 宏任务(setTimeout / 接口请求)。
实际开发技巧:
想要获取修改响应式数据后的最新 computed 值 / 更新后的 DOM,必须用 nextTick 等待微任务执行。
避免在同步代码中频繁修改响应式数据(Vue 会批量处理微任务,优化性能)。
watch 回调内的异步操作(宏任务)会在所有微任务执行完后才执行,不会阻塞当前的响应式更新。
watch 能写 await 异步请求、computed 不能的核心原因:
- 设计初衷:computed 是 "同步计算派生值" 的纯函数,必须立即返回值;watch 是 "处理数据变化副作用的监听器,无返回值要求,可执行异步。
- 底层逻辑:computed 的缓存、依赖收集依赖同步执行,异步返回的 Promise 无法参与响应式系统 ;watch仅触发回调,不参与值的派生,异步不影响核心逻辑。
- 最佳实践:watch 负责异步请求、数据赋值等副作用,computed 负责基于已有响应式数据的同步格式化 / 计算,两者配合是 Vue异步场景的标准写法。