Vue: 等待异步组件生命周期钩子

创建于 2017-12-08  ·  51评论  ·  资料来源: vuejs/vue

这个功能解决了什么问题?

如果用户需要实现依赖于异步操作的生命周期钩子,vue 应该尊重所实现钩子的异步特性并在 vue 地等待它。

提议的 API 是什么样的?

API 不会改变; 只有现在它是如何通过等待异步钩子来工作的。

最有用的评论

这是我想要等待的实际代码:

  beforeMount: async function() {
       this.user = await client.get({type: 'user', id: this.$route.params.id});
    }

这将是UserPage组件的一部分。

所有51条评论

所以你要

created () {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log('created')
            resolve()
        })
    })
},
mounted () {
    console.log('mounted')
}

显示

mounted

?

创建功能请求时,请添加一个真实世界的用例,以使请求值得实施。

虽然这在理论上是一个很酷的想法,但这需要对架构进行根本性的重新思考/重写才能实现,并且可能会破坏许多依赖于生命周期钩子同步特性的逻辑。 因此,为了证明这种改变的合理性,收益必须是巨大的——否则只有在我们计划进行全面突破性升级时才能考虑这一点,这不太可能很快发生。

暂时结束,但请随时跟进更具体的推理/用例/含义。

@posva 明白了——我道歉。 我的实际用例是我有一个UserPage组件,它从页面路由参数(通过this.$route.params )接收user_id ,然后从数据库中获取实际用户数据在服务器上使用如下命令:
this.user = await client.get({type: 'user', id: this.$route.params.id})
其中this.user指的是UserPage组件的data部分中的UserPage user字段。

理想情况下,我希望在创建组件之后执行该行代码(以便this.$route.params可用)但组件实际安装user插值而不会出现有关未定义值的任何错误。

@yyx990803
我可能是个菜鸟,但唯一的变化不应该是在调用生命周期钩子之前添加await关键字,例如mounted等,在 Vue 领域吗?

这是我想要等待的实际代码:

  beforeMount: async function() {
       this.user = await client.get({type: 'user', id: this.$route.params.id});
    }

这将是UserPage组件的一部分。

不用担心! 我在想象那个用例。 最好按照 vue-router 文档中的描述处理它,因为它打开了显示加载状态的不同方式。 顺便说一句,您可以在渲染组件之前等待数据存在。

好的,这是有道理的。 但是,现在,如果我有一个用户组件是用户页面的精简版本(例如,当您将鼠标悬停在 facebook 上的用户名上并“查看”他们的个人资料时出现的组件),那么路由器此处不涉及, id将作为属性传递给组件。

从大局来看,JavaScript 中的函数现在可以是同步的或异步的,生命周期钩子是函数,以及我们将它们视为函数的方式,应该支持异步(如我的用例和直观的“到达”所示)对于我在这里使用的方法)。

你有很多方法可以做到。 最简单的方法是使用一个以 null 开头的变量,获取数据并设置它,切换实际组件(因为 v-if)。 一个更奇特的版本是一个解析组件并使用<component :is="myDynamicComp"/>函数😄
但是请不要将问题劫持为问题😉 使用论坛或不和谐

不,我真的不想帮助代码! 实际上,我已经实施了与您的建议非常相似的解决方法。 我想说的是,仅使用 JS 异步功能要直观得多。

我没有意识到的部分是异步代码和同步代码本质上是不同的,因此不能在不从根本上将自身更改为异步代码的情况下强制同步代码遵循异步代码。 yyx990803 立即看到了,但我花了一些时间才完全理解他的评论。 感谢你们的时间,如果我在途中的某个地方有误会,我很抱歉。

我在这里有一些用例,想得到一些建议和解决方法。

MainPage.vue是我的主要容器。 我在beforeCreate处调用 ajax "/init" 来获取用户信息,然后提交到 Vuex.store。
Content.vueMainPage.vue里面的孩子。 我想根据来自 Vuex.store 的用户角色在mounted阶段调用不同的 api 调用。

如果生命周期调用在 async/await 流中,它将遵循顺序
Parent beforeCreate -> Parent create -> Child beforeCreate -> Child create -> Child installed ....(如果我对组件生命周期的理解正确的话)。

但是目前我无法在Content.vue处获取用户信息,我现在该如何解决?
我想在MainPage.vue内调用“/init”api,因为它在许多页面(Vue-Router 中的容器)中使用。

在 stackoverflow 上发布的问题

谢谢

一个有价值的黑客解决方法:

{
  created: function(){
    this.waitData = asyncCall();
  },
  mounted: function(){
    this.waitData.then(function(data) { ... })
  }
}


一个可能的更“扁平”的解决方案:

{
    async created () {
        let createdResolve = null
        let createdReject = null
        this.createdPromise = new Promise(function(resolve, reject){
            createdResolve = resolve
            createdReject = reject
        })
        await asyncCall1()
        await asyncCall2()
        ...
        createdResolve(someResult)
    }
    async mounted () {
        let result = await this.createdPromise
        ...
    }
    data () {
        return {
            createdPromise: null
        }
    }
}

这还不是一回事吗?

data() {
 ...
},
async created() {
  const something = await exampleMethod();
  console.log(something);
}

正在为我工​​作(如@fifman 所述)。

@breadadams是的,当然。 _inside_ created方法的函数将被等待 - 但是createdmounted函数本身不是。

因此,在created中的任何长时间运行的进程完成之前,Vue 实例将调用created并立即调用mounted

,我糟糕的@darren-dev - 我这边的用例不同,但我现在看到了这个问题 😅 感谢您的澄清。

@breadadams 完全没问题 - 我们都在这里为自己的案例,很高兴我能澄清!

异步生命周期钩子可以在下一个主要版本中很好地实现

在我看来,默认情况下允许对生命周期方法的异步支持会鼓励不良的 UX 实践。 如何? 异步函数用于不能立即完成的请求(例如长时间运行或网络请求)。 强制 Vue 延迟创建或挂载或任何其他生命周期方法以等待您的网络请求或长时间运行的异步进程,将以明显的方式影响用户。 想象一下,一个用户来到您的站点,然后必须等待 4 秒钟,屏幕一片空白,而组件则等待用户的参差不齐的单元连接完成您的网络请求。 这不仅会对用户产生负面影响,而且您还牺牲了对情况的控制权 - 作为开发人员,您无法让用户感知加载时间更快,或者显示确定或不确定的进度指示器。 因此,通过将这种能力构建到 Vue 中,您并没有让网络变得更好; 你正在启用不良做法。

从一开始就为异步情况进行规划和设计要好得多:在createdmounted或任何地方启动异步流程,然后使您的组件具有骨架结构或最坏的情况等待 API 返回用户权限时的微调器。 更好的用户体验,你不会牺牲任何控制。 并且 Vue 不必添加代码来处理异步生命周期函数,从而使包更小。 赢赢。

@seanfisher你提出了一个有效的观点。 从架构上讲,围绕异步事件集进行设计应该由开发人员处理 - 因为这是正确描绘消息的唯一方法。

免责声明:以下内容是根据页面生成的想法编写的。 在我的论点无效的情况下,肯定存在有效的用例。


但是,不应该由您使用的框架决定开发人员的设计模式。 我的论点是,如果你不是在等待一个阶段完成——那为什么会有不同的阶段呢? 为什么有一个创建,然后安装的舞台? 如果一切基本上都是同时发生的,那么完全忽略创建的阶段是可以的。

从字面上看,我唯一一次(自 Vue 早期以来)使用 created 是当我不得不注入一些 vue 需要依赖的东西时——它与我的页面的设置或布局无关。 但是,我不得不等待(短)异步任务运行,这会更好_before_页面呈现(例如挂钩到 Firebase 的身份验证方法)。 将其创建,然后在安装之前等待它完成将完全减少对 hacky 解决方法的需求。

请记住,我的论点是 Vue 不应该告诉我我开发错误。 它应该只提供所需的功能。

但是,不应该由您使用的框架决定开发人员的设计模式。

嗯....构建框架是为了专门限制或引导或“框架”开发人员进入某些设计模式和实践。 这是他们的主要目的。 任何好的框架都会提供一个智能的 API,它准确地提供了一种清晰而明显的方式来使用框架,但它也会受到限制。

是的,一个框架提供了某些功能,但同时也将开发人员限制在某些设计实践中,这是自相矛盾的。 这正是框架内的意见可以帮助或伤害它的地方。 很难找到正确的平衡点,我认为 Vue 或者更确切地说是 Evan 和 Vue 开发团队在做出这些判断时已经做了并且做得很好。

斯科特

我永远不会争辩说一个设计良好的框架应该用相同的设计模式进行扩展,但我的论点是框架不能决定它。 你是对的,但我是说,无论框架有多好,最终开发人员仍然应该愿意做任何他们想做的事情。

但是您还没有触及使创建和安装的事件异步的 _actual_ 论点 - 您只是在我的意见中添加了您的意见(这没有错),这通常会导致巨大的出轨。

我永远不会争辩说一个设计良好的框架应该用相同的设计模式进行扩展,但我的论点是框架不能决定它。

抱歉,但这对我来说毫无意义。 请向我展示一个没有规定它应该如何扩展的框架。

我认为我所说的“Evan and Co 做出良好判断”会表明我的观点。 但是,要更清楚。 挂载和创建的生命周期钩子不需要异步工作,或者更确切地说,它们同步工作的事实有助于推理应用程序逻辑。 无论如何都需要在 UI 中考虑任何“等待”,并且仍然可以在每个钩子中运行异步代码。 我们可以争论 beforeMount 和 mount 钩子现在是必要的。 但是,例如,您可能需要在 mount 中的一两件事,例如,您还不能在 created 中访问,如已编译的渲染函数。

斯科特

如果 Vue 对异步生命周期钩子有一种或另一种观点,那不应该是猜测的问题。 无需推测 Vue 采用、提供或推荐的标准、API、指南和最佳实践何时可供所有人阅读。

在 Evan 的原始回复中,异步生命周期钩子不在标准 API 中并不是因为它一定是个坏主意,而是因为好处不足以证明实现成本是合理的。

对于让用户等待异步created或其他生命周期钩子而没有指示正在发生的事情是一种不好的做法的观点,可以认为 Vue 支持异步钩子,现在也许提供了一些可以称为“阶段模板”的东西,它也可以解决这个问题(并且很容易实现)。

对于让用户等待异步创建或其他生命周期挂钩而没有指示正在发生的事情的观点,这是一种不好的做法,

这真的是一个问题吗?

斯科特

这是我遇到的问题——也许有人可以建议我应该如何做,因为这看起来有问题。

我们的 Vue 应用程序(相当大)广泛使用 Vuex。 在 create() 生命周期中的许多 Vue 组件中,我们通过 store.dispatch() 设置了商店中的一些项目(显然)。 然而,正如我注意到的那样 - store.dispatch() 总是返回一个 promise .. 即使底层逻辑和函数不是异步的。 所以我输入了 async created() { await store.dispatch('foo/action') } 但如前所述,实际上失败了..

我也在使用 Typescript,当我不等待 / 时它会抱怨得很痛苦。然后 store.dispatch() 调用 .. 有“浮动”承诺..

那么当我们不能异步它们时,在生命周期中使用 Vuex store.dispatch() 的最佳方法是什么?

干杯!!

关于 vue 的具体意见的所有其他讨论,以及框架是否应该将意见强加于人之外,更清楚地记录这种行为可能是有益的。

我正在查看上面@fifman的“更扁平”解决方案,我不确定它是否为我解决了问题,这确保我在mounted()返回时已经加载了我的 XHR。 在那个解决方案中, created()mounted()都是异步的,所以 Vue 会调用它们中的每一个,并且它们会或多或少地立即返回,异步的东西在后台进行。 事实上,我不确定这比取消createdPromise并在created()mounted()执行所有异步工作更好,例如:

async mounted() {
  let response = await fetch(my_url)
  let data = await response.text()
  my_data_member = data
}

在任何情况下, my_data_member将在 XHR 完成后“稍后”填充,在mounted()返回其 Promise 之后很久,对吗?

您也可以尝试在根组件<div id="app"/>上使用v-if <div id="app"/>并在数据加载完成时触发它。 例如,我将它用于刷新令牌。

这是一个很酷的功能,有很多用例。 对于我的用例,我需要在组件的实际呈现之前加载特定的 API 属性。 但是,我不认为生命周期钩子中的异步是理想的解决方案。

目前每个人都在这里提到一个完美的场景,异步功能运行顺利。 但是,例如,如果用户连接缓慢或滞后,并且我们在 API 响应后等待组件安装,则整个组件生命周期都是分布式的。 我们将无法向用户显示加载信息。 或者更糟糕的是,如果 API 超时或返回错误,应用程序将挂起而不挂载。

虽然这是一个很酷的特性,但我建议它由应用程序而不是框架来处理,因为域逻辑实现对底层逻辑的了解比框架更深入,从而产生更少的副作用。

+1 此功能。
我有一个被各种组件使用的 mixin,mixin 从挂载钩子中的数据库中获取数据。
组件必须使用 mixin 获取的数据也在挂载的钩子中工作。
就像现在一样,我必须实现一个回调,以便在 mixin 完成加载数据并丢弃组件中的挂载钩子时调用。
它有效,但如果安装的钩子处理承诺会更漂亮。

@cederron纯粹是出于兴趣,为什么不能下载父组件中的数据并将其作为 prop 传递? 使用v-if在数据加载时显示加载指示器,当数据显示时,您可以显示该组件,当它被创建时,它将拥有它需要的所有数据。

避免让组件代表两种截然不同的不相关状态(“未加载数据,正在等待”和“数据已加载,您可以对其进行操作”)

如果您需要在多个地方重用逻辑,甚至可能将下载逻辑移动到 Vuex,这有点有意义,因为下载数据将在 Vuex 操作而不是组件中进行。

@ReinisV我认为我过于简化了我的案例,组件从 mixin 挂载钩子获取的数据中创建新数据,并且组件视图绑定到这个新数据。
因此,mixin 必须从数据库中获取数据 > 组件从中创建数据 > 现在显示了组件

AFAIK 这行不通:

export const MyMixin = {
    data: function () {
        return {
            dbData: null
        }
    },
   mounted:  async function () {
      this.dbData = await asyncFetchDataFromDB()
   }
}


export const MyComponent = {
    mixins: [MyMixin],
    data: function () {
        return {
            newData: null
        }
    },
   mounted:  function () {
      this.newData = handleDBData(this.dbData)
   }
}

dbData 在组件挂载钩子中将为 null。

目前,我在 mixin 获取数据时执行回调,但只是使挂载函数异步会更漂亮。

关于 vuex 不能说太多,因为我没有使用它

我真的想重申@seanfisher在这里提到的内容。 让 vue 等待标记为 async 的组件不仅会给用户带来问题,而且到处都存在使用 async/await 开始数据查找的模式。 它会强制将这些生命周期钩子中的代码显式转换为未等待的 promise,以显式避免阻塞 vue。 在某些情况下它可能很好,如果要引入一项功能,我将不得不建议同时运行两个生命周期,当前一个处理组件挂钩执行,另一个等待全局回调的执行。

但是我真的会失望地重写我的每一个生命周期钩子以避免阻塞 vue, async/await更干净,所以我们不使用承诺。 以非向后兼容的方式改变这一点会将成功坑(未等待的组件生命周期)改为失败坑。

从一开始就为异步情况进行规划和设计要好得多:在createdmounted或任何地方启动异步流程,然后使您的组件具有骨架结构或最坏的情况等待 API 返回用户权限时的微调器。 更好的用户体验,你不会牺牲任何控制。 并且 Vue 不必添加代码来处理异步生命周期函数,从而使包更小。 赢赢。

@seanfisher谢谢,这很有帮助!

为什么不改用异步组件,它只会在异步调用返回时呈现?
关于 API 的更多信息在这里
https://vuejs.org/v2/guide/components-dynamic-async.html#Async -Components

new Vue({
  components: {
    root: () => ({ // Aync component
      // The component to load (should be a Promise)
      component: new Promise(async function (resolve) {
        await FetchMyVariables()
        resolve(MyComponent) // MyComponent will be rendered only when FetchMyVariables has returned
      }),
      // A component to use while the async component is loading
      loading: { render: (h) => h('div', 'loading') }, 
    })
  },
  render: h => h('root')
})

虽然这些解决方案中的大多数看起来都不错,但我认为这是 Vue 的主要缺失部分之一,使其如此直观。 我认为 Vue 3 需要实现这一点,因为我们已经到了使用 async await 现在成为日常事情的地步。 所以请@yyx990803你,让我们在Vue 3中使用它。PLEEEEEEEASE。 整个 VUE 架构是在不考虑这些情况的情况下构建的,人们在这里发布的大部分内容都只是变通方法和骇人听闻的。 我认为钩子实际上应该尊重异步函数,并且也期望返回值然后传递给下一个钩子函数。

我将重构我的代码,因为它没有得到尊重,但丑陋的代码会从中产生,因为它是一个黑客。

在我看来,默认情况下允许对生命周期方法的异步支持会鼓励不良的 UX 实践。 如何? 异步函数用于不能立即完成的请求(例如长时间运行或网络请求)。 强制 Vue 延迟创建或挂载或任何其他生命周期方法以等待您的网络请求或长时间运行的异步进程,将以明显的方式影响用户。 想象一下,一个用户来到您的站点,然后必须等待 4 秒钟,屏幕一片空白,而组件则等待用户的参差不齐的单元连接完成您的网络请求。 这不仅会对用户产生负面影响,而且您还牺牲了对情况的控制权 - 作为开发人员,您无法让用户感知加载时间更快,或者显示确定或不确定的进度指示器。 因此,通过将这种能力构建到 Vue 中,您并没有让网络变得更好; 你正在启用不良做法。

从一开始就为异步情况进行规划和设计要好得多:在createdmounted或任何地方启动异步流程,然后使您的组件具有骨架结构或最坏的情况等待 API 返回用户权限时的微调器。 更好的用户体验,你不会牺牲任何控制。 并且 Vue 不必添加代码来处理异步生命周期函数,从而使包更小。 赢赢。

千人之一意见。 仅仅因为您无法想象延迟的组件渲染可以与积极的用户体验并存的场景并不意味着它不存在。

如果一个框架与开发人员打架,开发人员会找到另一个框架。

@robob4him

千人之一意见。 仅仅因为您无法想象延迟的组件渲染可以与积极的用户体验并存的场景并不意味着它不存在。

如果一个框架与开发人员发生冲突,开发人员会找到另一个框架

你是绝对正确的,但你在这里分享的任何内容都不是以某种方式解决问题的有效论据。 你引入了一种恐吓策略来强迫社区开发一个功能。 不是对话的建设性延续。

@wparad ,这绝对是一种恐吓策略,但它不会强迫任何人做任何事情。 提出一个功能支持坏习惯或反模式或会破坏更大的开发人员社区的论点同样是一种恐吓策略。

任何框架/语言的一半功能对开发人员都是危险的; 公共方法可以扩展,Vue 鼓励访问元素($el)等。框架提供这些东西,因为在一天结束时,开发人员需要完成工作。

此功能请求已有一年历史。 人们应该明白,原因实际上并不是因为它会导致不良做法,也不应该将延迟渲染视为不良做法。

我需要在 vue 中使用 requirejs。 不是因为我喜欢 requirejs,而是因为我想将 vue 与开源 LMS 一起使用,该 LMS 将所有模块作为 AMD 模块。 如果我可以在 beforeCreate 钩子中加载我需要的所有库,那就太好了。 目前对我来说的另一种选择是将它们加载到 vue 之外,然后将它们传递到更混乱的地方。

在我看来,默认情况下允许对生命周期方法的异步支持会鼓励不良的 UX 实践。 如何? 异步函数用于不能立即完成的请求(例如长时间运行或网络请求)。 强制 Vue 延迟创建或挂载或任何其他生命周期方法以等待您的网络请求或长时间运行的异步进程,将以明显的方式影响用户。 想象一下,一个用户来到您的站点,然后必须等待 4 秒钟,屏幕一片空白,而组件则等待用户的参差不齐的单元连接完成您的网络请求。 这不仅会对用户产生负面影响,而且您还牺牲了对情况的控制权 - 作为开发人员,您无法让用户感知加载时间更快,或者显示确定或不确定的进度指示器。 因此,通过将这种能力构建到 Vue 中,您并没有让网络变得更好; 你正在启用不良做法。

从一开始就为异步情况进行规划和设计要好得多:在createdmounted或任何地方启动异步流程,然后使您的组件具有骨架结构或最坏的情况等待 API 返回用户权限时的微调器。 更好的用户体验,你不会牺牲任何控制。 并且 Vue 不必添加代码来处理异步生命周期函数,从而使包更小。 赢赢。

您所说的就像添加v-if/v-clock/v-show功能会鼓励不良做法,因此我们通过删除这些功能来更好地防止框架出错。 然后使用一些复杂的方法来做同样的事情,以便 Vue 更小,因为不放置这 3 个指令。 1st 开发人员不傻。 第二个防呆框架反过来限制了它的可用性,因为你限制了基于明显的“傻瓜”可以做的事情。 为什么要为他们的整个网站放置v-if以通过异步操作阻止它而使整个屏幕空白?

我认为您忽略了一个事实,即大多数用例甚至可能都没有空白页。 它们被称为组件是有原因的。 我个人想要使用它的情况是屏幕上已经有一些东西在做其他事情。 例如,这可能是一个被v-if阻止的组件,并在发生变化时触发。 但是,在第一次渲染它的过程中,需要尊重async functions等作为组件启动。 我浏览了整个 Vue 文档来寻找这个,最后用一个非常不那么漂亮的解决方法来完成它,就像上面的 hackish 例子一样。

让我担心的是有人/甚至后来我维护代码。 这就像 Promise 回调地狱 vs 异步......等待。

实际上,我认为它以一种易于跟进的方式增强了框架的灵活性和可控性。 看看上面的技巧,看看我的意思。 例如,开发人员所做的一切只是为了填补简单的async mounted () { await... }语句的空白。 如果您不想使用这些功能,您只需不要将函数定义为async甚至根本不使用await

实际上,真正使用async mounted生命周期挂钩的人很可能会了解他们在做什么以及为什么要这样做,并且很可能不会做出您担心的那些不良做法。

@emahuni ,我认为没有人会不同意您分享的期望,但我认为存在细微差别,即被排除在外。 假设async mountedasync created延迟了组件的呈现。 在这种情况下,父母会怎么做? 可以:

  • 也阻止
  • 假设组件将被移除 DOM v-if直到它完成加载
  • 假设组件是隐藏的,直到完成加载
  • 在它的位置显示一个临时元素?

虽然我同意动态加载组件的期望是一致的,但我认为父组件的行为并非如此。 在这些情况下,IMO 会将子项的实现公开给父项,并强制父项弄清楚如何处理该动态组件。 取而代之的是如何加载数据以及子组件的状态应该由子组件决定。 如果它加载异步,那么它需要某种方式向 Vue 解释应该在其位置呈现(或不呈现)什么。 处理这个问题的最好方法是框架已经工作的方式,而不是引入新的复杂性。

此外,我并没有完全遵循你的论点:

您所说的就像添加 v-if/v-clock/v-show 功能会鼓励不良做法,因此我们可以通过删除这些功能来更好地对框架进行防呆

虽然在这种情况下,我们可以清楚地看到引入等待挂载或创建的异步组件导致不良做法,但尚不清楚是否会这样做。 其次,这是在回避问题,即使这些确实导致了不良做法,我们也应该选择修复它们,而不是将它们用作创建新不良做法的理由。 如果您知道v-if等造成的不良做法......我邀请您明确分享(当然在另一个问题中)这些问题是什么,而不是试图将其用作理由进行不同的讨论。

@emahuni ,我认为没有人会不同意您分享的期望,但我认为存在细微差别,即被排除在外。 假设async mountedasync created延迟了组件的呈现。 在这种情况下,父母会怎么做? 可以:

  • 也阻止

父级可以直接渲染而无需等待子级,为什么要这样做? 一旦孩子呈现,它可以继续运行并运行updated

  • 假设组件将被移除 DOM v-if直到它完成加载

我不确定我是否明白这一点......但答案是否定的,我们不认为它会被删除,它只需要在安装期间做一些事情,在这段时间内必须阻止安装。 上面有很多用例。

  • 假设组件是隐藏的,直到完成加载

这取决于开发人员,他们为什么要异步挂载或任何其他挂钩。

  • 在它的位置显示一个临时元素?

情况可能完全不是这样。 同样,这取决于开发人员他们打算实现的目标。 关键是,没有什么东西应该是直的。 例如,当设计v-if时,并不是因为他们想到了为什么有人每次都想要阻止组件的渲染,以及他们放置的内容并对其进行了防呆。 根据开发人员的设计, v-if可能会出现很多问题。 你不应该担心在那段时间屏幕上会出现什么,这不是框架需要担心的。

虽然我同意动态加载组件的期望是一致的,但我认为父组件的行为并非如此。 在这些情况下,IMO 会将子项的实现公开给父项,并强制父项弄清楚如何处理该动态组件。 取而代之的是如何加载数据以及子组件的状态应该由子组件决定。 如果它加载异步,那么它需要某种方式向 Vue 解释应该在其位置呈现(或不呈现)什么。 处理这个问题的最好方法是框架已经工作的方式,而不是引入新的复杂性。

仅供参考:您同意这需要实施,但是,它会引入您正在哭泣的这些复杂性,他认为可能会在引入重大更改而不是 Vue 3 时完成。关键是他觉得这是必要的.

此外,我并没有完全遵循你的论点:

您所说的就像添加 v-if/v-clock/v-show 功能会鼓励不良做法,因此我们可以通过删除这些功能来更好地对框架进行防呆

虽然在这种情况下,我们可以清楚地看到引入等待挂载或创建的异步组件导致不良做法,但尚不清楚是否会这样做。 其次,这是在回避问题,即使这些确实导致了不良做法,我们也应该选择修复它们,而不是将它们用作创建新不良做法的理由。 如果您知道v-if等造成的不良做法......我邀请您明确分享(当然在另一个问题中)这些问题是什么,而不是试图将其用作理由进行不同的讨论。

我只是指出这些指令作为功能示例,这些功能可能会被错误地用于阻止组件的呈现,类似于您所说的async... 。 他们没有错。 那么我们是否应该仅仅因为有人可以使用“不良做法”来创建显示一分钟空白页面的组件而删除这些指令? 实际上,您没有看到任何人这样做,因为它不会发生,除非您试图举一个非常糟糕的例子。

看,重点是,如果您还看不到任何用例,那么不要说其他人会使用它,因此不应该这样做。 不好的做法是无知的问题,无知的人可能永远不会完全使用这些功能。

有人在上面问了这个https://github.com/vuejs/vue/issues/7209#issuecomment -424349370 就我所见,没有人回答他。 到目前为止,这确实表明 Vue 在这方面落后了。 架构的这一部分是在异步还不是事物的时候设计的。 因此,更新它以满足现代架构的现代要求肯定是一个好主意。 否则,剩下的就是hackish和变通方法,需要特定的方法来做,而不是做行业中正在发生的事情。

但是,我还不确定,但乍一看新的函数式 API 似乎确实可以做到这一点。 因为它是功能性的,这意味着人们可以做一些他们客观上无法做到的事情,比如异步生命周期钩子。

看,重点是,如果您还看不到任何用例,那么不要说其他人会使用它,因此不应该这样做。 不好的做法是无知的问题,无知的人可能永远不会完全使用这些功能。

从来没有指出这一点,我指出我希望在默认情况下执行异步操作,而不会阻止组件渲染。 在mountedcreated块中执行async操作会导致组件的渲染延迟,这是不直观的。 假设是这种情况,我反而会看到复杂性来自想要当前功能的消费者如何进行。 到目前为止的争论不是所要求的,不能做的,而是所要求的应该是默认的。 您已经可以通过基于v-if="loaded"切换显示的模板来阻止组件的呈现。

现在渲染而不阻塞代码看起来像这样:
现在该代码是:

<template>
  <div>  
    <spinner v-if="!loaded">
    <div v-else>
      ....
    </div>
</template>

<script>
  async created() { 
    await this.loadData();
    this.loaded = true;
  }
</script>

并且使用阻塞进行渲染看起来完全相同,因为您实际上无法阻塞。 假设async created()实际上阻​​塞了组件。 然后我们现在有一个代码分离。 为了显示微调器,我们写:

<template>
  <div>  
    <spinner v-if="!loaded">
    <div v-else>
      ....
    </div>
</template>

<script>
  created() { 
    this.loadData().then(() => this.loaded = true);
  }
</script>

并且忽略在我们编写的屏幕上渲染组件

<template>
  <div>  
    <div>
      ....
    </div>
</template>

<script>
  async created() { 
    await this.loadData();
  }
</script>

为阻止渲染添加这种简化有什么好处可以使不阻止变得更加复杂? 我只是没有看到它。

组件的依赖处理

@yyx990803请看一下这个,它并不完美,但仍然是一个复杂的用例场景。

好的,这是一个用例,如果生命周期钩子具有异步...等待,则可以优雅地处理:
_我实际上希望在一个应用程序中做到这一点,并得到了丑陋的代码来实现这一点。 这是一个非常人为的例子 coz_

我需要组件 A等待组件 B && C 的mounted钩子在安装之前触发。 所以组件 A 的mounted必须等待它的created钩子,这触发了组件 B && C的安装,这些

A发出触发事件并侦听来自BC 的响应 _( BC在继续之前等待A的信号,然后在安装后发出事件)_ 在继续之前,很简单。 这更像是一个组件的依赖场景,没有任何额外的数据散落在别处用于状态管理。

主要托管组件,所有组件一起加载,但使用事件和异步等待正确的组件......等待。 不太关心这些孩子做什么,他们自己命令。

<template>
  <div>
     ...
     <component-A/>
     <component-B/>
     <component-C/>
     ... other conent
  </div>
</template>
<script>
  import ComponentA...
  ...
  export default {
      components: { ComponentA... }
  }
</script>

组件 A控制 B 和 C 作为依赖项的安装。 触发可以稍后在其他钩子中完成,甚至可以通过相同组件的 UX 事件完成。 以下只是为了炫耀这里的想法。

<template>
  ...
</template>
<script>
  export default {
      async created() {
          // ...do something here before mounting thrusters, even await it
         this.root.$emit('mount-thrusters');
         await Promise.all([
            this.wasMounted('thruster-1-mounted'), 
            this.wasMounted('thruster-2-mounted')
         ]); 
      },
      mounted() {
        // will only run after components B and C have mounted
        ...
      },

     methods: {
       wasMounted(compEvent) {
          return new Promise( (resolve)=>this.root.$once(compEvent, ()=>resolve()));
       }
    }
  }
</script>

B组份

<template>
  ...
</template>
<script>
  export default {
      async created() {
          // ...do something here, even await it, but happens at the same time as all components
         await new Promise( (resolve)=>this.root.$once('mount-thrusters', ()=>resolve()));
      },
     mounted() {
       this.root.$emit('thruster-1-mounted');
    }
  }
</script>

组分 C

<template>
  ...
</template>
<script>
  export default {
      async created() {
          // ...do something here, even await it, but happens at the same time as all components
         await new Promise( (resolve)=>this.root.$once('mount-thrusters', ()=>resolve()));
      },
     mounted() {
       this.root.$emit('thruster-2-mounted');
    }
  }
</script>

上面的代码可以通过 mixins 进一步简化,看到有很多重复的代码片段,我只是想让它清楚。 wasMounted 方法可以封装在 mixin 中,并在所有 3 个组件中使用。

在这里,我们可以清楚地看到每个组件的期望,而无需在其他地方散布任何其他黑客或路由器代码来控制相同的事情。 如果没有此功能,实际执行此操作非常令人困惑,相信我,我已经在应用程序中完成了此操作。

这显然摆脱了不必要的复杂和不可维护的代码。

现在在一个大型应用程序中想象一下,它有 32 个表现不同的推进器组件。 你只需要记住大约 3 个点,如果你加入 mixin,它们甚至可以减少到 2 个。

让事物保持新鲜

因为这不仅限于安装和创建,而且实际上应该与所有其他生命周期挂钩一起使用。 想象一下,如果这是一个闪亮的新beforeActivate / beforeUpdate钩子。 我们可以让组件等待_activation/update_ 并且只在每次组件 _activated/updated_ 完成刷新时_activate/update_; 确保事物保持新鲜。

一旦实施,这个清单是无穷无尽的。

看,重点是,如果您还看不到任何用例,那么不要说其他人会使用它,因此不应该这样做。 不好的做法是无知的问题,无知的人可能永远不会完全使用这些功能。

从来没有指出这一点,我指出我希望在默认情况下执行异步操作,而不会阻止组件渲染。 在mountedcreated块中执行async操作会导致组件的渲染延迟,这是不直观的。 假设是这种情况,我反而会看到复杂性来自想要当前功能的消费者如何进行。 到目前为止的争论不是所要求的,不能做的,而是所要求的应该是默认的。 您已经可以通过基于v-if="loaded"切换显示的模板来阻止组件的呈现。

现在渲染而不阻塞代码看起来像这样:
现在该代码是:

<template>
  <div>  
    <spinner v-if="!loaded">
    <div v-else>
      ....
    </div>
</template>

<script>
  async created() { 
    await this.loadData();
    this.loaded = true;
  }
</script>

并且使用阻塞进行渲染看起来完全相同,因为您实际上无法阻塞。 假设async created()实际上阻​​塞了组件。 然后我们现在有一个代码分离。 为了显示微调器,我们写:

<template>
  <div>  
    <spinner v-if="!loaded">
    <div v-else>
      ....
    </div>
</template>

<script>
  created() { 
    this.loadData().then(() => this.loaded = true);
  }
</script>

并且忽略在我们编写的屏幕上渲染组件

<template>
  <div>  
    <div>
      ....
    </div>
</template>

<script>
  async created() { 
    await this.loadData();
  }
</script>

为阻止渲染添加这种简化有什么好处可以使不阻止变得更加复杂? 我只是没有看到它。

这是 Vue 101 训练营,没有什么新东西......例如,不足以涵盖上述情况。 这里的想法是降低实际使用 Vue 的用户区的复杂性,并更容易推理代码。

这里的渲染不是问题,重要的是渲染之前发生的事情。 我们希望在继续或渲染组件之前可以自由地做事情。 无论如何,这也与阻塞渲染完全无关。 Vue 支持很多生命周期钩子,如果有一种方法可以针对其他钩子处理异步代码,那么这些钩子实际上很有用。 这个想法是让 Vue 在进入下一个钩子函数之前在内部尊重异步。

我真的对此感到困惑,而不是将 B 和 C 耦合到 A,我会将代码移动到“加载 A”到 A.js 中,然后让 A.js 更新“B.data”和“C.data” . 这样,如果 A.data 由于任何原因发生变化,其他组件会自动重新渲染而不是尝试委托
控制从一个组件到另一个组件。 即使在共享的情况下,我仍然会分离数据以从组件 A呈现fetchDatahasInitialized等方法,后者是承诺,前者解决后者。
将组件直接耦合在一起会创建意外的依赖树,从而阻止组件可重用并允许 vue 正确重新渲染它们。

或者,我什emit会将

  <template>
    <a-component @rendered="showBandC = true" />
    <b-component v-if="showBandC" />
    <c-component v-if="showBandC" />
  </template>

在这种情况下,我们实际上需要在 B 和 C 渲染之前渲染 A 有什么关系。 如果created()方法中有内容,则没有什么可以阻止它通过 javascript 类或使用store模块填充商店。 但是明确的用例会更有帮助,即无法捕获的用户故事的用户体验是什么?

这个想法是让 Vue 在进入下一个钩子函数之前在内部尊重异步。

当然这部分是有道理的,但我不确定为什么这个例子需要令人费解,为什么不说这样的用户故事:

我的组件同时具有async beforeMountasync mounted ,但mounted中的代码在beforeMount的代码完成之前触发。 我们如何在beforeMount完成之前阻止mounted触发?

这是原始请求的内容,因此我认为第二个响应中提出的问题仍然相关: https :

暂时结束,但请随时跟进更具体的推理/用例/含义。

实际上是否有一个有效的用例需要阻止先前执行的生命周期钩子,或者生命周期钩子同步是否正确。 到目前为止,讨论本质上是哲学性的(正如好的架构讨论倾向于做的那样),但真正的问题是是否有真正的充分理由这样做。 我毫不怀疑框架等待异步代码是合理的。 我在没有这样做或传递回调(或传递回调但未将回调传递给回调)的 N 个其他库中遇到了确切的问题。 然而,有一个异步生命周期钩子实际上是合理的,或者是尝试做“不应该做”的事情的结果的原因吗?

即当你试图个究竟unmount尚未完成作为一个组件created ,哇,那将是坏要等待仍。 或者destroying尚未完成的mounted ,我不羡慕该功能的实现者。

我真的对此感到困惑,而不是将 B 和 C 耦合到 A,我会将代码移动到“加载 A”到 A.js 中,然后让 A.js 更新“B.data”和“C.data” . 这样,如果 A.data 由于任何原因发生变化,其他组件会自动重新渲染而不是尝试委托

这增加了复杂性,不好的做法。 试着把它完整地写在这里,让我们看看你的意思,但对我来说,你只是增加了很多复杂性。

控制从一个组件到另一个组件。 即使在共享的情况下,我仍然会分离数据以从组件 A呈现fetchDatahasInitialized等方法,后者是承诺,前者解决后者。

这现在耦合组件太多了。 我们希望这些在没有其他人的情况下工作,除了 A。

将组件直接耦合在一起会创建意外的依赖树,从而阻止组件可重用并允许 vue 正确重新渲染它们。

事实上,您忽略了这一点,它们与扩展松散耦合,每个都可以使用和维护而不会影响另一个。 事实上,你可以在任何地方多次删除它们中的任何一个,而无需在<component-x />之外的父级中编写更多代码,没有v-if , vuex 来管理其状态或其他任何东西。

我将它们 A 耦合到 B 和 C 只是为了演示,我可以很好地拆分它,或者只是计算有多少组件响应,然后在达到某个预期数量(在这种情况下为 2)时继续,例如:

 this.$root.$emit('thruster-mounted') // in B and C
// instead of 
 this.$root.$emit('thruster-1-mounted') // for B and 2 for C

// then count the responses and resolve once they are >= 2 in component A

无论如何,这就是我说组件依赖处理的原因,这是期望的和预期的,但要以尽可能少的复杂性来完成,因为它越复杂就越令人困惑。

或者,我什emit会将

我知道你会这么说,但这是不受欢迎的。 这些组件不应该被其他任何东西控制,因此我强调主要组件不太关心它的孩子做什么,他们应该保持独立。 我希望能够将它们放置在应用程序中的任何位置,并且仍然可以使同样的事情发挥作用。 当你按照你说的做的那一刻,观察一切是如何分崩离析的。 但是我可以轻松地将组件放在 DOM 树中的任何位置,甚至不会退缩,一切都会正常工作。 我什至可以有多个副本,它仍然可以工作。 这一切都要归功于生命周期中的async ... await

  <template>
    <a-component @rendered="showBandC = true" />
    <b-component v-if="showBandC" />
    <c-component v-if="showBandC" />
  </template>

在这种情况下,我们实际上需要在 B 和 C 渲染之前渲染 A 有什么关系。

我们希望每个组件在安装之前都做独特的工作。 但是所有组件只有在其他所有组件都完成了这项工作时才会呈现。 这就是这里的故事。
所以祝状态管理好运, v-if只是为了控制它,更不用说它可能会在vuex store 中产生的实际数据。 无论在何处使用该组件,您最终都会编写大量重复代码。 假设您有另一个仅托管 A 和 C 且配置不同的组件? 您必须编写那些v-if的 vuex 状态管理才能使其工作。 你看到问题了吗? 让我举例说明:

  // component Z totally different from foo, so we can't make this a component for reuse
  <template>
    <a-component @rendered="showBandC = true" />
    <c-component v-if="showBandC" />
  </template>
 ...
computed: {
showBandB() { ...vuex ...,
showBandC() { ...vuex ...,
}
  // component Foo totally unique, probably has other things it does
  <template>
    <b-component v-if="showBandC" />
    <c-component v-if="showBandC" />
  </template>
 ...
computed: {
// ok you could mixin this, fair, but complex nevertheless
showBandB() { ...vuex ...,
showBandC() { ...vuex ...,
}

.....等等

而不是只使用组件而不像这样在父组件中做任何事情:

  // component Z
  <template>
    <a-component />
    <b-component />
  </template>
  // component Foo
  <template>
    <b-component  />
    <c-component />
  </template>

...等等

如果created()方法中有内容,则没有什么可以阻止它通过 javascript 类或使用store模块填充商店。 但是明确的用例会更有帮助,即无法捕获的用户故事的用户体验是什么?

还记得我说的到处都是状态管理吗? 我们不希望因为管理这些组件意味着我们将在其他地方管理很多事情,这非常复杂,而不是我刚才所做的。 此外,它不会做我想要做的事情; 仅当每个组件完成它应该做的事情时才安装,而且复杂度很低。

这个想法是让 Vue 在进入下一个钩子函数之前在内部尊重异步。

当然这部分是有道理的,但我不确定为什么这个例子需要令人费解,为什么不说这样的用户故事:

我的组件同时具有async beforeMountasync mounted ,但mounted中的代码在beforeMount的代码完成之前触发。 我们如何在beforeMount完成之前阻止mounted触发?

关键是我们希望事情在这些组件中的任何一个渲染之前发生并且可以使用而无需在组件本身之外进行太多输入。 这是我注意到的一件实际事情,如果这是我们正在制作的应用程序,我本可以做得更好。 我最终编写了包含大量 mixin、vuex 和重度耦合父项的代码(使用 mixins),因为缺少这些组件而使用了组件。 这不是什么复杂的故事,我实际上是在用简单的方式告诉你发生了什么。 我们不得不重新思考 UI 应该如何设计和维护。 它在视觉上变得不那么有趣,并且在代码方面变得更加复杂。

你有没有看到你在这个简单的设置中引入了多少复杂的东西,这些东西刚刚被生命周期中的那个小 async ... await 特性解决了?

@wparad

即当您尝试卸载尚未完成创建的组件时会发生什么,哇,等待它会很糟糕。 或者销毁一个没有完成安装的,我不羡慕那个功能的实现者。

这是哪里:

...需要对架构进行根本性的重新思考/重写才能实现,并且可能会破坏许多依赖于生命周期钩子同步性质的逻辑......

...我相信你提到的部分。 我们需要一种方法来处理这些边缘情况,并且可能会引入更多。 换句话说,我们的意图必须在不使应用程序崩溃的情况下工作。

在所有这些情况下,我会使用超时来检查或中止等待,就像大多数可能失败的异步操作一样。

但是,如果您仔细查看我提供的示例,就会明白我的意思。 这本可以解决很多问题,而无需在任何地方编写太多代码。 事实上,如果可能的话,我们的应用程序本可以通过一个更小、经过碎片整理且易于掌握的代码库来做得更好。
这类功能的问题在于,您只有在遇到障碍时才会看到它们的重要性。 我第一次来到这个页面时,我们想到了很多关于我刚刚给出的例子的解决方法。 我不是来寻求帮助的,而是提出功能请求,因为我们注意到它在 Vue 中不可用。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

wufeng87 picture wufeng87  ·  3评论

lmnsg picture lmnsg  ·  3评论

bdedardel picture bdedardel  ·  3评论

bfis picture bfis  ·  3评论

aviggngyv picture aviggngyv  ·  3评论