一、 Vue 的使用
vue
基本知识点,如下所示:
- 插值、表达式,指令、动态属性,
v-html
会有XSS
风险,会覆盖子组件,代码如下所示:
<template>
<div>
<p>文本插值 {{message}}</p>
<p>JS 表达式 {{ flag ? 'yes' : 'no' }} (只能是表达式,不能是 js 语句)</p>
<p :id="dynamicId">动态属性 id</p>
<hr/>
<p v-html="rawHtml">
<span>有 xss 风险</span>
<span>【注意】使用 v-html 之后,将会覆盖子元素</span>
</p>
<!-- 其他常用指令后面讲 -->
</div>
</template>
<script>
export default {
data() {
return {
message: 'hello vue',
flag: true,
rawHtml: '指令 - 原始 html <b>加粗</b> <i>斜体</i>',
dynamicId: `id-${Date.now()}`
}
}
}
</script>
computed
和watch
,computed
有缓存,data
不变则不会重新计算。watch
可以进行深度监听,watch
监听引用类型,拿不到oldVal
,代码如下所示:- computed 的代码:
<template> <div> <p>num {{num}}</p> <p>double1 {{double1}}</p> <input v-model="double2"/> </div> </template> <script> export default { data() { return { num: 20 } }, computed: { double1() { return this.num * 2 }, double2: { get() { return this.num * 2 }, set(val) { this.num = val/2 } } } } </script>
- watch 的代码:
<template> <div> <input v-model="name"/> <input v-model="info.city"/> </div> </template> <script> export default { data() { return { name: '张三', info: { city: '上海' } } }, watch: { name(oldVal, val) { // eslint-disable-next-line console.log('watch name', oldVal, val) // 值类型,可正常拿到 oldVal 和 val }, info: { handler(oldVal, val) { // eslint-disable-next-line console.log('watch info', oldVal, val) // 引用类型,拿不到 oldVal 。因为指针相同,此时已经指向了新的 val }, deep: true // 深度监听 } } } </script>
- computed 的代码:
class
与style
,使用动态属性,使用驼峰式写法,代码如下所示:
<template>
<div>
<p :class="{ black: isBlack, yellow: isYellow }">使用 class</p>
<p :class="[black, yellow]">使用 class (数组)</p>
<p :style="styleData">使用 style</p>
</div>
</template>
<script>
export default {
data() {
return {
isBlack: true,
isYellow: true,
black: 'black',
yellow: 'yellow',
styleData: {
fontSize: '40px', // 转换为驼峰式
color: 'red',
backgroundColor: '#ccc' // 转换为驼峰式
}
}
}
}
</script>
<style scoped>
.black {
background-color: #999;
}
.yellow {
color: yellow;
}
</style>
- 条件渲染,
v-if
与v-else
的用法,可使用变量,也可以使用===
表达式,需要区分v-if
与v-show
的区别和使用场景,代码如下所示:
<template>
<div>
<p v-if="type === 'a'">A</p>
<p v-else-if="type === 'b'">B</p>
<p v-else>other</p>
<p v-show="type === 'a'">A by v-show</p>
<p v-show="type === 'b'">B by v-show</p>
</div>
</template>
<script>
export default {
data() {
return {
type: 'a'
}
}
}
</script>
- 循环列表渲染,遍历对象可以使用
v-for
,key
也很重要,不能够乱写。注意的是,v-for
和v-if
是不能一起使用的,代码如下所示:
<template>
<div>
<p>遍历数组</p>
<ul>
<li v-for="(item, index) in listArr" :key="item.id">
{{index}} - {{item.id}} - {{item.title}}
</li>
</ul>
<p>遍历对象</p>
<ul >
<li v-for="(val, key, index) in listObj" :key="key">
{{index}} - {{key}} - {{val.title}}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
flag: false,
listArr: [
{ id: 'a', title: '标题1' }, // 数据结构中,最好有 id ,方便使用 key
{ id: 'b', title: '标题2' },
{ id: 'c', title: '标题3' }
],
listObj: {
a: { title: '标题1' },
b: { title: '标题2' },
c: { title: '标题3' },
}
}
}
}
</script>
- 事件,
event
参数,自定义参数。事件修饰符,按键修饰符,观察事件被绑定到哪里,代码如下所示:
<template>
<div>
<p>{{num}}</p>
<button @click="increment1">+1</button>
<button @click="increment2(2, $event)">+2</button>
</div>
</template>
<script>
export default {
data() {
return {
num: 0
}
},
methods: {
increment1(event) {
// eslint-disable-next-line
console.log('event', event, event.__proto__.constructor) // 是原生的 event 对象
// eslint-disable-next-line
console.log(event.target)
// eslint-disable-next-line
console.log(event.currentTarget) // 注意,事件是被注册到当前元素的,和 React 不一样
this.num++
// 1. event 是原生的
// 2. 事件被挂载到当前元素
// 和 DOM 事件一样
},
increment2(val, event) {
// eslint-disable-next-line
console.log(event.target)
this.num = this.num + val
},
loadHandler() {
// do some thing
}
},
mounted() {
window.addEventListener('load', this.loadHandler)
},
beforeDestroy() {
//【注意】用 vue 绑定的事件,组建销毁时会自动被解绑
// 自己绑定的事件,需要自己销毁!!!
window.removeEventListener('load', this.loadHandler)
}
}
</script>
- 表单,
v-model
。常见的表单项textarea、checkbox、radio、select
,修饰符lazy、number、trim
,代码如下所示:
<template>
<div>
<p>输入框: {{name}}</p>
<input type="text" v-model.trim="name"/>
<input type="text" v-model.lazy="name"/>
<input type="text" v-model.number="age"/>
<p>多行文本: {{desc}}</p>
<textarea v-model="desc"></textarea>
<!-- 注意,<textarea>{{desc}}</textarea> 是不允许的!!! -->
<p>复选框 {{checked}}</p>
<input type="checkbox" v-model="checked"/>
<p>多个复选框 {{checkedNames}}</p>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<p>单选 {{gender}}</p>
<input type="radio" id="male" value="male" v-model="gender"/>
<label for="male">男</label>
<input type="radio" id="female" value="female" v-model="gender"/>
<label for="female">女</label>
<p>下拉列表选择 {{selected}}</p>
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>下拉列表选择(多选) {{selectedList}}</p>
<select v-model="selectedList" multiple>
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
name: '张三',
age: 18,
desc: '自我介绍',
checked: true,
checkedNames: [],
gender: 'male',
selected: '',
selectedList: []
}
}
}
</script>
- 对于
vue
组件的使用,props
和$emit
,组件间通讯,自定义事件,组件生命周期,代码如下所示:
- index.vue 如下:
<template>
<div>
<Input @add="addHandler"/>
<List :list="list" @delete="deleteHandler"/>
</div>
</template>
<script>
import Input from './Input'
import List from './List'
export default {
components: {
Input,
List
},
data() {
return {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
}
]
}
},
methods: {
addHandler(title) {
this.list.push({
id: `id-${Date.now()}`,
title
})
},
deleteHandler(id) {
this.list = this.list.filter(item => item.id !== id)
}
},
created() {
// eslint-disable-next-line
console.log('index created')
},
mounted() {
// eslint-disable-next-line
console.log('index mounted')
},
beforeUpdate() {
// eslint-disable-next-line
console.log('index before update')
},
updated() {
// eslint-disable-next-line
console.log('index updated')
},
}
</script>
- Input.vue 如下:
<template>
<div>
<input type="text" v-model="title"/>
<button @click="addTitle">add</button>
</div>
</template>
<script>
import event from './event'
export default {
data() {
return {
title: ''
}
},
methods: {
addTitle() {
// 调用父组件的事件
this.$emit('add', this.title)
// 调用自定义事件
event.$emit('onAddTitle', this.title)
this.title = ''
}
}
}
</script>
- List.vue 如下:
<template>
<div>
<ul>
<li v-for="item in list" :key="item.id">
{{item.title}}
<button @click="deleteItem(item.id)">删除</button>
</li>
</ul>
</div>
</template>
<script>
import event from './event'
export default {
// props: ['list']
props: {
// prop 类型和默认值
list: {
type: Array,
default() {
return []
}
}
},
data() {
return {
}
},
methods: {
deleteItem(id) {
this.$emit('delete', id)
},
addTitleHandler(title) {
// eslint-disable-next-line
console.log('on add title', title)
}
},
created() {
// eslint-disable-next-line
console.log('list created')
},
mounted() {
// eslint-disable-next-line
console.log('list mounted')
// 绑定自定义事件
event.$on('onAddTitle', this.addTitleHandler)
},
beforeUpdate() {
// eslint-disable-next-line
console.log('list before update')
},
updated() {
// eslint-disable-next-line
console.log('list updated')
},
beforeDestroy() {
// 及时销毁,否则可能造成内存泄露
event.$off('onAddTitle', this.addTitleHandler)
}
}
</script>
- event.js 如下:
import Vue from 'vue'
export default new Vue()
Vue
高级特性,自定义v-model、$nextTick、slot
、动态和异步组件、keep-alive、mixin
,如下所示:
- 自定义
v-model
,代码如下所示:
<template>
<!-- 例如:vue 颜色选择 -->
<input type="text"
:value="text1"
@input="$emit('change1', $event.target.value)"
>
<!--
1. 上面的 input 使用了 :value 而不是 v-model
2. 上面的 change1 和 model.event1 要对应起来
3. text1 属性对应起来
-->
</template>
<script>
export default {
model: {
prop: 'text1', // 对应 props text1
event: 'change1'
},
props: {
text1: String,
default() {
return ''
}
}
}
</script>
$nextTick
,Vue
是异步渲染,data
改变之后,DOM
不会立刻渲染,$nextTick
会在DOM
渲染之后被触发,以获取最新的DOM
节点,代码如下所示:
<template>
<div id="app">
<ul ref="ul1">
<li v-for="(item, index) in list" :key="index">
{{item}}
</li>
</ul>
<button @click="addItem">添加一项</button>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
list: ['a', 'b', 'c']
}
},
methods: {
addItem() {
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
// 1. 异步渲染,$nextTick 待 DOM 渲染完再回调
// 2. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
this.$nextTick(() => {
// 获取 DOM 元素
const ulElem = this.$refs.ul1
// eslint-disable-next-line
console.log( ulElem.childNodes.length )
})
}
}
}
</script>
-
slot
,插槽的基本使用,作用域插槽,具名插槽,代码如下所示:- slot.vue,如下
<template> <a :href="url"> <slot> 默认内容,即父组件没设置内容时,这里显示 </slot> </a> </template> <script> export default { props: ['url'], data() { return {} } } </script>
- scopedSlot.vue,如下
<template> <a :href="url"> <slot :slotData="website"> {{website.subTitle}} <!-- 默认值显示 subTitle ,即父组件不传内容时 --> </slot> </a> </template> <script> export default { props: ['url'], data() { return { website: { url: 'http://wangEditor.com/', title: 'wangEditor', subTitle: '轻量级富文本编辑器' } } } } </script>
-
动态组件,
:is = "component-name"
用法,需要根据数据,动态渲染的常见,即组件类型不确定。异步组件,import()
函数,按需加载,异步加载大组件,代码如下所示:
<template>
<div>
<p>vue 高级特性</p>
<hr>
<!-- 自定义 v-model -->
<!-- <p>{{name}}</p>
<CustomVModel v-model="name"/> -->
<!-- nextTick -->
<!-- <NextTick/> -->
<!-- slot -->
<!-- <SlotDemo :url="website.url">
{{website.title}}
</SlotDemo> -->
<!-- <ScopedSlotDemo :url="website.url">
<template v-slot="slotProps">
{{slotProps.slotData.title}}
</template>
</ScopedSlotDemo> -->
<!-- 动态组件 -->
<!-- <component :is="NextTickName"/> -->
<!-- 异步组件 -->
<!-- <FormDemo v-if="showFormDemo"/>
<button @click="showFormDemo = true">show form demo</button> -->
<!-- keep-alive -->
<!-- <KeepAlive/> -->
<!-- mixin -->
<MixinDemo/>
</div>
</template>
<script>
// import CustomVModel from './CustomVModel'
// import NextTick from './NextTick'
// import SlotDemo from './SlotDemo'
// import ScopedSlotDemo from './ScopedSlotDemo'
// import KeepAlive from './KeepAlive'
import MixinDemo from './MixinDemo'
export default {
components: {
// CustomVModel
// NextTick
// SlotDemo,
// ScopedSlotDemo,
// FormDemo: () => import('../BaseUse/FormDemo'),
// KeepAlive
MixinDemo
},
data() {
return {
name: '张三',
website: {
url: 'http://baidu.com/',
title: '百度',
subTitle: '百度前端'
},
// NextTickName: "NextTick",
showFormDemo: false
}
}
}
</script>
-
keep-alive
,缓存组件,频繁切换,不需要重复渲染,Vue
的常见性能优化,代码如下所示:- keepAlive.vue
<template> <div> <button @click="changeState('A')">A</button> <button @click="changeState('B')">B</button> <button @click="changeState('C')">C</button> <keep-alive> <!-- tab 切换 --> <KeepAliveStageA v-if="state === 'A'"/> <!-- v-show --> <KeepAliveStageB v-if="state === 'B'"/> <KeepAliveStageC v-if="state === 'C'"/> </keep-alive> </div> </template> <script> import KeepAliveStageA from './KeepAliveStateA' import KeepAliveStageB from './KeepAliveStateB' import KeepAliveStageC from './KeepAliveStateC' export default { components: { KeepAliveStageA, KeepAliveStageB, KeepAliveStageC }, data() { return { state: 'A' } }, methods: { changeState(state) { this.state = state } } } </script>
- keepAliveStateA.vue
<template> <p>state A</p> </template> <script> export default { mounted() { // eslint-disable-next-line console.log('A mounted') }, destroyed() { // eslint-disable-next-line console.log('A destroyed') } } </script>
- keepAliveStateB.vue
<template> <p>state B</p> </template> <script> export default { mounted() { // eslint-disable-next-line console.log('B mounted') }, destroyed() { // eslint-disable-next-line console.log('B destroyed') } } </script>
- keepAliveStateC.vue
<template> <p>state C</p> </template> <script> export default { mounted() { // eslint-disable-next-line console.log('C mounted') }, destroyed() { // eslint-disable-next-line console.log('C destroyed') } } </script>
- keepAlive.vue
-
mixin
,多个组件有相同的逻辑,抽离出来。mixin
并不是完美的解决方案,会有一些问题,Vue3
提出的Composition API
旨在解决这些问题。同样的,mixin
也会存在一些问题,变量来源不明确,不利于阅读,多mixin
可能会造成命名冲突,mixin
和组件可能出现多对多的关系,复杂度较高,代码如下所示:- Mixin.vue
<template> <div> <p>{{name}} {{major}} {{city}}</p> <button @click="showName">显示姓名</button> </div> </template> <script> import myMixin from './mixin' export default { mixins: [myMixin], // 可以添加多个,会自动合并起来 data() { return { name: '张三', major: 'web 前端' } }, methods: { }, mounted() { // eslint-disable-next-line console.log('component mounted', this.name) } } </script>
- mixin.js
export default { data() { return { city: '上海' } }, methods: { showName() { // eslint-disable-next-line console.log(this.name) } }, mounted() { // eslint-disable-next-line console.log('mixin mounted', this.name) } }
- 对于用于
Vue
组件,dispatch、commit、mapState、mapGetters、mapActions 和 mapMutations
也需要熟系。 - 对于
vue-router
,需要熟系路由模式,hash
和H5 history
,路由配置,动态路由和懒加载。对于vue-router
路由模式,hash
模式是默认的,如http://abc.com/#/user/10。H5 history
模式,如http://abc.com/user/20
,后者需要server
端支持,因此无特殊需求可选择前者。
二、 Vue 的原理
- 对于
vue
的原理,从组件化、响应式、vdom
和diff
、模版编译、渲染过程、前端路由几个方面。 - 组件化,很久之前就已经存在了,在
asp、jsp、php、nodejs
都有类似的组件化。数据驱动视图,MVVM、setState
。传统组件,只是静态渲染,更新依赖于操作DOM
,数据驱动视图,vue MVVM
,数据驱动视图,react setState
。 vue
的响应式,组件data
的数据一旦变化,立即触发视图的更新,实现数据驱动视图的第一步。vue
的响应式,核心API
是Object.defineProperty
。对于Object.defineProperty
也存在一些缺点,Vue3.0
启用Proxy
。Proxy
也存在兼容性的问题,兼容性不好,无法polyfill
。Object.defineProperty
实现响应式,监听对象,监听数组,复杂对象,深度监听。Object.defineProperty
的缺点,深度监听,需要递归到底,一次性计算量大,无法监听新增属性和删除属性,Vue.set
和Vue.delete
,无法原生监听数组,需要特殊处理,代码如下所示:
// 触发更新视图
function updateView() {
console.log('视图更新')
}
// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
arrProto[methodName] = function () {
updateView() // 触发视图更新
oldArrayProperty[methodName].call(this, ...arguments)
// Array.prototype.push.call(this, ...arguments)
}
})
// 重新定义属性,监听起来
function defineReactive(target, key, value) {
// 深度监听
observer(value)
// 核心 API
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue !== value) {
// 深度监听
observer(newValue)
// 设置新值
// 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
value = newValue
// 触发更新视图
updateView()
}
}
})
}
// 监听对象属性
function observer(target) {
if (typeof target !== 'object' || target === null) {
// 不是对象或数组
return target
}
// 污染全局的 Array 原型
// Array.prototype.push = function () {
// updateView()
// ...
// }
if (Array.isArray(target)) {
target.__proto__ = arrProto
}
// 重新定义各个属性(for in 也可以遍历数组)
for (let key in target) {
defineReactive(target, key, target[key])
}
}
// 准备数据
const data = {
name: 'zhangsan',
age: 20,
info: {
address: '北京' // 需要深度监听
},
nums: [10, 20, 30]
}
// 监听数据
observer(data)
// 测试
// data.name = 'lisi'
// data.age = 21
// // console.log('age', data.age)
// data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
// delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete
// data.info.address = '上海' // 深度监听
data.nums.push(4) // 监听数组
- 虚拟
DOM( Virtual DOM)
和diff
,vdom
是实现vue
和react
的重要基石,diff
算法是vdom
中最核心、最关键的部分,如下所示:
DOM
操作非常耗费性能,以前用jQuery
,可以自行控制DOM
操作的时机,手动调整。Vue
和React
是数据驱动视图,如何有效的控制DOM
操作。- 解决方案就可以用
vdom
,有了一定复杂度,想减少计算次数比较难。将计算转移为JS
计算,JS
执行速度很快。vdom
用JS
模拟DOM
结构,计算出最小的变更,操作DOM
- 通过
snabbdom
学习vdom
,简洁强大的vdom
库,易学易用,Vue
参考它实现的vdom
和diff
,地址为https://github.com/snabbdom/snabbdom
。在Vue3.0
重写了vdom
的代码,优化了性能 diff
算法是vdom
中最核心、最关键的部分,在日常使用vue
、react
中可以体现出来,如key
。diff
即对比,是一个广泛的概念,如linux diff
命令、git diff
等。两个JS
对象也可以做diff
,两棵树做diff
,如vdom diff
等等。- 树
diff
的时间复杂度O(n^3)
,第一,遍历tree 1
;第二,遍历tree2
;第三,排序。1000
个节点,要计算1
亿次,算法不可用 - 优化时间复杂度到
O(n)
,只比较同一层级,不跨级比较。tag
不相同,则直接删掉重建,不再深度比较。tag
和key
,两者都相同,则认为是相同节点,不再深度比较。
- 对于
diff
算法的总结,patchVnode、addVnodes removeVnodes、updateChildren
,key
的重要性。vdom
核心概念很重要,h、vnode、patch、diff、key
等等。vdom
存在的价值更加重要,数据驱动视图,控制DOM
操作。snabbdom
的简单实现,代码如下所示:
const snabbdom = window.snabbdom
// 定义 patch
const patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
])
// 定义 h
const h = snabbdom.h
const container = document.getElementById('container')
// 生成 vnode
const vnode = h('ul#list', {}, [
h('li.item', {}, 'Item 1'),
h('li.item', {}, 'Item 2')
])
patch(container, vnode)
document.getElementById('btn-change').addEventListener('click', () => {
// 生成 newVnode
const newVnode = h('ul#list', {}, [
h('li.item', {}, 'Item 1'),
h('li.item', {}, 'Item B'),
h('li.item', {}, 'Item 3')
])
patch(vnode, newVnode)
})
- 模版编译,模版是
vue
开发中最常用的部分,即与使用相关联的原理。它不是html
,有指令、插值、JS
表达式,会通过组件渲染和更新过程所去体现出来,如下所示:
- 前置知识也是
JS
的with
语法,能改变{}
内自由变量的查找方式,vue template complier
将模版编译为render
函数,执行render
函数生成vnode
with
语法,改变{}
内自由变量的查找规则,当做obj
属性来查找。如果找不到匹配的obj
属性,就会报错。with
要慎用,它打破了作用域规则,易读性变差- 编译模版,模版不是
html
,有指令、插值、JS
表达式,能实现判断、循环。html
是标签语言,只有JS
才能实现判断、循环,图灵完备的。因此,模版一定是转换为某种JS
代码,即编译模版
- 对于
vue
组件中使用render
代替template
。在有些复杂情况中,不能用template
,可以考虑用render
。React
一直都用render
,没有模版,和这里一样。所以,vue
组件中使用render
代替template
,代码如下所示:
const compiler = require('vue-template-compiler')
// 插值
// // const template = `<p>{{message}}</p>`
// // with(this){return createElement('p',[createTextVNode(toString(message))])}
// // h -> vnode
// // createElement -> vnode
// // // 表达式
// // const template = `<p>{{flag ? message : 'no message found'}}</p>`
// // // with(this){return _c('p',[_v(_s(flag ? message : 'no message found'))])}
// // // 属性和动态属性
// // const template = `
// // <div id="div1" class="container">
// // <img :src="imgUrl"/>
// // </div>
// // `
// // with(this){return _c('div',
// // {staticClass:"container",attrs:{"id":"div1"}},
// // [
// // _c('img',{attrs:{"src":imgUrl}})])}
// // // 条件
// // const template = `
// // <div>
// // <p v-if="flag === 'a'">A</p>
// // <p v-else>B</p>
// // </div>
// // `
// // with(this){return _c('div',[(flag === 'a')?_c('p',[_v("A")]):_c('p',[_v("B")])])}
// // 循环
// // const template = `
// // <ul>
// // <li v-for="item in list" :key="item.id">{{item.title}}</li>
// // </ul>
// // `
// // with(this){return _c('ul',_l((list),function(item){return _c('li',{key:item.id},[_v(_s(item.title))])}),0)}
// // 事件
// // const template = `
// // <button @click="clickHandler">submit</button>
// // `
// // with(this){return _c('button',{on:{"click":clickHandler}},[_v("submit")])}
// // v-model
// const template = `<input type="text" v-model="name">`
// // 主要看 input 事件
// // with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})}
// // render 函数
// // 返回 vnode
// // patch
// // 编译
// const res = compiler.compile(template)
// console.log(res.render)
// // ---------------分割线--------------
// // // 从 vue 源码中找到缩写函数的含义
// // function installRenderHelpers (target) {
// // target._o = markOnce;
// // target._n = toNumber;
// // target._s = toString;
// // target._l = renderList;
// // target._t = renderSlot;
// // target._q = looseEqual;
// // target._i = looseIndexOf;
// // target._m = renderStatic;
// // target._f = resolveFilter;
// // target._k = checkKeyCodes;
// // target._b = bindObjectProps;
// // target._v = createTextVNode;
// // target._e = createEmptyVNode;
// // target._u = resolveScopedSlots;
// // target._g = bindObjectListeners;
// // target._d = bindDynamicKeys;
// // target._p = prependModifier;
// // }
-
对于组件的渲染和更新过程,一个组件渲染到页面,修改
data
触发更新,数据驱动视图,对背后原理和流程需要很熟悉。响应式,监听data
属性getter
和setter
,包括数组。模版编译,模版到render
函数,再到vnode
。vdom,patch(elem, vnode)
和patch(vnode, newVnode)
。 -
对于初次渲染过程,更新过程和异步渲染,也需要很熟悉,如下所示:
- 初次渲染过程,解析模版为
render
函数或者是在开发环境已完成,vue-loader
。触发响应式,监听data
属性getter
和setter
。执行render
函数,生成vnode
,patch( elem, vnode)
- 对于更新过程,修改
data
,触发setter
,此前setter
中已被监听。重新执行render
函数,生成newVnode,patch(vnode, newVnode)
- 对于异步渲染,需要用到
$nextTick
,汇总data
的修改,一次性更新视图。减少DOM
操作次数,提高性能。异步渲染,$nextTick
待DOM
渲染完再回调。页面渲染时会将data
的修改做整合,多次data
修改只会渲染一次。
-
对于模版的渲染与隔离过程,需要理清渲染和响应式的关系、渲染和模版编译的关系、渲染和
vdom
关系。初次渲染过程,更新过程和异步渲染,同样需要理清。 -
前端路由原理,对于
SPA
,都需要路由,vue-router
也是vue
全家桶的标配之一。vue-router
的路由模式、hash
和H5 history
,如下所示:
hash
的特点,如下所示:hash
变化会触发网页跳转,即浏览器的前进和后退hash
变化不会刷新页面,SPA
必须的特点hash
永远不会提交到server
端
H5 history
,用url
规范的路由,但跳转时不刷新页面,history.pushState
和window.onpopstate
- 对于两者的选择,如下所示:
to B
的系统推荐使用hash
,简单易用,对url
规范不敏感to C
的系统,可以考虑选择H5 history
,但需要服务端支持- 能选择简单的,就别用复杂的,要考虑成本和收益