vue是组件化开发框架,所以对于vue应用来说组件间的数据通信非常重要。 此题主要考查大家vue基本功,对于vue基础api运用熟练度。 另外一些边界知识如provide/inject/$attrs则提现了面试者的知识广度。
组件传参的各种方式
注意vue3中废弃的几个API
v3-migration.vuejs.org/breaking-ch…
v3-migration.vuejs.org/breaking-ch…
v3-migration.vuejs.org/breaking-ch…
父子组件
props
/$emit
/$parent
/ref
/$attrs
兄弟组件
$parent
/$root
/eventbus
/vuex
跨层级关系
eventbus
/vuex
/provide
+inject
此题考查常识,文档中曾有详细说明v2|v3;也是一个很好的实践题目,项目中经常会遇到,能够看出面试者api熟悉程度和应用能力。
实践中不应该把v-for和v-if放一起
在vue2中,v-for的优先级是高于v-if,把它们放在一起,输出的渲染函数中可以看出会先执行循环再判断条件,哪怕我们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表,这会比较浪费;另外需要注意的是在vue3中则完全相反,v-if的优先级高于v-for,所以v-if执行时,它调用的变量还不存在,就会导致异常
通常有两种情况下导致我们这样做:
为了过滤列表中的项目 (比如 v-for="user in users" v-if="user.isActive"
)。此时定义一个计算属性 (比如 activeUsers
),让其返回过滤后的列表即可(比如users.filter(u=>u.isActive)
)。
为了避免渲染本应该被隐藏的列表 (比如 v-for="user in users" v-if="shouldShowUsers"
)。此时把 v-if
移动至容器元素上 (比如 ul
、ol
)或者外面包一层template
即可。
文档中明确指出永远不要把 v-if
和 v-for
同时用在同一个元素上,显然这是一个重要的注意事项。
源码里面关于代码生成的部分,能够清晰的看到是先处理v-if还是v-for,顺序上vue2和vue3正好相反,因此产生了一些症状的不同,但是不管怎样都是不能把它们写在一起的。
做个测试, 两者同级时,渲染函数如下:
ƒ anonymous(
) {
with(this){return _c('div',{attrs:{"id":"app"}},_l((items),function(item){return (item.isActive)?_c('div',{key:item.id},[_v("\n "+_s(item.name)+"\n ")]):_e()}),0)}
}
做个测试,test-v3.html
源码中找答案
v2:github1s.com/vuejs/vue/b…
v3:github1s.com/vuejs/core/…
必问题目,考查vue基础知识。
1.每个Vue组件实例被创建后都会经过一系列初始化步骤,比如,它需要数据观测,模板编译,挂载实例到dom上,以及数据变化时更新dom。这个过程中会运行叫做生命周期钩子的函数,以便用户在特定阶段有机会添加他们自己的代码。
2.Vue生命周期总共可以分为8个阶段:创建前后, 载入前后, 更新前后, 销毁前后,以及一些特殊场景的生命周期。vue3中新增了三个用于调试和服务端渲染场景。
生命周期v2 | 生命周期v3 | 描述 |
---|---|---|
beforeCreate | beforeCreate | 组件实例被创建之初 |
created | created | 组件实例已经完全创建 |
beforeMount | beforeMount | 组件挂载之前 |
mounted | mounted | 组件挂载到实例上去之后 |
beforeUpdate | beforeUpdate | 组件数据发生变化,更新之前 |
updated | updated | 数据数据更新之后 |
beforeDestroy | beforeUnmount | 组件实例销毁之前 |
destroyed | unmounted | 组件实例销毁之后 |
生命周期v2 | 生命周期v3 | 描述 |
---|---|---|
activated | activated | keep-alive 缓存的组件激活时 |
deactivated | deactivated | keep-alive 缓存的组件停用时调用 |
errorCaptured | errorCaptured | 捕获一个来自子孙组件的错误时被调用 |
- | renderTracked | 调试钩子,响应式依赖被收集时调用 |
- | renderTriggered | 调试钩子,响应式依赖被触发时调用 |
- | serverPrefetch | ssr only,组件实例在服务器上被渲染前调用 |
4.结合实践:
beforeCreate:通常用于插件开发中执行一些初始化任务
created:组件初始化完毕,可以访问各种数据,获取接口数据等
mounted:dom已创建,可用于获取访问数据和dom元素;访问子组件等。
beforeUpdate:此时view
层还未更新,可用于获取更新前各种状态
updated:完成view
层的更新,更新后,所有状态已是最新
beforeunmount:实例被销毁前调用,可用于一些定时器或订阅的取消
unmounted:销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器
vue3中生命周期的派发时刻:
github1s.com/vuejs/core/…
vue2中声明周期的派发时刻:
github1s.com/vuejs/vue/b…
双向绑定是vue
的特色之一,开发中必然会用到的知识点,然而此题还问了实现原理,升级为深度考查。
v-model
,可以绑定一个响应式数据到视图,同时视图中变化能改变该值。v-model
是语法糖,默认情况下相当于:value
和@input
。使用v-model
可以减少大量繁琐的事件处理代码,提高开发效率。v-model
,还可以在自定义组件上使用,表示某个值的输入和输出控制。
的方式将xxx的值绑定到表单元素value上;对于checkbox,可以使用true-value
和false-value指定特殊的值,对于radio可以使用value指定特殊的值;对于select可以通过options元素的value设置特殊的值;还可以结合.lazy,.number,.trim对v-mode的行为做进一步限定;v-model
用在自定义组件上时又会有很大不同,vue3中它类似于sync
修饰符,最终展开的结果是modelValue属性和update:modelValue事件;vue3中我们甚至可以用参数形式指定多个不同的绑定,例如v-model:foo和v-model:bar,非常强大!v-model
是一个指令,它的神奇魔法实际上是vue的编译器完成的。我做过测试,包含v-model
的模板,转换为渲染函数之后,实际上还是是value属性的绑定以及input事件监听,事件回调函数中会做相应变量更新操作。编译器根据表单元素的不同会展开不同的DOM属性和事件对,比如text类型的input和textarea会展开为value和input事件;checkbox和radio类型的input会展开为checked和change事件;select用value作为属性,用change作为事件。v-model
和sync
修饰符有什么区别v-model
如果想要改变事件名或者属性名应该怎么做测试代码
观察输出的渲染函数:
//
_c('input', {
directives: [{ name: "model", rawName: "v-model", value: (foo), expression: "foo" }],
attrs: { "type": "text" },
domProps: { "value": (foo) },
on: {
"input": function ($event) {
if ($event.target.composing) return;
foo = $event.target.value
}
}
})
//
_c('input', {
directives: [{ name: "model", rawName: "v-model", value: (bar), expression: "bar" }],
attrs: { "type": "checkbox" },
domProps: {
"checked": Array.isArray(bar) ? _i(bar, null) > -1 : (bar)
},
on: {
"change": function ($event) {
var $$a = bar, $$el = $event.target, $$c = $$el.checked ? (true) : (false);
if (Array.isArray($$a)) {
var $$v = null, $$i = _i($$a, $$v);
if ($$el.checked) { $$i < 0 && (bar = $$a.concat([$$v])) }
else {
$$i > -1 && (bar = $$a.slice(0, $$i).concat($$a.slice($$i + 1))) }
} else {
bar = $$c
}
}
}
})
//
_c('select', {
directives: [{ name: "model", rawName: "v-model", value: (baz), expression: "baz" }],
on: {
"change": function ($event) {
var $$selectedVal = Array.prototype.filter.call(
$event.target.options,
function (o) { return o.selected }
).map(
function (o) {
var val = "_value" in o ? o._value : o.value;
return val
}
);
baz = $event.target.multiple ? $$selectedVal : $$selectedVal[0]
}
}
}, [
_c('option', { attrs: { "value": "vue" } }, [_v("vue")]), _v(" "),
_c('option', { attrs: { "value": "react" } }, [_v("react")])
])
此题属于实践题,考察大家对vue常用api使用熟练度,答题时不仅要列出这些解决方案,同时最好说出他们异同。
按照逻辑扩展和内容扩展来列举,
逻辑扩展有:mixins、extends、composition api;
内容扩展有slots;
分别说出他们使用方法、场景差异和问题。
作为扩展,还可以说说vue3中新引入的composition api带来的变化
常见的组件扩展方法有:mixins,slots,extends等
混入mixins是分发 Vue 组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。
// 复用代码:它是一个配置对象,选项和组件里面一样
const mymixin = {
methods: {
dosomething(){}
}
}
// 全局混入:将混入对象传入
Vue.mixin(mymixin)
// 局部混入:做数组项设置到mixins选项,仅作用于当前组件
const Comp = {
mixins: [mymixin]
}
插槽主要用于vue组件中的内容分发,也可以用于组件扩展。
子组件Child
这个内容会被父组件传递的内容替换
父组件Parent
来自老爹的内容
如果要精确分发到不同位置可以使用具名插槽,如果要使用子组件中的数据可以使用作用域插槽。
组件选项中还有一个不太常用的选项extends,也可以起到扩展组件的目的
// 扩展对象
const myextends = {
methods: {
dosomething(){}
}
}
// 组件扩展:做数组项设置到extends选项,仅作用于当前组件
// 跟混入的不同是它只能扩展单个对象
// 另外如果和混入发生冲突,该选项优先级较高,优先起作用
const Comp = {
extends: myextends
}
混入的数据和方法不能明确判断来源且可能和当前组件内变量产生命名冲突,vue3中引入的composition api,可以很好解决这些问题,利用独立出来的响应式模块可以很方便的编写独立逻辑并提供响应式的数据,然后在setup选项中组合使用,增强代码的可读性和维护性。例如:
// 复用逻辑1
function useXX() {}
// 复用逻辑2
function useYY() {}
// 逻辑组合
const Comp = {
setup() {
const {xx} = useXX()
const {yy} = useYY()
return {xx, yy}
}
}
Vue.extend方法你用过吗?它能用来做组件扩展吗?
mixins原理:
github1s.com/vuejs/core/…
github1s.com/vuejs/core/…
slots原理:
github1s.com/vuejs/core/…
github1s.com/vuejs/core/…
github1s.com/vuejs/core/…
这是一个实践知识点,组件化开发过程中有个单项数据流原则,不在子组件中修改父组件是个常识问题。
参考文档:staging.vuejs.org/guide/compo…
所有的 prop 都使得其父子之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。另外,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器控制台中发出警告。
const props = defineProps(['foo'])
// ❌ 下面行为会被警告, props是只读的!
props.foo = 'bar'
实际开发过程中有两个场景会想要修改一个属性:
**这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。**在这种情况下,最好定义一个本地的 data,并将这个 prop 用作其初始值:
const props = defineProps(['initialCounter'])
const counter = ref(props.initialCounter)
**这个 prop 以一种原始的值传入且需要进行转换。**在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
const props = defineProps(['size'])
// prop变化,计算属性自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
实践中如果确实想要改变父组件属性应该emit一个事件让父组件去做这个变更。注意虽然我们不能直接修改一个传入的对象或者数组类型的prop,但是我们还是能够直接改内嵌的对象或属性。
综合实践题目,实际开发中经常需要面临权限管理的需求,考查实际应用能力。
权限管理一般需求是两个:页面权限和按钮权限,从这两个方面论述即可。
权限管理一般需求是页面权限和按钮权限的管理
具体实现的时候分后端和前端两种方案:
前端方案会把所有路由信息在前端配置,通过路由守卫要求用户登录,用户登录后根据角色过滤出路由表。比如我会配置一个asyncRoutes
数组,需要认证的页面在其路由的meta
中添加一个roles
字段,等获取用户角色之后取两者的交集,若结果不为空则说明可以访问。此过滤过程结束,剩下的路由就是该用户能访问的页面,最后通过router.addRoutes(accessRoutes)
方式动态添加路由即可。
后端方案会把所有页面路由信息存在数据库中,用户登录的时候根据其角色查询得到其能访问的所有页面路由信息返回给前端,前端再通过addRoutes
动态添加路由信息
按钮权限的控制通常会实现一个指令,例如v-permission
,将按钮要求角色通过值传给v-permission指令,在指令的moutned
钩子中可以判断当前用户角色和按钮是否存在交集,有则保留按钮,无则移除按钮。
纯前端方案的优点是实现简单,不需要额外权限管理页面,但是维护起来问题比较大,有新的页面和角色需求就要修改前端代码重新打包部署;服务端方案就不存在这个问题,通过专门的角色和权限管理页面,配置页面和按钮权限信息到数据库,应用每次登陆时获取的都是最新的路由信息,可谓一劳永逸!
路由守卫
github1s.com/PanJiaChen/…
路由生成
github1s.com/PanJiaChen/…
动态追加路由
github1s.com/PanJiaChen/…
类似Tabs
这类组件能不能使用v-permission
指令实现按钮权限控制?
⽤户管理
⻆⾊管理
服务端返回的路由信息如何添加到路由器中?
// 前端组件名和组件映射表
const map = {
//xx: require('@/views/xx.vue').default // 同步的⽅式
xx: () => import('@/views/xx.vue') // 异步的⽅式
}
// 服务端返回的asyncRoutes
const asyncRoutes = [
{ path: '/xx', component: 'xx',... }
]
// 遍历asyncRoutes,将component替换为map[component]
function mapComponent(asyncRoutes) {
asyncRoutes.forEach(route => {
route.component = map[route.component];
if(route.children) {
route.children.map(child => mapComponent(child))
}
})
}
mapComponent(asyncRoutes)
这是一道必问题目,但能回答到位的比较少。如果只是看看一些网文,通常没什么底气,经不住面试官推敲,但像我们这样即看过源码还造过轮子的,回答这个问题就会比较有底气啦。
vue2响应式:
github1s.com/vuejs/vue/b…
vue3响应式:
github1s.com/vuejs/core/…
github1s.com/vuejs/core/…
现有框架几乎都引入了虚拟 DOM 来对真实 DOM 进行抽象,也就是现在大家所熟知的 VNode 和 VDOM,那么为什么需要引入虚拟 DOM 呢?围绕这个疑问来解答即可!
虚拟dom顾名思义就是虚拟的dom对象,它本身就是一个 JavaScript
对象,只不过它是通过不同的属性去描述一个视图结构。
通过引入vdom我们可以获得如下好处:
将真实元素节点抽象成 VNode,有效减少直接操作 dom 次数,从而提高程序性能
方便实现跨平台
vdom如何生成?在vue中我们常常会为组件编写模板 - template, 这个模板会被编译器 - compiler编译为渲染函数,在接下来的挂载(mount)过程中会调用render函数,返回的对象就是虚拟dom。但它们还不是真正的dom,所以会在后续的patch过程中进一步转化为dom。
挂载过程结束后,vue程序进入更新流程。如果某些响应式数据发生变化,将会引起组件重新render,此时就会生成新的vdom,和上一次的渲染结果diff就能得到变化的地方,从而转换为最小量的dom操作,高效更新视图。
vnode定义:
github1s.com/vuejs/core/…
观察渲染函数:21-vdom/test-render-v3.html
创建vnode:
github1s.com/vuejs/core/…
github1s.com/vuejs/core/…
github1s.com/vuejs/core/…
mount:
github1s.com/vuejs/core/…
调试mount过程:mountComponent
21-vdom/test-render-v3.html
1.Vue中的diff算法称为patching算法,它由Snabbdom修改而来,虚拟DOM要想转化为真实DOM就需要通过patch方法转换。
2.最初Vue1.x视图中每个依赖均有更新函数对应,可以做到精准更新,因此并不需要虚拟DOM和patching算法支持,但是这样粒度过细导致Vue1.x无法承载较大应用;Vue 2.x中为了降低Watcher粒度,每个组件只有一个Watcher与之对应,此时就需要引入patching算法才能精确找到发生变化的地方并高效更新。
3.vue中diff执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行render函数获得最新的虚拟DOM,然后执行patch函数,并传入新旧两次虚拟DOM,通过比对两者找到变化的地方,最后将其转化为对应的DOM操作。
4.patch过程是一个递归过程,遵循深度优先、同层比较的策略;以vue3的patch为例:
patch关键代码
github1s.com/vuejs/core/…
调试
官网列举的最值得注意的新特性:v3-migration.vuejs.org/
也就是下面这些:
以上这些是api相关,另外还有很多框架特性也不能落掉。
api层面Vue3新特性主要包括:Composition API、SFC Composition API语法糖、Teleport传送门、Fragments 片段、Emits选项、自定义渲染器、SFC CSS变量、Suspense
另外,Vue3.0在框架层面也有很多亮眼的改进:
体验编译器优化
sfc.vuejs.org/
reactive实现
github1s.com/vuejs/core/…
API题目,考查基础能力,不容有失,尽可能说的详细。
User
组件,它应该对所有用户进行渲染,但用户 ID 不同。在 Vue Router 中,我们可以在路径中使用一个动态字段来实现,例如:{ path: '/users/:id', component: User }
,其中:id
就是路径参数:
表示。当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.params
的形式暴露出来。/users/:username/posts/:postId
;除了 $route.params
之外,$route
对象还公开了其他有用的信息,如 $route.query
、$route.hash
等。router.vuejs.org/zh/guide/es…
router.vuejs.org/zh/guide/es…
首先思考vue路由要解决的问题:用户点击跳转链接内容切换,页面不刷新。
一个SPA应用的路由需要解决的问题是页面跳转内容改变同时不刷新,同时路由还需要以插件形式存在,所以:
createRouter
函数,返回路由器实例,实例内部做几件事:
github1s.com/vuejs/route…
github1s.com/vuejs/route… RouterView
github1s.com/vuejs/route…
github1s.com/vuejs/route…
这是一道特别常见的问题,主要考查大家对虚拟DOM和patch细节的掌握程度,能够反映面试者理解层次。
测试代码
上面案例重现的是以下过程
如果使用key
// 首次循环patch A
A B C D E
A B F C D E
// 第2次循环patch B
B C D E
B F C D E
// 第3次循环patch E
C D E
F C D E
// 第4次循环patch D
C D
F C D
// 第5次循环patch C
C
F C
// oldCh全部处理结束,newCh中剩下的F,创建F并插入到C前面
源码中找答案:
判断是否为相同节点
github1s.com/vuejs/core/…
更新时的处理
github1s.com/vuejs/core/…
不使用key
如果使用key
// 首次循环patch A
A B C D E
A B F C D E
// 第2次循环patch B
B C D E
B F C D E
// 第3次循环patch E
C D E
F C D E
// 第4次循环patch D
C D
F C D
// 第5次循环patch C
C
F C
// oldCh全部处理结束,newCh中剩下的F,创建F并插入到C前面
源码中找答案:
判断是否为相同节点
github1s.com/vuejs/core/…
更新时的处理
github1s.com/vuejs/core/…
这道题及考察使用,有考察原理,nextTick在开发过程中应用的也较少,原理上和vue异步更新有密切关系,对于面试者考查很有区分度,如果能够很好回答此题,对面试效果有极大帮助。
nextTick是等待下一次 DOM 更新刷新的工具方法。
Vue有个异步更新策略,意思是如果数据变化,Vue不会立刻更新DOM,而是开启一个队列,把组件更新函数保存在队列中,在同一事件循环中发生的所有数据变更会异步的批量更新。这一策略导致我们对数据的修改不会立刻体现在DOM上,此时如果想要获取更新后的DOM状态,就需要使用nextTick。
开发时,有两个场景我们会用到nextTick:
nextTick签名如下:function nextTick(callback?: () => void): Promise
所以我们只需要在传入的回调函数中访问最新DOM状态即可,或者我们可以await nextTick()方法返回的Promise之后做这件事。
在Vue内部,nextTick之所以能够让我们看到DOM更新后的结果,是因为我们传入的callback会被添加到队列刷新函数(flushSchedulerQueue)的后面,这样等队列内部的更新函数都执行完毕,所有DOM操作也就结束了,callback自然能够获取到最新的DOM值。
组件更新函数入队:
github1s.com/vuejs/core/…
入队函数:
github1s.com/vuejs/core/…
nextTick定义:
github1s.com/vuejs/core/…
两个重要API,反应应聘者熟练程度。
computed特点:具有响应式的返回值
const count = ref(1)
const plusOne = computed(() => count.value + 1)
watch特点:侦测变化,执行回调
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
computed的实现
github1s.com/vuejs/core/…
ComputedRefImpl
github1s.com/vuejs/core/…
缓存性
github1s.com/vuejs/core/…
github1s.com/vuejs/core/…
watch的实现
github1s.com/vuejs/core/…
这题考查大家对创建过程的理解程度。
观察beforeCreated和created钩子的处理
github1s.com/vuejs/core/…
github1s.com/vuejs/core/…
观察beforeMount和mounted钩子的处理
github1s.com/vuejs/core/…
测试代码
缓存组件使用keep-alive组件,这是一个非常常见且有用的优化手段,vue3中keep-alive有比较大的更新,能说的点比较多。
开发中缓存组件使用keep-alive组件,keep-alive是vue内置组件,keep-alive包裹动态组件component时,会缓存不活动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,防止重复渲染DOM。
结合属性include和exclude可以明确指定缓存哪些组件或排除缓存指定组件。vue3中结合vue-router时变化较大,之前是keep-alive
包裹router-view
,现在需要反过来用router-view
包裹keep-alive
:
缓存后如果要获取数据,解决方案可以有以下两种:
beforeRouteEnter:在有vue-router的项目,每次进入路由的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){
next(vm=>{
console.log(vm)
// 每次进入路由执行
vm.getData() // 获取数据
})
},
actived:在keep-alive
缓存的组件被激活的时候,都会执行actived
钩子
activated(){
this.getData() // 获取数据
},
keep-alive是一个通用组件,它内部定义了一个map,缓存创建过的组件实例,它返回的渲染函数内部会查找内嵌的component组件对应组件的vnode,如果该组件在map中存在就直接返回它。由于component的is属性是个响应式数据,因此只要它变化,keep-alive的render函数就会重新执行。
KeepAlive定义
github1s.com/vuejs/core/…
缓存定义
github1s.com/vuejs/core/…
缓存组件
github1s.com/vuejs/core/…
获取缓存组件
github1s.com/vuejs/core/…
测试缓存特性
综合实践类题目,考查实战能力。没有什么绝对的正确答案,把平时工作的重点有条理的描述一下即可。
从0创建一个项目我大致会做以下事情:项目构建、引入必要插件、代码规范、提交规范、常用库和组件
目前vue3项目我会用vite或者create-vue创建项目
接下来引入必要插件:路由插件vue-router、状态管理vuex/pinia、ui库我比较喜欢element-plus和antd-vue、http工具我会选axios
其他比较常用的库有vueuse,nprogress,图标可以使用vite-svg-loader
下面是代码规范:结合prettier和eslint即可
最后是提交规范,可以使用husky,lint-staged,commitlint
目录结构我有如下习惯: .vscode
:用来放项目中的 vscode 配置
plugins
:用来放 vite 插件的 plugin 配置
public
:用来放一些诸如 页头icon 之类的公共文件,会被打包到dist根目录下
src
:用来放项目代码文件
api
:用来放http的一些接口配置
assets
:用来放一些 CSS 之类的静态资源
components
:用来放项目通用组件
layout
:用来放项目的布局
router
:用来放项目的路由配置
store
:用来放状态管理Pinia的配置
utils
:用来放项目中的工具方法类
views
:用来放项目的页面文件
看到这样的题目,可以用以下图片来回答:
查看vue官方文档:
风格指南:vuejs.org/style-guide…
性能:vuejs.org/guide/best-…
安全:vuejs.org/guide/best-…
访问性:vuejs.org/guide/best-…
发布:vuejs.org/guide/best-…
我从编码风格、性能、安全等方面说几条:
template: + userProvidedString +
问我们template到render过程,其实是问vue编译器
工作原理。
vue3编译过程窥探:
github1s.com/vuejs/core/…
测试
挂载过程完成了最重要的两件事:
把这两件事说清楚即可!
测试代码,test-v3.html mount函数定义
github1s.com/vuejs/core/…
首次render过程
github1s.com/vuejs/core/…