Vue2
组件化基础
- 数据驱动视图 (MVVM,setState)
- MVVM
- M => data
- V => view
- VM => 一个抽象的层级,Vue提供的能力,提供 V 和 M 连接的一部分
响应式
- 组件 data 的数据一旦变化,会触发视图的更新
- Object.defineProperty (Vue3.0启用 proxy)
- 监听对象,监听数组
- 复杂对象,深度监听
- Object.defineProperty缺点:
- 深度监听,需要递归到底,一次性计算量较大
- 无法监听新增属性、删除属性 (Vue.set / Vue.delete)
- 无法原生监听数组,需要特殊处理
- Proxy 兼容性不好,且无法 polyfill
组件渲染和更新的过程
虚拟 DOM (Virtual DOM) 和 Diff
- vdom 是实现 vue 和 React 的重要基石
- diff 算法 是 vdom 中最核心、最关键的部分
- DOM 操作非常耗费性能
- vdom - 用 JS 模拟 DOM 结构,新旧 vnode对比,计算出最小的变更,最后更新 DOM
- vdom 核心概念:h、vnode、patch、diff、key 等
- vdom 存在的价值:数据驱动视图,控制 DOM 操作等
snabbdom
github.com/snabbdom/snabbd om
简洁强大的 vdom 库
Vue 参考它实现的 vdom 和 diff
Vue3 重写了 vdom 的代码,优化了性能,但是 vdom 的基本理念不变
h函数、vnode数据结构、patch函数
patchVnode函数:新旧都有 children,执行update。新有旧无,执行add。新无旧有,执行remove
updateChildren函数
h 函数
第一个参数:标签
第二个参数:属性
第三个参数:子元素
patch 函数 渲染 vnode
const vnode = h('ul#list',{},[
h('li.item',{},'Item1'),
h('li.item',{},'Item2')
])
const = document.getElementById('container')
patch(container,vnode) //初次渲染
const newVnode = h('ul#list',{},[
h('li.item',{},'Item')
])
patch(vnode,newVnode) //dom更新
diff 算法
- diff 算法是 vdom 中最核心、最关键的部分
- diff 即对比,是一个广泛的概念,如 linux diff 命令,git diff 等
- 两个 JS 对象也可以做 diff :jiff 库
- 树的 diff 的时间复杂度是 O(n^3)
- 遍历 tree1
- 遍历 tree2
- 排序
- 1000个节点,要计算一亿次,算法不可用
- 优化时间复杂度到 O(n)
- 只比较同一层级,不跨级比较
- tag 不同,则直接删掉重建,不再深度比较
- tag 和 key ,两者都相同,则认为是相同节点,不再深度比较
模板编译
JS 的 with 语法
- 它改变了 {} 内自由变量的查找规则,当做 obj 属性来查找
- 如果找不到匹配 obj 下的属性,就会报错
- with 要慎用,它打破了作用域规则,易读性变差
vue template complier 将模板编译为 render 函数
执行 render 函数生成 vnode
vue的模板不是 html,有指令,插值,js表达式,判断,循环
因此,模板一定是转换为某种 js 代码,即编译模板
vue组件可以用 render 代替 template
vue-template-compiler
模板 通过 compiler 编译成 render 函数,执行函数生成 vnode,再到渲染和更新
基于 vnode 再执行 patch 和 diff
这里面的 _c 等同于 createElement 等同于 h函数
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;
}
组件 渲染/更新/过程
- 初次渲染过程
- 解析模板为 render 函数 (或在开发环境已完成,vue-loader)
- 触发响应式,监听 data 属性 getter setter
- 执行 render 函数,生成 vnode,patch(elem,vnode)
- 执行 render 函数会触发 getter
- 更新过程
- 修改 data,触发 setter ( 此前在getter 中已被监听 )
- 重新执行 render 函数
- patch (vnode, newVnode)
- 异步渲染
- $nextTick 待 DOM 渲染完再回调
- 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
- 汇总 data 的修改,一次性更新视图
- 减少 DOM 操作次数,提高性能
Vue3
Vue3对比Vue2的优势
- 性能更好
- 体积更小
- 更好的ts支持
- 更好的代码组织
- 更好的逻辑抽离
- 更多新功能
Options API 生命周期
- destroyed 改为 unmouted
- beforeDestroy 改为 beforeUnmount
- 其他沿用 Vue2 的声明周期
//使用setup() 替代
beforecreate -> use setup()
created -> use setup()
//前面加on
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onbeforeUpdate
updated -> onUpdated
activated -> onActivated
deactivated -> onDeactivated
errorCaptured -> onErrorCaptured
//更改名称
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
//新加入,主要用于开发阶段调试使用
onRenderTracked
onRenderTriggered
Composition API 对比 Options API
- Composition API
- 更好的代码组织
- 更好的逻辑复用
- 更好的类型推导
理解 ref / toRef / toRefs
ref
- 生成值类型的响应式数据
- 可用于模板和 reactive
- 通过 .value 修改值
toRef
针对一个响应式对象(reactive 封装)的 prop
创建一个 ref,具有响应式
两者保持引用关系
toRef 如果用于普通对象(非响应式对象),产出的结果不具备响应式
const state = reactive({
age:10,
name:'xx'
})
const ageRef = toRef(state,'age') //单个
//修改其一都会联动
state.age = 20
ageRef.value = 30
toRefs
将响应式对象 (reactive 封装)转换为普通对象
对象的每个 prop 都是对应的 ref
两者保持引用关系
//不能直接结构使用,需要 toRefs
const state = reactie({
age:10,
name:'xx'
})
const stateAsRefs = toRefs(state) //整个
const {age:ageRef,name:nameRef} = stateAsRefs //每个属性 都是ref响应式对象
return {
ageRef,
nameRef
}
return stateAsRefs
return {
...stateAsRefs
}
return toRefs(state)
最佳使用方式
- 用 reactive 做对象的响应式,用 ref 做值类型响应式
- setup 中返回 toRefs(state),或者 toRef(state,'xx')
- ref 的变量命名都用 xxxRef
- 合成函数返回响应式对象时,使用 toRefs,有助于使用方对数据进行结构
6-9
路由
路由模式
hash
hash 变化会触发网页跳转,即浏览器的前进和后退
hash 变化不会刷新页面,SPA 必需的特点
hash 永远不会提交到 server 端
window.onhashchange
window.onhashchange = (event) =>{
console.log('old url:',event.oldURL)
console.log('new url:',event.newURL)
console.log('hash:',event.hash)
}
H5 history
用 url 规范的路由,但跳转时不刷新页面
history.pushState 向浏览器历史添加了⼀个状态(增加⼀个记录)
history.replaceState 把当前的页⾯的历史记录替换掉
window.onpopstate
// 页面初次加载,获取 path
document.addEventListener('DOMContentLoaded', () => {
console.log('load', location.pathname)
})
// 打开一个新的路由
// 用 pushState 方式,浏览器不会刷新页面
document.getElementById('btn1').addEventListener('click', () => {
const state = { name: 'page1' }
console.log('切换路由到', 'page1')
history.pushState(state, '', 'page1') // 重要
})
// 监听浏览器前进、后退
window.onpopstate = (event) => { // 重要
console.log('onpopstate', event.state, location.pathname)
}
总结
v-show 和 v-if
- v-show 通过 CSS display 控制显示和隐藏
- v-if 组件真正的渲染和销毁
- 频繁的切换显示状态用 v-show,否则用 v-if
v-for 中的 key
- 必须用 key,且不能是 index 和 random
- diff 算法中 通过 tag 和 key 来判断 ,是否是sameNode
- 减少渲染次数,提升渲染性能
Vue组件中生命周期(父子组件)
- 单组件生命周期图
- 父子组件生命周期关系
Vue组件通信
- 父子组件 props 和 $emit
- 自定义事件 event.$on 、event.$emit、event.$off
- Event.Bus
- vuex
v-model实现原理
- input 元素的 value = this.name
- 绑定 input 事件 this.name = $event.target.value
- data 更新 触发 re-render
computed 特点
- 缓存,data 不变不会重新计算
- 提高性能
ajax请求放在哪个生命周期
- mounted
- 放在 mounted 之前没用,只会让逻辑更加混乱
将组件所有props传递给子组件
- $props
<User v-bind="$props" />
beforeDestory
- 解除自定义事件 event.$off
- 清除定时器
- 解绑自定义的 DOM 事件,如 window,scroll 等
作用域插槽
- 将子组件的 data 传递给父组件,子组件:
<slot :user="user">
- 父组件通过
<template v-slot='slotProps'>
接收
Vuex中 action 和 mutation
- action 支持异步,mutation 不支持
- Mutation 做原子操作(每次只做一个操作)
- action 可以整合多个 mutation
Vue-router 异步加载
- component:()=>import('./xxxxx')
Vue监听数组变化
- Object.definedProperty 不能监听数组变化
- 重新定义原型,重写 push pop 等方法,实现监听
- Proxy 可以原生支持监听数组变化