Vue3.0的六大亮点:
- Performance:性能比 Vue2.x 快 1.2~2 倍
- Tree shaking support:按需编译,体积比 Vue2.x更小
- Composition API:组合API(类似 React Hooks)
- Better TypeScript support:更好的 Ts 支持
- Custom Renderer API:暴露了自定义渲染API
- Fragment,Teleport(Protal),Suspense:更先进的组件
Vue3.0变的更快:
-
diff 方法优化:
- Vue2.x中的虚拟DOM是进行全量对比
- Vue3.0新增了静态标记(PatchFlag),虚拟DOM对比时,只对比带有patch flag 的节点
export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("p", null, "你好,小明"), _createVNode("p", null, "你好,小明"), _createVNode("p", null, "你好,小明"), _createVNode("p", null, "你好," + _toDisplayString(_ctx.name), 1 /* TEXT */) ])) } 最后一个_createVNode中有个数字1,就是静态标记
-
hoistStatic 静态提升:
- Vue2.x中无论元素是否参与更新,每次都会重新创建
- Vue3.0中对于不参与更新的元素,只会被创建一次,之后会在渲染时被复用
静态提升之前: export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("p", null, "你好,小明"), _createVNode("p", null, "你好,小明"), _createVNode("p", null, "你好,小明"), _createVNode("p", null, "你好," + _toDisplayString(_ctx.name), 1 /* TEXT */) ])) } 静态提升之后: const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "你好,小明", -1 /* HOISTED */) const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "你好,小明", -1 /* HOISTED */) const _hoisted_3 = /*#__PURE__*/_createVNode("p", null, "你好,小明", -1 /* HOISTED */) export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _hoisted_1, _hoisted_2, _hoisted_3, _createVNode("p", null, "你好," + _toDisplayString(_ctx.name), 1 /* TEXT */) ])) }
-
cacheHandlers 事件侦听器缓存:
- 默认情况下onClick会被视为动态绑定,所以每次都会追踪它的变化。
但因为是同一个函数,所以没有追踪变化,直接缓存服用。
<div> <button @click="onClick">按钮</button> </div> 事件监听缓存之前: export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("button", { onClick: _ctx.onClick }, "按钮", 8 /* PROPS */, ["onClick"]) ])) } 事件监听缓存之后: export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("button", { onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick && _ctx.onClick(...args))) }, "按钮") ])) }
- 默认情况下onClick会被视为动态绑定,所以每次都会追踪它的变化。
-
ssr 渲染:
-
当有大量静态的内容时候,这些内容会被当做纯字符串推进一个buffer里面,即使存在动态的绑定,会通过模板插值嵌入进去。这样会比通过虚拟DOM来渲染的快上很多很多。
-
当静态内容大到一定量级时候,会用 _createStaticVNode 方法在客户端去生成一个static node,这些静态的node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。
-
创建Vue3.0项目
-
Vue-CLI的方式:
- npm install -g @vue/cli
- vue create projectName
- cd projectName
- npm install
- vue add vue-next (如果VUCLI的版本比较低,会安装失败,安装最新版本VUECLI即可) 现在vue cli 默认支持 vue3.0,所以可以省略该步骤
- npm run serve
-
Vite方式:
- npm install -g create-vite-app
- create-vite-app projectName(项目名称)
- cd projectName
- npm install
- npm run serve
组合API (composition API)
setup() 函数
-
该方法是在 beforecreate 钩子之前完成的
-
是组合API的入口,该函数执行时尚未创建组件实例,所以没有this。
-
在组合API中定义的变量/方法,想在外界使用,必须通过 return 暴露。
-
如果函数返回对象,对象中的变量/方法,可以直接在模板中使用。
-
函数只能监听简单类型的变化,不能监听复杂类型的变化(对象/数组)
-
函数只能是同步的,不能是异步的
ref
- 作用:定义一个响应式数据。
- 语法:const xxx = ref(初始值)
- 在js中使用ref的值必须通过value获取
- 在模板中使用不用通过value获取
- 修改响应式数据(( 值类型 / 对象的属性 / 数组的某个值 ))是不会影响到原始数据的
<template>
<div class="home">
<p>{{count}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import { ref } from "vue";
export default {
name: 'Home',
setup(){
// 定义一个count变量,初始值为0
let count = ref(0);
// 在组合API中,如果想定义方法,不用定义到methods中,直接定义即可。
function myFn(){
count.value += 1;
}
//注意点:在组合API中定义的变量/方法,要想在外界使用,必须通过return {xxx,xxx} 暴露出去
return {count,myFn}
}
}
</script>
reactive
-
是Vue3中提供的实现响应式数据的方法,本质:将传入的数据包装成一个Proxy对象
-
Vue2.x中响应式数据是通过defineProperty来实现的
-
Vue3中响应式数据是通过ES6的Proxy来实现的
-
参数必须是对象(json/array)
-
如果给reactive传递了其它对象(其它类型数据):
- 默认情况下修改对象,界面不会自动更新
- 如果想更新,可以通过重新赋值的方式
<template>
<div class="home">
<p>{{stateage.time}}</p>
<button @click="myFn">按钮</button>
<ul>
<li v-for="(stu,index) in state.stus" :key="stu.id"
@click="removeStu(index)">{{stu.name}} -- {{stu.age}}</li>
</ul>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
name: 'Home',
// setup函数是组合API的入口函数
setup(){
// 创建一个相应式数据
// 本质:将传入的数据包装成一个 Proxy 对象。
// let stateage = reactive(123); // 普通数据类型
// let stateage = reactive([1,2,3]) // 数组
// let stateage = reactive({ // json 对象
// age:10
// });
let stateage = reactive({
time:new Date()
}) // 其它对象
function myFn(){
// stateage = 666 // 由于创建时不是一个对象,所以无法实现响应式。
// stateage[0] += 1; // 可以实现响应式
// stateage.age += 1; // 可以实现响应式
// 修改以前的,界面不会更新
stateage.time.setDate(stateage.time.getDate() + 1);
// 重新赋值
const newTime = new Date(stateage.time.getTime());
newTime.setDate(stateage.time.getDate() + 1);
stateage.time = newTime;
console.log(stateage.time)
}
// 第一种写法:
// let state = reactive({
// stus:[
// {id:1,name:"张三",age:10},
// {id:2,name:"李四",age:20},
// {id:3,name:"王五",age:30},
// ]
// });
// function removeStu(index){
// state.stus = state.stus.filter((stu,idx) => idx !== index);
// }
// 第二种写法:
let {state,removeStu} = useRemoveStudent();
return {state,removeStu,stateage,myFn}
}
}
function useRemoveStudent(){
let state = reactive({
stus:[
{id:1,name:"张三",age:10},
{id:2,name:"李四",age:20},
{id:3,name:"王五",age:30},
]
});
function removeStu(index){
state.stus = state.stus.filter((stu,idx) => idx !== index);
}
return {state, removeStu}
}
</script>
isRef 与 isReactive
- isRef 方法可以判断数据是否是 ref 对象
- isReactive 方法可以判断数据是否是 reactive 对象
// 判断是否是 ref 对象
function isRef(obj){
// 如果是 ref 对象 返回 true ,不是返回 false
// return Boolean(obj && obj.__v_isRef === true);
// 如果是 ref 对象 返回 true ,不是返回 undefined
return obj && obj.__v_isRef;
}
// 判断是否是 reactive 对象
function isReactive(obj){
// 如果是 reactive 对象 返回 true ,不是返回 false
// return Boolean(obj && obj.__v_isReactive === true);
// 如果是 reactive 对象 返回 true ,不是返回 undefined
return obj && obj.__v_isReactive;
}
// 测试
console.log(isRef(ref(0))) // true
console.log(isRef(reactive({}))) // false
console.log(isReactive(ref(0))) // false
console.log(isReactive(reactive({}))) // true
<template>
<div class="home">
<div>{{age}}</div>
<div>{{name.value}}</div>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {isRef,isReactive, reactive,ref} from "vue";
export default {
name: 'Home',
setup(){
let age = ref(18);
let name = reactive({
value:"小明"
})
function myFn(){
console.log(isRef(age)); // true
console.log(isRef(name)); // false
console.log(isReactive(age)); // false
console.log(isReactive(name)); // true
age.value += 1;
name.value = "小张";
}
return { age, name, myFn }
}
}
</script>
递归监听
- 默认情况下,无论是通过ref和reactive都是递归监听
- 问题:数据量较大时,非常耗性能
递归监听
<template>
<div class="home">
<p>{{stateref.a}}</p>
<p>{{stateref.gf.b}}</p>
<p>{{stateref.gf.f.c}}</p>
<p>{{stateref.gf.f.s.d}}</p>
<p>------------------------</p>
<p>{{statereact.a}}</p>
<p>{{statereact.gf.b}}</p>
<p>{{statereact.gf.f.c}}</p>
<p>{{statereact.gf.f.s.d}}</p>
<p>--------------------</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {reactive,ref} from "vue";
export default {
name: 'Home',
setup(){
let stateref = ref({
a:"a",
gf:{
b:"b",
f:{
c:"c",
s:{
d:"d"
}
}
}
});
let statereact = reactive({
a:"a",
gf:{
b:"b",
f:{
c:"c",
s:{
d:"d"
}
}
}
});
function myFn(){
stateref.value.a = 1;
stateref.value.gf.b = 2;
stateref.value.gf.f.c = 3;
stateref.value.gf.f.s.d = 4;
statereact.a = "1";
statereact.gf.b = "2";
statereact.gf.f.c = "3";
statereact.gf.f.s.d = "4";
}
return { stateref, statereact, myFn}
}
}
</script>
shallowReactive 和 shallowRef、triggerRef
-
shallowReactive 和 shallowRef 可以实现非递归监听,只监听数据的第一层的变化
-
shallowRef 的本质
:shallowRef -> shallowReactive。即:shallowRef(10) -> shallowReactive({ value:10 }) -
注意点:如果通过 shallowRef 创建的数据,vue监听的是 .value 的变化,并不是数据的第一层的变化,因为本质上 value 才是第一层。
-
triggerRef 可以实现更改 shallowRef 中非第一层的数据并更新页面。
-
注意点:vue3 只提供了 triggerRef 方法,没有提供 triggerReactive 方法,所以如果是 reactive 类型的数据,那么是无法主动触发界面更新的。
toRaw 和 markRaw
-
toRaw:
从 Reactive 或 Ref 中得到原始数据。 -
作用:做一些不想被监听的事情 ( 提升性能 )
-
注意点:如果想通过 toRaw 拿到 ref 类型的原始数据(创建传入的数据),就必须明确告诉 toRaw 方法,要获取的是 .value 的值,因为经过vue处理之后,.value 中保存的才是当初创建时传入的原始数据。
import { toRaw, reactive, ref } from "vue";
export default {
setup(){
let obj = {name:'lnj',age:18};
let state = reactive(obj);
let obj2 = toRaw(state);
console.log(obj2 == obj) // true
console.log(obj === state); // false
console.log(obj == state); // false
// let state = ref(obj);
// let obj2 = toRaw(state);
// console.log(obj2 === obj) // false
// let obj2 = toRaw(state.value)
// console.log(obj2 === obj) // true
}
}
- state和obj的关系:引用的关系,state的本质是一个 Proxy 对象,在这个 Proxy 对象中引用了 obj。
- 如果直接修改obj,是无法触发界面更新的。只有通过包装之后的对象来修改,才会触发界面的更新。
markRaw:
使 Reactive 或 Ref 无法将数据创建(转化)为响应式数据- 作用:将数据变为不可被追踪监听的非响应式数据。
toRef 和 toRefs
-
ref:
如果利用 ref 将定义的数据( 值类型 / 对象的属性 / 数组的某个值 )变成响应式的数据,修改响应式数据是不会影响到原始数据的。会触发UI界面更新。 -
toRef:
如果利用 toRef 将定义的数据( 值类型 / 对象的属性 / 数组的某个值 )变成响应式数据,修改响应式数据是会影响到原始数据的。但是如果响应式数据是通过 toRef 创建的,那么修改了数据并不会触发UI界面的更新。 -
toRefs:
可以同时监听整个对象的属性 / 数组,改变响应式数据时,会改变原始数据,不会触发UI更新。
customRef
-
返回一个 ref 对象,可以显示地依赖和触发响应
-
注意点:
不能在get方法中发送网络请求
import {customRef} from "vue";
function myRef(value){
return customRef((track,trigger)=>{
return {
get(){
track(); // 告诉 vue 这个数据是需要追踪变化的
console.log('get',value);
return value
},
set(newvalue){
console.log('set',newvalue);
value = newvalue;
trigger(); // 告诉vue触发界面更新
}
}
})
}
export default {
setup(){
let age = myRef(18);
function myFn(){
age.value += 1;
}
return {state,myFn}
}
}
ref 获取元素
- vue2.x 中我们可以通过给元素添加
ref='xxx'
然后在代码中通过refs.xxx
的方式来获取元素 - 在 vue3.x 中我们也可以通过 ref 来获取元素
<template>
<div class="ref_get">
<div ref="box">我是div</div>
</div>
</template>
<script>
import {ref,onMounted} from "vue";
// onMounted 监听dom是否渲染完成
export default {
setup(){
let box = ref(null);
onMounted(()=>{
console.log('onMounted',box.value);
})
console.log(box.value);
return {box}
}
}
</script>
readonly 和 isReadonly、shallowReadonly
-
readonly:
用于创建一个只读数据,并且是递归只读 -
shallowReadonly:
用于创建一个只读的数据,但不是递归只读,只是数据第一层只读。 -
isReadonly:
判断数据是否是只读的。 -
const
和readonly
区别:-
const:
赋值保护,不能给变量重新赋值 -
readonly:
属性保护,不能给属性重新赋值。
-
<template>
<div class="readonly">
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {readonly, isReadonly, shallowReadonly} from "vue"
export default {
setup(){
let state = readonly({
name:'lng',
attr:{
age:18,
height:180
}
});
let shallstate = shallowReadonly({
name:'lng',
attr:{
age:18,
height:180
}
});
console.log(isReadonly(state));
function myFn(){
// state.name = "zs"; // 修改失败,警告
// state.attr.age = 20; // 修改失败,警告
// shallstate.name = "zs"; // 修改失败,警告
shallstate.attr.age = 20; // 修改成功
console.log(shallstate.attr.age);
}
return { state,shallstate,myFn }
}
}
</script>
手写组合API
reactive
<script>
export default {
setup(){
function ref(val){
return reactive({value:val})
}
function reactive(obj){
if(typeof obj === 'object'){
if(obj instanceof Array){
/**
* 如果是一个数组,那么取出数组中的每一个元素
* 判断每一个元素是否又是一个对象,如果又是一个对象,那么也需要包装成 Proxy
*/
obj.forEach((item,index) => {
if(typeof item == 'object'){
obj[index] = reactive(item)
}
})
}else{
/**
* 如果是一个对象,那么取出对象中的每一个属性的值
* 判断每一个属性的值是否又是一个对象,如果又是一个对象,那么也需要包装成 Proxy
*/
for (const key in obj) {
let item = obj[key];
if(typeof item == 'object'){
obj[key] = reactive(item);
}
}
}
return new Proxy(obj,{
get(obj,key){
return obj[key]
},
set(obj,key,value){
obj[key] = value;
console.log('更新UI界面')
return true
}
})
}else{
console.warn(`message:${obj} is not object`)
}
}
}
}
</script>
shallowReadonly
function shallowReadonly(obj){
return new Proxy(obj,{
get(obj,key){
return obj[key]
},
set(obj,key,val){
console.warn(`message:${obj} 的 ${key} is onlyread`)
}
})
}
shallowRef 和 shallowReactive
function shallowRef(val){
return shallowReactive({value:val})
}
function shallowReactive(obj){
return new Proxy(obj,{
get(obj,key){
return obj[key]
},
set(obj,key,value){
obj[key] = value;
return true
}
})
}