一、 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
,但需要服務端支持- 能選擇簡單的,就別用複雜的,要考慮成本和收益