前言:
对vue3.0进行一次整理
目录:
四.什么是Vite,这里如果要用vite可以用这个来创建项目,其他的直接执行升级命令
3.composition API和Option API的混合使用
8.toRef(),toRef和ref,toRef和toRefs
一.vue3.0的亮点
1.性能比vue2.x快1.2~2倍
①diff方法优化
②静态提升
③事件侦听器缓存
④ssr渲染
2.支持按需编译,按需导入,体积比vue2.x更小
3.支持组合API,类似React Hooks
4.更好的支持ts,因为他是ts写的
5.暴露了自定义渲染的API
6.提供过了更先进的组件
二.vue3.0是如何变快的
1. diff方法优化
- vue2.0中的虚拟dom是进行全量的对比
- vue3.0新增了静态标记(PatchFlag),在于上次虚拟节点进行对比的时候,只对比带有patch flag的节点。并且可以通过flag的信息得知当前节点要对比的具体内容。
-
验证:是否添加静态标记
vue3.0转换代码的网址:https://https://vue-next-template-explorer.netlify.app/
静态标记枚举类:
2.静态提升
- vue2.0无论元素是否参与更新,每次都会重新创建,然后再渲染
- vue3.0对于不参与更新的元素,会做静态提升,只会被新创建一次,在渲染时直接复用即可
验证:
默认情况下,每次都会重新创建createNode节点,这里我们选择hoisStatic。
会发现这些节点从render里面提取出来了,成为了全局变量。后面每次render的时候就可以复用
3.事件侦听器缓存
- 默认情况下,onClick会被视为动态绑定,所以每次都会去追踪它的变化。但是因为事件绑定的函数是同一个函数(同一个方法),所以没有追踪变化,直接缓存起来复用即可。
验证:
【注意】转换之后的代码,大家可能还看不懂,但是不要紧。因为只需要观察有没有静态标记即可,因为在Vue3的diff算法中,只有有静态标记的才会进行比较,才会进行追踪。
//事件监听缓存
<button @click="onClick">按钮</button>
//开启事件监听缓存之前(把静态提升关闭了也是这样)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("button", { onClick: _ctx.onClick }, "按钮", 8 /* PROPS */, ["onClick"])
]))
}
//开启事件监听缓存之后(把options选择为cacheHandlers)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("button", {
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
}, "按钮")
]))
}
4.ssr渲染
- 当有大量静态的内容时候,这些内容会被当做纯字符串推进一个buffer里面,即使存在动态绑定,会通过模板插值嵌入进去,这样会比通过虚拟dom来渲染的快很多很多。
- 当静态内容达到一定量级时候,会用_createStaticVNode方法在客户端去生成一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。
三.创建vue3.0的三种方式
1.Vue-CLI
- git clone https://github.com/vue.js/vue-next-webpack-preview.git ProjectName
- cd ProjectName
- npm install
- npm run dev
2.Webpack
- npm install -g @vue/cli
- vue create ProjectName
- cd ProjectName
- vue add vue-next
- npm run serve
3.Vite
1)安装Vite
npm install -g create-vite-app
2)利用Vite创建Vue3.0项目
create-vite-app projectName
【注意】我在在使用该命令的时候报错,当然不是所有人都遇到这个情况。不报错就不需要解决。
当时我就查了很多资料,有人说nodejs版本过低,需要升级。开始我升级了还是没有效果。
解决的办法:配置nodejs的环境变量
①在c\Program File\nodejs文件夹下创建node_cache和node_global这两个文件夹
②创建完两个空文件夹之后,打开cmd命令窗口,输入
npm config set prefix “C:\Program Files\nodejs\node_global”
npm config set cache “C:\Program Files\nodejs\node_cache”
③接下来设置环境变量,关闭cmd窗口,“我的电脑”-右键-“属性”-“高级系统设置”-“高级”-“环境变量”
④ 进入环境变量对话框,在【系统变量】下新建【NODE_PATH】,输入【C:\Program Files\nodejs\node_global\node_modules】,将【用户变量】下的【Path】修改为【C:\Program Files\nodejs\node_global】
最后,关掉黑窗口重启一下,再试一下create-vite-app programName的命令看下是否报错。
3)安装依赖运行项目
cd projectName
npm install
npm run dev
四.什么是Vite,这里如果要用vite可以用这个来创建项目,其他的直接执行升级命令
- vite是vue作者开发的一款取代webpack的工具
- 其实现原理是利用es6的import会发送请求去加载文件的特性,拦截这些请求,做一些预编译,省去webpack冗长的打包时间
1.安装vite
cnpm install -g create-vite-app
2.利用vite创建vue3.0项目
create -vite -app projectName
3.安装依赖运行项目
cd projectName
npm install
五.组合API
vue2.x存在的问题:
vue3.x的组合API:
setup函数是组合API的入口函数
1.ref()
1)什么是ref ?
ref和reactive一样,也是用来实现响应式数据的方法。由于reactive必须传递一个对象,所以导致在企业开发中,如果我们只想让某个变量实现响应式的时候会非常麻烦,所以vue3就给我们提供了ref方法,实现对简单值的监听。
2)ref的本质
ref底层的本质其实还是reactive。当我们给ref函数传递一个值之后,ref函数底层会自动将ref转换成eactive,如ref(10)—>reactive({value:18})。
注意:
①如果是通过tref创建的数据,那么在template中使用的时候不用通过.value来获取,因为vue会自动给我们添加.value。
②但在js中使用ref函数必须通过value获取。
3)ref的功能和使用方法
功能:只能监听简单类型的变化
使用方法:import 引入{ref},return 暴露出去。获取ref的变量需要.value
<script>
import {ref} from 'vue'
export default {
name: 'App',
setup(){
let count=ref(0);
function changeBtn(){
count.value++;
}
return {count,changeBtn}
}
}
</script>
2.reactive()函数
1)什么是reactive?
①reactive是vue3中提供的实现响应式数据的方法;
②在vue2中响应式数据是通过defineProperty来实现的,而在vue3中响应式数据是通过es6的Proxy来实现的。
2)reactive注意点:
①reactive参数必须是对象(obj/json/arr)
②如果给reactive传递了其他对象(比如:日期对象),默认情况下修改该对象,界面不会更新;如果想更新,可以通过重新赋值的方式
3)功能和使用方法
功能:监听对象和数组的变化
使用方法:import 引入{reactive},return 暴露出去方法名称。
<script>
import {ref} from 'vue'
import {reactive} from 'vue'
export default {
name: 'App',
setup(){
let {state,remStu}=useRemoveStudent()
return {state,remStu}
}
}
//整个相关的方法和变量都写在一起了
function useRemoveStudent(){
let state=reactive({
stus:[
{id:1,name:'张三',age:10},
{id:2,name:'李四',age:20},
{id:3,name:'王五',age:30},
]
});
function remStu(index){
state.stus=state.stus.filter((stu,idx)=>idx!==index);
}
return {state,remStu}
}
</script>
组合API的思想:
整个相关的方法和变量都写在一起了
demo2:删除和新增用户
<template>
<div>
<form action="">
<input type="text" v-model="state2.stu.id">
<input type="text" v-model="state2.stu.name">
<input type="text" v-model="state2.stu.age">
<input type="submit" @click="addStu">
</form>
<ul>
<li v-for="(stus,index) in state.stus" :key="stus.id"
@click="remStu(index)">
{{stus.name}}--{{stus.age}}
</li>
</ul>
</div>
</template>
<script>
import {ref} from 'vue'
import {reactive} from 'vue'
export default {
name: 'App',
setup(){
let {state,remStu}=useRemoveStudent()
let {state2,addStu}=useAddStudent(state)
return {state,remStu,state2,addStu}
}
}
//删除用户
function useRemoveStudent(){
let state=reactive({
stus:[
{id:1,name:'张三',age:10},
{id:2,name:'李四',age:20},
{id:3,name:'王五',age:30},
]
});
function remStu(index){
state.stus=state.stus.filter((stu,idx)=>idx!==index);
}
return {state,remStu}
}
//增加用户
function useAddStudent(state){
let state2=reactive({
stu:{
id:'',
name:'',
age:''
}
});
function addStu(e){
e.preventDefault();
const stu=Object.assign({},state2.stu);
state.stus.push(stu)
state2.stu.id=''
state2.stu.name=''
state2.stu.age=''
}
return {state2,addStu}
}
</script>
【注意】
①ref和reactive区别:
如果在template里使用的是ref类型的数据,那么vue会自动帮我们添加.value,
如果在template里使用的是reactive类型的数据,那么vue不会自动帮我们添加.value。
②vue是如何决定是否需要自动添加.value的?
vue在解析数据之前,会自动判断这个数据是否是ref类型,
如果是就自动添加.value,如果不是就不自动添加.value。
③vue是如何判断当前的数据是否是ref类型的?
通过当前数据的__v_ref来判断的。如果有这个私有属性,并且取值为true,那么就代表是一个ref类型的数据。
④开发人员如何判断当前的数据类型?
isRef和isReactive来分别判断是否是普通函数和对象。
3.composition API和Option API的混合使用
option API就是之前vue2.x的API
composition AP是现在vue3.x新增的语法
composition API和Option API的是可以混合使用
composition API的本质是什么?
可以翻译成:组合API(或注入API)
他的本质就是在运行的时候,将composition API中暴露出去的数据,注入到Option API中,比如说暴露了一个普通变量age,这个age就会被注入到data()方法里面;暴露了一个方法submit(),这个方法就会被注入到methods里面。
4.setup函数的执行时机和注意点
1)setup函数的执行时机:
setup函数是在beforeCreate和created之间执行的。
beforeCreate:表示组件刚刚被创建出来,组件的data和methods还没初始化号;
created:表示组件刚刚被创建出来,并且组件的data的methods已经初始化好了。
2)setup注意点:
①由于在执行setup函数的时候,还没执行created生命周期方法,所以在setup函数中,是无法使用data和methods的。
②由于我们不能在setup函数中使用data和methods,所以vue为了避免我们错误的使用,他直接将setup函数中的this值修改为了Undefined。
③setup函数只能是同步,不能是异步的。
5.递归监听
1)递归监听
默认情况下,无论是通过ref还是reactive都是递归监听
<template>
<div>
<p>{{state.a}}</p>
<p>{{state.gf.b}}</p>
<p>{{state.gf.f.c}}</p>
<p>{{state.gf.f.s.d}}</p>
<button @click='myFn'>按钮</button>
</div>
</template>
<script>
import {ref} from 'vue'
import {reactive} from 'vue'
export default {
name: 'App',
setup(){
let state=reactive({
a:'a',
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
})
function myFn(){
state.a='1';
state.gf.b='2';
state.gf.f.c='3';
state.gf.f.s.d='4';
console.log(state)
console.log(state.gf)
console.log(state.gf.f.s)
}
return {state,myFn}
}
}
</script>
2)递归监听存在的问题
如果数据量比较大,非常消耗性能。因为他把每一层都包裹成了proxy对象。
3)非递归监听
shadowRef / shallowReactive
非递归监听就是只能监听第一层,不能监听其他层。
【使用场景】只有需要监听的数据量比较大的时候,我们才使用。
①shallowReactive
<script>
import {shallowReactive} from 'vue'
import {shallowRef} from 'vue'
export default {
name: 'App',
setup(){
let state=shallowReactive({
a:'a',
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
})
function myFn(){
state.a='1';
state.gf.b='2';
state.gf.f.c='3';
state.gf.f.s.d='4'
console.log(state)
console.log(state.gf)
console.log(state.gf.f.s)
}
return {state,myFn}
}
}
</script>
会发现只有第一层被包装成了proxy
②shallowRef
如果是shallowRef创建数据,那么vue监听的是.value的变化,并不是第一层的变化。
本质:ref(10)->reactive({value:10}),因为底层本质上是第一层。
<script>
import {shallowReactive} from 'vue'
import {shallowRef} from 'vue'
export default {
name: 'App',
setup(){
let state=shallowRef({
a:'a',
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
})
function myFn(){
state.value.a='1';
state.value.gf.b='2';
state.value.gf.f.c='3';
state.value.gf.f.s.d='4'
console.log(state)
console.log(state.value)
console.log(state.value.gf)
console.log(state.value.gf.f.s)
}
return {state,myFn}
}
}
所以说上面上的字母没有变成数字。要想把他变成数字,需要把直接把state改掉
<script>
import {shallowReactive} from 'vue'
import {shallowRef} from 'vue'
export default {
name: 'App',
setup(){
let state=shallowRef({
a:'a',
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
})
function myFn(){
state.value={
a:'4',
gf:{
b:'5',
f:{
c:'6',
s:{
d:'7'
}
}
}
}
state.value.a='1';
state.value.gf.b='2';
state.value.gf.f.c='3';
state.value.gf.f.s.d='4'
console.log(state)
console.log(state.value)
console.log(state.value.gf)
console.log(state.value.gf.f.s)
}
return {state,myFn}
}
}
</script>
③shallowRef只想修改第四层的数据,如果第四层改了,自动修改Ui界面。加上triggerRef一起使用。
【注意】vue3只提供了triggerRef方法,没有提供TriggerReactive方法。所以如果是reactive类型的数据,那么是无法主动触发界面更新的。
<script>
import {shallowReactive} from 'vue'
import {shallowRef,triggerRef} from 'vue'
export default {
name: 'App',
setup(){
let state=shallowRef({
a:'a',
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
})
function myFn(){
state.value.gf.f.s.d='8';
triggerRef(state)
console.log(state)
console.log(state.value)
console.log(state.value.gf)
console.log(state.value.gf.f.s)
}
return {state,myFn}
}
}
</script>
6.toRaw()原数据被修改,但不会更新ui
ref/reactive数据类型的特点:
每次修改都会被追踪,更新ui界面。但这样其实是非常消耗性能的。如果不需要更新ui界面,就可以通过toRaw方法拿到他的原始数据。此时对原始数据进行修改的时候就不会被追踪,不会更新ui界面。
<template>
<div>
{{state}}
<button @click='myFn'>按钮</button>
</div>
</template>
<script>
import {reactive,toRaw} from 'vue'
export default {
name: 'App',
setup(){
let obj={name:'小李',age:16}
let state=reactive(obj)//注意:如果这里是ref的话,下面toRaw(state.value)
let obj2=toRaw(state)
function myFn(){
// state.name="Angle";//会更新ui界面
obj2.name="Angle";//数据虽然被改变了,不会更新ui界面。这样会减少性能的消耗
console.log(obj2)
}
return {state,myFn}
}
}
</script>
7.markRaw()确认原数据,且永远不会被追踪
<template>
<div>
{{state}}
<button @click='myFn'>按钮</button>
</div>
</template>
<script>
import {reactive,markRaw} from 'vue'
export default {
name: 'App',
setup(){
let obj={name:'小李',age:16}
obj=markRaw(obj)//永远不会被追踪
let state=reactive(obj)
function myFn(){
state.name="Angle";
console.log(obj)
}
return {state,myFn}
}
}
</script>
8.toRef(),toRef和ref,toRef和toRefs
toRef的应用场景:
如果想让响应式数据和以前的数据关联起来,并且更新响应式数据之后还不想更新ui时。
ref和toRef的区别:
ref:相当于复制,修改响应式数据不会影响以前的数据;toRef:相当于引用,修改响应式数据会影响以前的数据;
ref:数据改变,界面就更新;toRef:数据改变,界面并不更新;
ref->相当新复制的一份,没有改原数据。但ref包裹的数据改变了,且改变会更新页面。
<template>
<div>
{{state}}
<button @click='myFn'>按钮</button>
</div>
</template>
<script>
import {reactive,ref} from 'vue'
export default {
name: 'App',
setup(){
let obj={name:'小李',age:16}
let state=ref(obj.name)
function myFn(){
state.value="Angle";
console.log(obj)//没有改变obj
console.log(state)//state是新的复制出来的一份
}
return {state,myFn}
}
}
</script>
->toRef引用,改变了原数据,但界面不更新:
<template>
<div>
{{state}}
<button @click='myFn'>按钮</button>
</div>
</template>
<script>
import {reactive,toRef} from 'vue'
export default {
name: 'App',
setup(){
let obj={name:'小李',age:16}
let state=toRef(obj,'name')
function myFn(){
state.value="Angle";
console.log(obj)//因为引用,所以改变了
console.log(state)//改变了,但界面没更新
}
return {state,myFn}
}
}
</script>
toRefs是toRef多个使用的简写:
<script>
import {reactive,toRef,toRefs} from 'vue'
export default {
name: 'App',
setup(){
let obj={name:'小李',age:16}
let name=toRef(obj,'name');
let age=toRef(obj,'age');
// let state=toRef(obj);//简写
function myFn(){
name.value="Angle";
age.value=18;
// state.name.value='Angle';
// state.age.value=18;
console.log(obj)//因为引用,所以改变了
console.log(state)//改变了,但界面没更新
}
return {state,myFn}
}
}
</script>
9.customRef()自定义ref
返回一个ref对象,可以显示的控制以来追踪和触发相应。
get的时候会触发track(),告诉vue这个数据是需要追踪变化的;
set的时候会触发trigger()。告诉vue触发界面更新。
<template>
<div>
{{age}}
<button @click='myFn'>按钮</button>
</div>
</template>
<script>
import {ref,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 {
name: 'App',
setup(){
let age=myRef(18);
function myFn(){
age.value+=1;
}
return {age,myFn}
}
}
</script>
使用场景:在自定义方便异步请求,看起来不像异步。
<template>
<div>
<ul>
<li v-for="item in state" :key="item.id">{{item.name}}</li>
</ul>
</div>
</template>
<script>
import {ref,customRef} from 'vue'
function myRef(value){
return customRef((track,trigger)=>{
//异步
fetch(value)
.then((res)=>{
return res.json()
})
.then((data)=>{
console.log(data);
value=data;
trigger();
})
.then((err)=>{
console.log(err)
})
return{
get(){//不能在get中发送异步请求,否则会死循环
track();//告诉vue这个数据是需要追踪变化的
console.log('get',value)
return value;
},
set(newValue){
console.log('set',newValue)
value=newValue;
trigger();//告诉vue触发界面更新
}
}
})
}
export default {
name: 'App',
setup(){
let state=myRef('./assets/data.json')
return {state}
}
}
</script>