vue父组件异步获取数据传值给子组件

vue父组件异步获取数据传值给子组件,第1张

原因是父组件请求数据时,子组件已经渲染完毕,这个时候传了一个空数组给子组件子,子组件用到父组件传过来的值而出现的报错

解决办法:

1 给子组件添加一个渲染条件,有值的时候在渲染

2 使用watch监听,数据变化时动态更新数据

3 把数据存储到vuex 读取vuex里面的数据

4 通过ref 直接给组件赋值,不过这已经是在 *** 作dom了(不建议使用)

前面讲过通过props子组件很容易就可以获取到父组件的数据,但是父组件怎么获取子组件的数据呢?通过查资料,发现可以通过自定义事件来实现父组件与子组件之间的通信。

首先,得创建一个子组件模板文件,子组件在父组件中用的时候,总得有个触发动作可以获取到子组件的数据吧,所以,加个按钮,触发个动作。如下所示:

Appvue

再有一棵树形结构的 JavaScript 对象后,我们现在需要做的就是将这棵树跟真实的 Dom 树形成映射关系,首先简单回顾之前遇到的 mountComponent 方法

我们已经执行完了 vm_render 方法拿到了 VNode ,现在将它作为参数传给 vm_update 方法并执行。 vm_update 这个方法的作用就是就是将 VNode 转为真实的 Dom ,不过它有两个执行的时机:

我们现在先来看下 vm_update 方法的定义:

这里的 vm$el 是之前在 mountComponent 方法内就挂载的,一个真实 Dom 元素。首次渲染会传入 vm$el 以及得到的 VNode ,所以看下 vm__patch__ 定义:

__patch__ 是 createPatchFunction 方法内部返回的一个方法,它接受一个对象:

nodeOps 属性:封装了 *** 作原生 Dom 的一些方法的集合,如创建、插入、移除这些,再使用到的地方再详解。

modules 属性:创建真实 Dom 也需要生成它的如 class / attrs / style 等属性。 modules 是一个数组集合,数组的每一项都是这些属性对应的钩子方法,这些属性的创建、更新、销毁等都有对应钩子方法,当某一时刻需要做某件事,执行对应的钩子即可。比如它们都有 create 这个钩子方法,如将这些 create 钩子收集到一个数组内,需要在真实 Dom 上创建这些属性时,依次执行数组的每一项,也就是依次创建了它们。

这里大家记住一句话即可,无论 VNode 是什么类型的节点,只有三种类型的节点会被创建并插入到的 Dom 中:元素节点、注释节点、和文本节点。

我们接着来看下 createPatchFunction 它究竟返回一个什么样的方法:

首次渲染时没有 oldVnode , oldVnode 就是 $el ,一个真实的 dom ,经过 emptyNodeAt(oldVnode) 方法包装:

再将传入的 $el 属性转为了 VNode 格式之后,我们继续:

createElm 方法开始生成真实的 Dom , VNode 生成真实的 Dom 的方式还是分为元素节点和组件两种方式,所以我们使用上一章生成的 VNode 分别说明。

大家可以先看下这个流程图有一个印象即可,接下来再看具体实现时相信思路会清晰很多:

开始创建 Dom ,我们来看下它的定义:

依次判断是否是元素节点、注释节点、文本节点,分别创建它们然后插入到父节点里面,这里主要介绍创建元素节点,另外两个并没有复杂的逻辑。我们来看下 createChild 方法定义:

开始创建子节点,遍历 VNode 的每一项,每一项还是使用之前的 createElm 方法创建 Dom 。如果某一项又是数组,继续调用 createChild 创建某一项的子节点;如果某一项不是数组,创建文本节点并将它添加到父节点内。像这样使用递归的形式将嵌套的 VNode 全部创建为真实的 Dom 。

再看一遍流程图,相信大家疑惑已经减少很多:

首先还是看张简易流程图,留个印象即可,方便理清之后的逻辑顺序:

执行 createComponent 方法,如果是元素节点不会返回任何东西,所以是 undefined ,会继续走接下来的创建元素节点的逻辑。现在是组件,我们看下 createComponent 的实现:

首先会将组件的 vnodedata 赋值给 i ,是否有这个属性就能判断是否是组件 vnode 。之后的 if(isDef(i = ihook) && isDef(i = iinit)) 集判断和赋值为一体, if 内的 i(vnode) 就是执行的组件 init(vnode) 方法。这个时候我们来看下组件的 init 钩子方法做了什么:

activeInstance 是一个全局的变量,再 update 方法内赋值为当前实例,再当前实例做 __patch__ 的过程中作为子组件的父实例传入,在子组件的 initLifecycle 时构建组件关系。将 createComponentInstanceForVnode 执行的结果赋值给了 vnodecomponentInstance ,所以看下它的返回的结果是什么:

再组件的 init 方法内首先执行 createComponentInstanceForVnode 方法,这个方法的内部就会将子组件的构造函数实例化,因为子组件的构造函数继承了基类 Vue 的所有能力,这个时候相当于执行 new Vue({}) ,接下来又会执行 _init 方法进行一系列的子组件的初始化逻辑,我们回到 _init 方法内,因为它们之间还是有些不同的地方:

前面都还执行的好好的,最后却因为没有 el 属性,所以没有挂载, createComponentInstanceForVnode 方法执行完毕。这个时候我们回到组件的 init 方法,补全剩下的逻辑:

我们在 init 方法内手动挂载这个组件,接着又会执行组件的 _render() 方法得到组件内元素节点 VNode ,然后执行 vm_update() ,执行组件的 __patch__ 方法,因为 $mount 方法传入的是 undefined , oldVnode 也是 undefined ,会执行 __patch__ 内的这段逻辑:

这次执行 createElm 时没有传入第三个参数父节点的,那组件创建好的 Dom 放哪生效了?没有父节点也要生成 Dom 不是,这个时候执行的是组件的 __patch__ ,所以参数 vnode 就是组件内元素节点的 vnode 了:

很明显这个时候不是组件了,即使是组件也没关系,大不了还是执行一遍 createComponent 创建组件的逻辑,因为总会有组件是由元素节点组成的。这个时候我们执行一遍创建元素节点的逻辑,因为没有第三个参数父节点,所以组件的 Dom 虽然创建好了,并不会在这里插入。请注意这个时候组件的 init 已经完成,但是组件的 createComponent 方法并没有完成,我们补全它的逻辑:

无论是嵌套多么深的组件,遇到组件的后就执行 init ,在 init 的 __patch__ 过程中又遇到嵌套组件,那就再执行嵌套组件的 init ,嵌套组件完成 __patch__ 后将真实的 Dom 插入到它的父节点内,接着执行完外层组件的 __patch__ 又插入到它的父节点内,最后插入到 body 内,完成嵌套组件的创建过程,总之还是一个由里及外的过程。

再回过头来看这张图,相信会好理解很多~

接下来会将 updateComponent 传入到一个 Watcher 的类中,这个类是干嘛的,我们下一章再说明,接下来执行 mounted 钩子方法。至此 new Vue 的整个流程就全部走完了。我们回顾下从 new Vue 开始它的执行顺序:

最后我们还是以一道 vue 可能会被问到的面试题作为本章的结束吧~

顺手点个赞或关注呗,找起来也方便~

你可能会用的上的一个vue功能组件库,持续完善中

$event:当前触发的是什么事件

$eventtarget:触发事件的元素对象(不一定是绑定事件的对象,会因为事件冒泡变化)

$eventcurrentTarget:绑定事件的元素对象

参考:

vue中关于$event的通俗理解

Vue 点击获得父元素,子元素,兄弟元素(DOM *** 作)

ref可以绑定dom节点或字组件,用于获取子组件的方法和属性。但是只有组件 完成渲染 时,才可以获取得到,且$refs也不是响应式的!

响应式处理可以包括以下几种方法:也就是说,在子组件完成渲染以后,动态修改的data或method,在父组件都可以实时获取。

(1)使用nexttick();子组件 同步 更改数据可反映到父组件上,nexttick属于微任务,也就是说,在本轮事件循环完成之前,可以执行异步 *** 作,从而保证实时性。

(2)如果子组件数据更新是异步,比如说从接口请求回来的这种,使用nexttick,甚至settimeout(()=>{},0) 都无法读取到已经修改的data,因为在消息队列里面,异步任务作为宏任务始终排在队尾。常发生的情况是,接口请求的数据还没到,父组件中已经读取了refs[componets]xxx的数据,这会导致这个数据打印出来是undefined。

解决办法是:

>>>settimeout(100ms)的等待,这种方法虽然可以解决,但是非常不好,原因是你无法控制接口要多少秒才能到达响应结果

打印结果:

由此可见 settimeout 0 会在接口响应前执行。

>>> 使用回调的方式。接口响应完,在then里,通过$emit触发父组件获取refs的方法。这种回调必然可以保证数据已经set到,再次手动获取,即可得到响应值。

(3)以上分别用于解决子组件同步和异步的问题,但如果子组件使用v-if(资料发现v-for同样会有这种问题),子组件未被渲染,同样会出现$refs无法读取的问题。暴力解决法是,v-if换成v-show。区别在于:v-show不会发生重拍,只是display:none。这种方式虽然可以保证 $refs获取到子组件的数据,但在某些特定的业务场景下(比如子组件请求必须在父组件完成某 *** 作的时候进行),会导致一些逻辑错误。因此合理的解决办法是,用(2)中回调的方法。

其他解决办法待更新

在Vue3中,父组件可通过创建一个ref(null),然后将赋值的元素写在当前子组件上即可,在需要的时候,通过定义的响应式变量即可获取,获取后即可取得当前子组件内部dom以及当前子组件内部变量方法等,并且直接使用子组件内部方法。但是有时候获取的时候返回的没有什么信息只有一个 {_v_skin:true} 这个信息,这条信息表示数据无法响应。

此时childDomvalue的值为{_v_skin:true}无法获取子组件内部信息

此时父组件即可获取子组件内部变量与方法与当前dom等信息

在 vue 中用 document 获取 dom 节点进行节点样式更改的时候有可能会出现 'style' is not definde的错误,

这时候可以在 mounted 里用 $refs 来获取样式,并进行更改:

<template>

<div style="display: block;" ref="abc">

<!-- -->

</div>

</template>

<script>

export default {

mounted () {

consolelog(this$refsabcstylecssText)

}

}

</script>

结果是 display: block;

如果我们给一个div设定全屏背景图,就需要获取屏幕高度进行赋值:

<template>

<div ref="nana">

<!-- -->

</div>

</template>

<script>export default {

mounted () {

let w = windowinnerWidth || documentdocumentElementclientWidth || documentbodyclientWidth;

let h = windowinnerHeight || documentdocumentElementclientHeight || documentbodyclientHeight;

this$refsnanastyleheight = h +'px';

}

}

以上就是关于vue父组件异步获取数据传值给子组件全部的内容,包括:vue父组件异步获取数据传值给子组件、Vue入门之自定义事件$emit-父组件获取子组件的数据、Vue原理解析(五):彻底搞懂虚拟Dom到真实Dom的生成过程等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/9560168.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-29
下一篇 2023-04-29

发表评论

登录后才能评论

评论列表(0条)

保存