摘要:看这篇之前,如果没有看过之前的文章,移步拉到文章末尾查看之前的文章。而该组件实例的父实例却并不固定,所以我们将这些在使用时才能确定的参数在组件实例化的时候传入。系列文章地址优化优化总结
看这篇之前,如果没有看过之前的文章,移步拉到文章末尾查看之前的文章。
前言在上一步,我们实现 extend 方法,用于扩展 Vue 类,而我们知道子组件需要通过 extend 方法来实现,我们从测试例子来入手,看看这一步我们需要实现什么:
let test = new Vue({ data() { return { dataTest: { subTest: 1 } } }, components: { sub: { props: { propsStaticTest: { default: "propsStaticTestDefault" }, propsDynamicTest: { default: "propsDynamicTestDefault" } }, watch: { "propsDynamicTest"(newValue, oldValue) { console.log("propsDynamicTest newValue = " + newValue) } } } } })
从例子可知: sub 是 test 的子组件,同时 test 组件向 sub 组件传递了 propsStaticTest/propsDynamicTest 两个 props 。
所以我们这一步要做两件事
实现子组件生成树结构
实现 props ,从例子上可以看出需要实现静态和动态两种 prop
VUE 中组件的生成虽然在之前的步骤中,我们一直没有涉及到模板,仅仅是把页面的渲染抽象成一个函数,主要是为了把 MVVM 中的数据绑定过程给解释清楚,但是父子组件的实现却必须要通过模板来联系,所以我们这里简单的介绍下 Vue 中由模板到生成页面渲染函数的过程
得到模板(DOM 字符串)或是 render 函数
分析模板,得到 HTML 语法树(AST),生成 render 函数。如果直接给的是 render 则没有这个步骤
由 render 函数生成 VNode 这就是虚拟树了
将 Vnode 作为参数传入一个函数中,就能得到 html 渲染函数
ok 看起来和组件好像没有什么关系,我们分析下组件写法
由上面这个标签我们可以得到什么?
这是一个子组件,组件名:sub
传递了一个静态的 prop :propsStaticTest
传递了一个动态的 prop :propsDynamicTest
静态说明这个属性不会发生变化,动态会,最明显的区别就是:动态属性有 :/v-bind 修饰
结合上面的第2个步骤,会分析出一些东西。仅仅针对 props ,假设模板解析引擎会解析出下面这样一个结构
let propsOption = [{ key: "propsStaticTest", value: "propsStaticValue", isDynamic: false }, { key: "propsDynamicTest", value: "dataTest.subTest", isDynamic: true }]
注: 这里仅仅是我的假设,方便理解,在 Vue 中的模板解析出来的内容要比这个复杂。
ok 有了上面的铺垫我们来实现父子组件和 props
父子组件实例初始化的实例我们需要做的仅仅就是保存组件之间的关系就行,ok 我们来实现它
class Vue extends Event { ··· _init(options) { let vm = this ··· // 获取父节点 let parent = vm.$options.parent // 将该节点放到父节点的 $children 列表中 if (parent) { parent.$children.push(vm) } // 设置父节点和根节点 vm.$parent = parent vm.$root = parent ? parent.$root : vm // 初始化子节点列表 vm.$children = [] } }
我们需要做的仅仅就是给传入 options 设置 parent ,就能明确组件之间的关系。
接着我们模拟一下当模板编译的时候碰到 的情况,具体的来说就是会执行以下代码:
let testSubClass = Vue.extend(test.$options.components.sub) let testSub = new testSubClass({parent: test}) console.log(testSub.$parent === test) // true
ok 现在我们先不想模板编译具体是如何进行的,从这两行代码中,我们可以看出我们先使用了 extend 扩展了 Vue 实例,生成一个子类(testSubClass),接着我们实例化该类,传入参数确定父实例。
想象下一,我们为什么要分两步把参数传入。
我们知道当我们写好子组件的配置时,子组件的内部状态就已经确定了,所以我们可以根据这些固定的配置去扩展 Vue 类方便我们调用(使用的时候 new 一下就可以)。
而该组件实例的父实例却并不固定,所以我们将这些在使用时才能确定的参数在组件实例化的时候传入。
接着我们来想象一下,如果子组件(sub)里面还有子组件(sub-sub)会怎么样?
使用 extend 扩展 Vue 类
确定父实例,new 的时候传入,而这个 parent 就是 sub
这样调用过多次之后,一颗 Vue 的实例树就生成了,每一个节点都保留着父实例的引用,子组件列表还有根实例。
希望你的脑子里已经长出了这颗树~
ok 接下来我们来实现 props
props希望你还记得下面这几行代码:
let propsOption = [{ key: "propsStaticTest", value: "propsStaticValue", isDynamic: false }, { key: "propsDynamicTest", value: "dataTest.subTest", isDynamic: true }]
这个是我们模拟模板编译时关于 props 的部分产出,具体的来说就是键值对,以及是否有 :/v-bind 修饰,而我们知道在 Vue 中这个修饰符是表示是否是动态绑定,所以我在这里使用 isDynamic 来标志。
首先我们来获取属性的数据,由于动态绑定的 props 是取值路径,所以我们得去父对象下获取值。
let propsData = {} propsOption.forEach(item => { if (item.isDynamic) { // eg: "dataTest.subTest" => test.dataTest.subTest 将字符串转换成取值 propsData[item.key] = item.value.split(".").reduce((obj, name) => obj[name], test) } else { propsData[item.key] = item.value } }) console.log(propsData) // { propsStaticTest: "propsStaticValue", propsDynamicTest: 1 }
ok 我们拿到中属性对应的值,接着把 propsData 给传进去
let testSub = new testSubClass({parent: test, propsData})
接着我们在 _init 方法中来处理 props
_init(options) { ··· let props = vm._props = {} let propsData = vm.$options.propsData for (let key in vm.$options.props) { let value = propsData[key] // 如果没有传值,使用默认值 if (!value) { value = vm.$options.props[key].default } props[key] = value } observe(props) for (let key in props) { proxy(vm, "_props", key) } ··· }
porps 的处理和 data 类似,需要变成可监听结构,代理到 this 对象下,无非 data 是从传入的函数取值,而 props 从传入的 propsData 中取值。
ok 直到现在为止,看起来都很美好,但是部分 props 是动态的,父组件相应值的变化是需要同步到子组件中的,但目前我们还没有实现父组件和子组件的联系,仅仅是把值给取出放在子组件内而已。
其实一看到监听变化就理所当然的想到 Watcher,ok 我们用 Watcher 来实现它:
propsOption.forEach(item => { if (item.isDynamic) { new Watcher({}, () => { return item.value.split(".").reduce((obj, name) => obj[name], test) }, (newValue, oldValue) => { testSub[item.key] = newValue }) } })
ok 最后一步完成,完整的测试代码
本来还想实现下 provide/inject 但篇幅有点大了,下一步实现,也做个总结。
系列文章地址VUE - MVVM - part1 - defineProperty
VUE - MVVM - part2 - Dep
VUE - MVVM - part3 - Watcher
VUE - MVVM - part4 - 优化Watcher
VUE - MVVM - part5 - Observe
VUE - MVVM - part6 - Array
VUE - MVVM - part7 - Event
VUE - MVVM - part8 - 优化Event
VUE - MVVM - part9 - Vue
VUE - MVVM - part10 - Computed
VUE - MVVM - part11 - Extend
VUE - MVVM - part12 - props
VUE - MVVM - part13 - inject & 总结
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/94884.html
摘要:通过装作这些变化,我们实现了从而到达了数据变化触发函数的过程。于此同时,我们还实现了来扩展这个可响应的结构,让这个对象拥有了触发和响应事件的能力。最后,根据我们的实现,这是最终的产出,一个框架,了解一下系列文章地址优化优化总结 看这篇之前,如果没有看过之前的文章,移步拉到文章末尾查看之前的文章。 provide / inject 在上一步我们实现了,父子组件,和 props 一样 pr...
摘要:在中关于如何实现在网上可以搜出不少,在看了部分源码后,梳理一下内容。换个说法,当我们取值的时候,函数自动帮我们添加了针对当前值的依赖,当这个值发生变化的时候,处理了这些依赖,比如说节点的变化。 在 VUE 中关于如何实现在网上可以搜出不少,在看了部分源码后,梳理一下内容。 首先,我们需要了解一下 js 中的一个 API :Object.defineProperty(obj, prop,...
摘要:所以方法,是对默认进行扩展,从而实现扩展。这里我用了这个库提供的合并方法,用来合并两个对象,并不会修改原对象的内容。测试符合我们的预期,方法也就实现了,下一步,实现父子组件。系列文章地址优化优化总结 看这篇之前,如果没有看过之前的文章,移步拉到文章末尾查看之前的文章。 组件的扩展 在 Vue 中有 extend 方法可以扩展 Vue 的实例,在上一步中,有一些实现是必须要通过子父组件才...
摘要:了解之后我们来实现它,同样的为了方便理解我写成了一个类这里的一般是的实例将属性代理到实例下的构造函数我们实现了代理属性和更新计算属性的值,同时依赖没变化时,也是不会触发的更新,解决了以上的个问题。 看这篇之前,如果没有看过之前的文章,移步拉到文章末尾查看之前的文章。 回顾 先捋一下,之前我们实现的 Vue 类,主要有一下的功能: 属性和方法的代理 proxy 监听属性 watche...
摘要:关于中的的实现,差不多也就这样了,当然这仅仅是基础的实现,而且视图层层渲染抽象成一个函数。不同于中的实现,这里少了很多各种标记和应用标记的过程。 看这篇之前,如果没有看过之前的文章,可拉到文章末尾查看之前的文章。 回顾 首先我们思考一下截止当前,我们都做了什么 通过 defineReactive 这个函数,实现了对于数据取值和设置的监听 通过 Dep 类,实现了依赖的管理 通过 Wa...
阅读 4132·2021-11-22 13:52
阅读 2499·2021-11-22 13:52
阅读 3671·2021-11-19 09:59
阅读 1173·2021-11-17 09:33
阅读 2433·2019-08-30 10:53
阅读 1188·2019-08-29 17:28
阅读 1293·2019-08-29 17:03
阅读 3086·2019-08-26 11:31