开发要点-Vue3.0通用知识学习
2020-09-18 发布的Vue3.0正式版
建议先学ts;因为Vue3.0框架多数都是用ts重写的;
API文档仔细阅读;
Vue3亮点:
- Performance:性能更快;
- Tree shaking support:按需编译,体积更小;
- Composition API:组合API(类似React Hooks);
- Better TypeScript support:更好的ts支持;
- Custom Renderer API:暴露了自定义渲染API;
- Fragment Teleport(Protal) Suspense:更先进的组件;
Vue3是如何变快的:
- diff优化:
- Vue2中虚拟Dom是进行全量对比;
- Vue3新增静态标记(PatchFlag),虚拟dom比对时,只对比带有patch flag的节点,而且可以通过flag的信息得知当前接待你要对比的具体内容;
- hoistStatic 静态提升:
- Vue2中无论元素是否参与更新,每次都会重新创建;
- Vue3中对不参与更新的元素,只会创建一次,之后会在每次渲染时候被不停的复用;
- cacheHandlers 事件侦听器缓存
- 默认情况下onClick会被视为动态绑定(静态标记是8),所以每次都回去追踪它的变化;使用缓存后,就不会进行静态标记,也就不会再追踪了,因为是同一函数缓存复用即可;
- ssr渲染:
- 当有大量静态的内容时,这些内容会被当做纯字符串推进一个buffer里;即使存在动态绑定,也会通过模板插值嵌入;比通过虚拟Dom来渲染的快很多;
- 当静态内容大到一定程度时,会用
_createStaticVNode
方法在客户端生成一个static node,它们会被直接innerHtml,无需创建对象;
PatchFlags 静态标记
Vue3.0的diff在创建虚拟Dom的时候会根据DOM中的内容会不会发生变化,添加静态标记;
PatchFlags:
export const enum PatchFlags {
TEXT = 1, // 动态文本节点
CLASS = 1 << 1, // 2 动态class
STYLE = 1 << 2, // 4 动态style
PROPS = 1 << 3, // 8 动态属性,但不包含类名和样式
FULL_PROPS = 1 << 4, // 16 具有动态key属性, 当key改变时,需要进行完整的diff比较;
HYDRATE_EVENTS = 1 << 5, // 32 带有监听事件的节点
STABLE_FRAGMENT = 1 << 6, // 64 一个不会改变子节点顺序的fragment
KEYED_FRAGMENT = 1 << 7, // 128 带有key属性的fragment 或 部分子节点有key
UNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有key的fragment
NEED_PATCH = 1 << 9, // 512 一个节点只会进行非props比较
}
可以通过探测Vue代码转换为js之后的内容来观察Vue3中添加的静态标记;
创建Vue3
创建方式:Vue-Cli、Webpack、Vite
基于Webpack快速创建的项目:
git clone https://github.com/vuejs/vue-next-webpack-preview.git projectName
cd projectName
npm install
npm run dev
基于Vue-Cli快速创建的项目:
npm install -g @vue/cli
vue create projectName
cd projectName
vue add vue-next
npm run serve
什么是Vite:
- Vite是vue作者开发的意图取代webpack的工具;
- 原理:利用ES6的import会发送请求去加载文件的特性,拦截请求,做一些预编译,省去webpack冗长的打包时间;
// 安装Vite
npm install -g create-vite-app
// 通过Vite创建Vue3项目
create-vite-app projectName
// 安装运行时依赖
cd projectName
npm install
npm run dev
Vue3 兼容 Vue2:
- 新的->按需导入
- 新的->组合API
- Vue2中每新建一个业务逻辑,一般需要在data中加数据,在methods/computed/watch中处理相应的逻辑;
- 问题是,当需要处理的业务逻辑很多时,就会维护困难;
- 未解决这个问题,Vue3引入可组合API
Object.assign(newObj, oldObj)
API可以实现数据的浅拷贝;
组合API —— Composition API
Vue2中的API一般叫做,Option API
;
Vue3的Composition API
:(也叫注入API)
- 将封装好的具有独立功能的函数组合在一起使用;
- 对组合API的理解 参看如下案例:
<script>
// ref 函数只能监听简单数据类型的响应式变化
import { ref } from 'vue';
// reactive 函数则可以监听 对象/数组 这样的复杂数据类型的响应式变化
import { reactive } from 'vue'
import useStuAdd from './useStuAdd.js'
export default {
name:'App',
// 组合API的入口函数(这是一个同步函数)
setup(){
// 组合1
let {
count,
addCount,
} = useCount();
// 组合2
let {
states,
} = useStus();
// 组合3
let {
stuInfo,
addStu
} = useStuAdd(states);
return {
//
count,// 暴露出的变量 将被注入到 Option API的 data中
addCount,// 暴露出的方法 将被注入到 Option API的 methods中
//
states,
//
stuInfo,
addStu
}
}
}
// 组合1
function useCount(){
// 定义变量 值使用ref函数包裹 获取响应式(响应双向绑定)
// count实际是一个对象 使用value属性获取值
let count = ref(0);
// 定义方法
function addCount(){
count.value += 1;
}
// 组合API中定义的变量或方法 必须通过 return {xxx,xxx}暴露出去
return {
count,
addCount,
};
}
// 组合2
function useStus(){
// 定义复杂数据类型变量
let states = reactive({
stus:[],
})
// 组合API中定义的变量或方法 必须通过 return {xxx,xxx}暴露出去
return {
states,
};
}
</script>
这些组合还可以定义到其他单独文件模块中:
import { reactive } from 'vue'
// 组合3
function useStuAdd(state){
let stuInfo = reactive({
id:'',
name:'',
age:0
});
function addStu(){
let stuOne = Object.assign({}, stuInfo);
state.stus.push(stuOne);
}
return {
stuInfo,
addStu
}
}
export default useStuAdd;
关于setup函数
setup函数执行时机:
setup -> beforeCreate -> created
;- setup:在setup函数中,无法使用data和methods数据;
setup
中的this
被修改为undefined
;- setup只能是同步函数;
- beforeCreate:组件被创建出来,组件的data和methods还没有初始化;
- created:组件的data和methods已经初始化;
Vue3中的reactive函数
reactive是Vue3中提供的实现响应式数据的方法:
- 在Vue2中响应式数据是通过
defineProperty
修改属性标识符来诗选的; - Vue3中响应式数据 则是通过ES6的Proxy来实现;
reactive函数的注意点:
- 参数为对象/json/arr;即将传入的对象包装在Proxy中;
- 如果不传对象,那么数据改变时,不会触发响应式的页面变化;
- 传入的数组,通过下标修改数据也是支持的;
- 值得注意的是,如果传入的对象/数组,包含其他对象,比如
new Date()
传入;- 这类的值需要重新赋值才能触发响应式,直接修改则无效;
setup(){
let date = reactive({
time:new Date()
});
function changeDate(){
// 直接修改(无法触发页面变化)
date.time.setDate(date.time.getDate() + 1);
// 重新赋值
const newTime = new Date(date.time.getTime());
date.time = newTime.setDate(date.time.getDate() + 1);
}
return {date,changeDate};
}
Vue3中的ref函数
ref和reactive一样,也是用来实现响应式数据的方法:
- reactive必须传递一个对象;ref方法,则实现对简单值的监听(
也可以是对象
); - ref底层还是reactive,系统会根据传入的值进行转换:
let age = ref(18) ->let age = reactive({value:18})
- 如果在template里使用ref类型数据(
__v_ifRef:true
),会自动添加.value; - 如果在template里使用reactive类型数据,不会自动添加.value;
- 如果在template里使用ref类型数据(
- 在Vue template模板中使用ref的值,直接用即可,无需通过value获取;
- 在js中使用ref的值,则必须通过value获取(ref返回的实际是一个对象);
对于setup函数中的数据,可以使用vue提供的isRef、isReactive函数进行类型判断:
import {ref,reactive} from 'vue';
import {isRef,isReactive} from 'vue';
setup(){
// ...
isRef(age);// true
isReactive(state);// true
}
递归监听
通过ref和reactive处理的对象,都是递归监听的:
- 包含对象嵌套的state数据,每一层都是被监听的;如果数据量比较大,会很耗性能;
- 外层对象包装成一个Proxy对象,里边的每一层嵌套,也都递归进行Proxy包装;
非递归监听
仅能监听一层:
- 创建非递归监听,使用
shallowReactive
和shallowRef
; - 需要注意的是,如果通过shallowRef创建的数据是嵌套对象,那么Vue监听的是.value的变化,而不是第一层数据,.value发生变化会触发页面重新渲染;shallowReactive监听的是第一层数据,第一层数据变化会触发页面的重新渲染;
如果在非递归监听下修改了嵌套的对象值,还想触发页面的重新渲染,可以使用triggerRef
函数:
- 注意,vue只提供了
triggerRef
函数,而没有提供triggerReactive
函数,也就是说,没有办法主动触发reactive类型数据变化之后的页面渲染; - 虽然没有提供
triggerReactive
函数,但这个函数确实存在,shallowRef
类型数据实际就会转换为triggerReactive
类型数据去处理;
import { shallowRef, triggerRef } from 'vue'
let state = shallowRef({...});
// ...
state.value.a.b = 'b';
trggierRef(state);
// ...
注意:正常使用递归监听即可,只有数据嵌套很多时,才会考虑使用非递归监听;
toRaw方法
Proxy对象的变化,会引起界面响应式的修改;但如果直接修改引用对象,并不会触发;
toRaw的作用就是,获取响应式数据引用的原始数据:
- 在某些不需要响应式变化的地方,还想修改响应式的数据,就可以通过toRaw拿到原始数据,来进行操作;
// reactive类型的原始数据获取
import {reactive, toRaw} from 'vue'
setup(){
let obj1 = {name:'haha',age:18};// 原始数据
let state = reactive(obj1);// Proxy对象
let obj2 = toRaw(state);// state引用的原始对象
obj1 === obj2 // true
return {state};
}
// ref类型的原始数据获取
import {ref, toRaw} from 'vue'
setup(){
let obj1 = {name:'haha',age:18};// 原始数据
let state = ref(obj1);// Proxy对象
let obj2 = toRaw(state.value);// state引用的原始对象
obj1 === obj2 // true
return {state};
}
markRaw方法
markRaw 处理之后的数据 再也不会被追踪 也不会产生响应式的变化
// reactive类型的原始数据获取
import {reactive, markRaw} from 'vue'
setup(){
let obj = {name:'haha',age:18};// 原始数据
obj = markRaw(obj);// markRaw 处理之后的数据 再也不会被追踪 也不会产生响应式的变化
let state = reactive(obj);// Proxy对象
function changeState(){
state.name = 'heihei';// 这个赋值操作既不会生效 也不会引起页面响应式的变化;
}
return {state, changeState};
}
toRef 和 toRefs
toRef:可以把响应式数据与以前的数据关联起来,而且修改响应式数据还不会更新UI;
ref:
- 复制,修改响应式数据state.value,obj中name不会改变;
- 修改响应式数据state.value,页面自动更新;
toRef、toRefs:
- 引用,修改响应式数据state.value,obj中name也会改变;
- 修改响应式数据state.value,页面不会自动更新;
// ref
let obj = {name:'haha'};
let state = ref(obj.name);//注意 这里响应式处理的只是obj的一个属性 相当于复制了obj.name的值
state.value = 'heihei';//原始数据 obj.name 不受影响,界面上绑定的数据显示也会改变
// toRef(也是一种变成响应式数据的方式)
let obj = {name:'haha'};
let state = toRef(obj, 'name');
state.value = 'heihei';// 原始数据 obj.name 也会发生变化,界面上绑定的数据显示不受影响
// toRefs
let obj = {name:'haha', age:18};
let state = toRefs(obj);
state.name.value = 'heihei';
state.age.value = 20;
customRef
自定义一个Ref:myRef
import { customRef } from 'vue'
function myRef(value, delay = 200){
let timeout//这个延时可以不要
return customRef((track, trigger) => {
retrun {
get(){
track()//当前值需要被追踪
return value
},
set(newValue){
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()// 触发响应式变化
}, delay)
}
}
})
}
...
setup(){
return {
text:myRef('DM')// 用起来和Ref一样 只是自定义的
}
}
自定义一个Ref的意义:
- 对于需要异步获取的数据A,如果要把它对应成响应式的数据,正常需要在setUp中编写相应的业务网络请求代码,将接口返回数据用准备暴露出去的响应式对象接收;
let state = ref([])
state.value = A
return { state }
- 使用自定义的Ref可以进一步将业务分离,直接将异步获取的对象转换成响应式的对象
import { customRef } from 'vue'
function myRef(value, delay = 200){
// 传入的value是一个接口请求的url
let timeout//这个延时可以不要
return customRef((track, trigger) => {
// 这里使用es6的fetch进行网络请求
fetch(value).then(res => {
value = data
trigger()
}).catch(err => {
console.log(err)
})
retrun {
get(){
track()//当前值需要被追踪
return value
},
set(newValue){
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()// 触发响应式变化
}, delay)
}
}
})
}
...
setup(){
return {
A:myRef('../list')// 通过接口获取的服务端对象 通过自定义Ref直接被处理成响应式的数据了
}
}
理解更多
ref获取元素
Vue2.0
<div ref="box">test</div>
...
// 获取组件
this.$ref.box
Vue3.0
<div ref="box">test</div>
...
import { ref, onMounted } from 'vue';
setUp(){
let box = ref(null);
// 抽离的生命周期函数 需要传入一个回调函数
onMounted(() => {
console.log(box.value)
})
// 直接在setUp方法中打印组件信息是无效的 因为此时处于的生命周期在created之前
return { box }
}
readonly isReadonly shallowReadonly
用于把对象处理成只读的响应式数据:
- readonly:递归只读处理
- shallowReadonly:第一层的只读处理(嵌套数据变化 不会引起页面显示变化)
- isReadonly:判断是不是只读数据;