vue3.0和vue2.0的比较

前言:

            对vue3.0进行一次整理

目录:

一.vue3.0的亮点

1.性能比vue2.x快1.2~2倍

2.支持按需编译,按需导入,体积比vue2.x更小

3.支持组合API,类似React Hooks

4.更好的支持ts,因为他是ts写的

5.暴露了自定义渲染的API

6.提供过了更先进的组件

二.vue3.0是如何变快的

1. diff方法优化

2.静态提升

3.事件侦听器缓存

4.ssr渲染

三.创建vue3.0的三种方式

1.Vue-CLI

2.Webpack

3.Vite

1)安装Vite

2)利用Vite创建Vue3.0项目

四.什么是Vite,这里如果要用vite可以用这个来创建项目,其他的直接执行升级命令

1.安装vite

2.利用vite创建vue3.0项目

3.安装依赖运行项目

五.组合API

1.ref()

2.reactive()函数

3.composition API和Option API的混合使用

4.setup函数的执行时机和注意点

5.递归监听

6.toRaw()原数据被修改,但不会更新ui

7.markRaw()确认原数据,且永远不会被追踪

8.toRef(),toRef和ref,toRef和toRefs

9.customRef()自定义ref


一.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_cachenode_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>

注意
refreactive区别:
          如果在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>

猜你喜欢

转载自blog.csdn.net/weixin_44727080/article/details/112977564