vue3源码剖析之简单实现方法
作者:ClyingDeng
前言
最近,由于我的第一个vue3 + ts的正式项目,已经进入验收阶段。听你们老说vue3、vue3的,我就想着去看看vue3到底和vue2有啥区别。🤷🏻♀️🤷🏻♀️🤷🏻♀️
文章主要阐述vue3的API用法,以及简单地实现一个vue3。带大家感受一下vue3与之前vue2的区别。以及简单带大家揭秘源码中vue3初始化的一个流程。
🍹准备工作
要想看看vue3内部的源码是咋搞得,首先跟vue2源码剖析一样,先从github上下一份源码到本地。
接着就是安装依赖:
yarn --ignore-scripts
在你执行命令的时候可能会遇到node版本过低的错误:
解决此问题可以升级自己的node版本,或者忽略该engine。
如果选择忽略的话可以设置
yarn config set --ignore-engines true
然后执行依赖安装。
依赖安装完成后,编译打包生成vuejs文件:
yarn dev
需要调试的话,可以在packages\vue\examples文件下建立测试文件。引用打包后的vue文件,可以应用packages\vue\dist\vue.global.js。
🍲vue3用法
vue3的特性我就不多阐述了,就vue3的用法而言,更倾向于函数式编程,通过对外暴露Vue中的createApp()API,以工厂函数的方式创建了一个应用程序实例。相比较vue2的new Vue实例,更加贴切。
在源码文件中,我们新建一个init.html文件。
<script src="../dist/vue.global.js"></script> <body> <div id="app">{{name}}</div> <script> const { createApp } = Vue const app1 = createApp({ data() { return { name: 'clying' } }, setup() { return { name: 'deng' } } }).mount('#app') </script> </body>
根据上例,我们可以看出vue3是即支持Composition API,也支持Options API,两者可以同时使用。
但是,我们可以看到在data和setup中,我同时使用了一个name变量进行赋值。那么页面中会展示哪一个呢?
3!2!1!上答案:
可以明显看出composition-api中setup优先级更高。
当然也可以在源码中的packages\runtime-core\src\componentPublicInstance.ts看到,通过switch先判断setup中的变量是否存在,然后再去判断data中的变量。所以setup中变量的优先级会高于data中的变量。
🍖实现
通过上面的用法,我们可以知道vue3中会对外暴露一个Vue变量,内部存在createApp、reactive等方法。
在此,我们先实现vue3的初始化框架。就createApp而言,它会接收用户传入的参数:data()、setup()等,最后进行实例挂载mount。所以在createApp中会接收一些参数options、内部还会存在mount方法。
const Vue = { createApp(options) { return { mount(selector) { //解析、获取render、挂载 } } } }
在mount中通过selector获取到宿主元素。
接下来就是对模板的编译,由于将template编译AST后,依旧要转成render函数。在此我们简化操作,在编译时直接返回一个render。
mount(selector) { //解析、获取render、挂载 const parent = document.querySelector(selector) console.log(parent); if (!options.render) { // 编译返回render options.render = this.compileToFunction(parent.innerHTML) } }, compileToFunction(template) { return function render() { const h = document.createElement('div') h.textContent = this.name return h } }
拿到render之后,执行它,将其添加到宿主元素中,将老的节点删除。
在执行render的时候,我们需要注意它的this指向。如果给它绑定data,那么它展示的就会使data中的name。
mount(selector) { //解析、获取render、挂载 const parent = document.querySelector(selector) console.log(parent); if (!options.render) { // 编译返回render options.render = this.compileToFunction(parent.innerHTML) } // 执行render const el = options.render.call(options.data()) parent.innerHTML = '' parent.appendChild(el) },
可以看到页面上展示的是clying。反之,如果绑定的是options.setup(),那么页面上出现的就是deng。
对于vue3的用法,我们知道setup的优先级是高于data的。那我们可以使用代理啊,将两者的属性变量,通过代理的方式,糅合到一起,优先考虑setup。当访问相同name时,实际访问的就是setup中的name。
mount(selector) { //解析、获取render、挂载 const parent = document.querySelector(selector) console.log(parent); if (!options.render) { // 编译返回render options.render = this.compileToFunction(parent.innerHTML) } if (options.setup) { this.setupState = options.setup() } if (options.data) { this.data = options.data() } this.proxy = new Proxy(this, { get(target, key) { if (key in target.setupState) { return target.setupState[key] } else if (key in target.data) { return target.data[key] }// 还可能存在props、watch等其他同名变量 }, set(target, key, value, newVal) { console.log(target, key, value, newVal); } }) // 执行render this.proxy就是整合setup和data的上下文 const el = options.render.call(this.proxy) console.log(el, options.render); parent.innerHTML = '' parent.appendChild(el) },
在proxy的get中,先看setup中是否存在目标属性,如果存在的话返回的就是setup中的属性变量,否则就是data中的。在渲染的时候,直接将整合的变量集传入即可。当然proxy中也会存在set方法,需要先代理,然后在外部获取变量做值得修改才能触发,在此有兴趣的同学可以自行研究哦!
总结
到此这篇关于vue3源码剖析之简单实现的文章就介绍到这了,更多相关vue3源码剖析内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!