Vue3.0学习

Vue编译网站:

Vue2.x:https://template-explorer.vuejs.org/#

Vue3.0:https://vue-next-template-explorer.netlify.app/#

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 比较
  DYNAMIC_SLOTS = 1 << 10, // 1024 // 动态 slot
  HOISTED = -1, // 静态节点
  // 指示在 diff 过程应该要退出优化模式
  BAIL = -2
}

Vue3.0六大亮点

  • Performance:性能比Vue2.x快1.2~2倍
  • Tree shaking support:按需编译,体积比Vue2.x更小
  • Composition API:组合API(类似React Hooks)
  • Custom Renderer API:暴露了自定义渲染API
  • Fragment,Teleport(Protal),Suspense:更先进的组件

Vue3.0是如何变快的?

  • diff 算法优化
  • hoistStatic 静态提升
  • cacheHandlers 事件监听缓存
  • ssr 渲染

diff算法优化

  • 在Vue2.x中的虚拟DOM是进行全量的对比
  • Vue3.0新增了静态标记(PatchFlag),在与上次虚拟节点进行对比时候,只对比带有patch flag的节点,并且可以通过flag的信息得知当前节点要对比的具体内容
<div>
    <p>hellop>
    <p>hellop>
    <p>hellop>
    <p>{
    {msg}}}p>
div>

编译之后:

export function render(_ctx, _cache, $props, $setup, $data, $options) {
     
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", null, "hello"),
    _createVNode("p", null, "hello"),
    _createVNode("p", null, "hello"),
    _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
  ]))
}

hoistStatic静态提升

  • Vue2.x中无论元素是否参与更新,每次都会重新创建,然后再渲染
  • Vue3.0中对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
<div>
    <p>hellop>
    <p>hellop>
    <p>hellop>
    <p>{
    {msg}}}p>
div>

没有静态提升时:

export function render(_ctx, _cache, $props, $setup, $data, $options) {
     
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", null, "hello"),
    _createVNode("p", null, "hello"),
    _createVNode("p", null, "hello"),
    _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
  ]))
}

静态提升之后:

const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "hello", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "hello", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createVNode("p", null, "hello", -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.msg) + "}", 1 /* TEXT */)
  ]))
}

cacheHandlers事件监听缓存

默认情况下onClick会被视为动态绑定,所以每次都会去追踪它的变化。但是因为是同一个函数,所以没有追踪变化,可以直接缓存起来复用即可

<div>
  <button @click="onClick">按钮button>
div>

没有开启事件监听缓存时:

转换之后的代码,可能还看不懂, 但是不要紧,我们只需要观察有没有静态标记即可。因为我们知道在Vue3.0的diff算法中,只有有静态标记的才会进行比较,才会进行追踪

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(...args)))
    }, "按钮")
  ]))
}

创建Vue3.0项目的三种方式

  • Vue Cli
  • Webpack
  • Vite

Vue Cli

  • npm install -g @vue/cli
  • vue create projectName
  • cd projectName
  • vue add vue-next
  • npm run serve

Webpack

  • git clone https://github.com/vuejs/vue-next-webpack-preview.git projectName
  • cd projectName
  • npm install ...
  • npm run dev

Vite

Vite是Vue作者开发的一款意图取代webpack的工具

实现原理:利用ES6的import会发送请求去加载文件的特性,拦截这些请求,做一些预编译,省去webpack冗长的打包时间

  • 安装Vite:npm install -g create-vite-app
  • 利用Vite创建Vue3.0项目:create-vite-app projectName
  • 进入Vue3.0项目中:cd projectName
  • 下载当前项目依赖,生成node_modules文件夹:npm install
  • 安装依赖:npm install ...
  • 运行项目:npm run dev

Vue2.x存在的问题

Vue2.x数据与业务逻辑太分散了,不利于管理和维护。Vue3.0针对该问题推出了组合API(注入API)

data () {
     
	return {
     
		//新增功能1的数据
		//新增功能2的数据
	}
},
methods: {
     
    //新增功能1的业务逻辑
    //新增功能2的业务逻辑
},
computed: {
     
    //新增功能1的业务逻辑
    //新增功能2的业务逻辑
},
watch: {
     
    //新增功能1的业务逻辑
    //新增功能2的业务逻辑
}

Vue3.0组合API

组合API(注入API)将return {}暴露出去的变量和方法分别注入到data和methods中

setup()执行时机

  • setup函数是在beforecreate钩子之前完成的
  • 执行setup函数的时候,还没有执行Created生命周期方法(即data和methods还没初始化),所以在setup函数中是无法使用data和methods的
  • Vue为了避免我们错误的使用,它直接将setup函数中this修改成了undefined
  • setup函数只能是同步的不能是异步的

reactive()

  • reactive()是Vue3.0提供实现响应式数据的方法
  • 在Vue2.x中响应式数据是通过defineProperty来实现的,在Vue3.0中响应式数据是通过ES6的Proxy来实现的
  • reactive()参数必须是对象(arr/json)
  • 如果给reactive()传递的不是一个对象的话,无法实现响应式
  • 如果给reactive()传递的是其它对象(除arr/json)的话,默认情况下修改对象,界面是不会自动更新的。如果想更新,可以通过重新赋值的方式
  • 本质:就是将传入的数据包装成一个Proxy对象

ref()

  • ref()也是Vue3.0提供实现响应式数据的方法,实现对简单数据类型的监听
  • 本质:ref()的底层其实还是reactive(),系统会自动根据我们给ref传入的值将它转换成ref(xxx)—>reactive( {value: xxx} )
  • 如果通过ref()创建的数据,那么在template中使用的时候不用通过.value来获取,因为Vue会自动给我们添加.value
  • 注意点:
    • 在JS中使用ref的值必须通过value获取
    • 在Vue中使用ref的值不同通过value获取

Vue是如何决定是否需要自动添加.value的?

Vue在解析数据之前,会自动判断这个数据是否是ref类型的,如果是就自动添加.value,否则不自动添加.value

Vue是如何判断当前的数据是否是ref类型?

Vue通过当前数据的__v_ref来判断的,如果有__v_ref这个私有属性并且取值为true,那么就代表是一个ref类型的数据

自己如何判断当前的数据是ref类型还是reactive类型?

Vue提供了对应的方法,isRef()isReactive()

App.vue

<template>
  <div>
    <form>
      <input type="text" v-model="state1.stu.id">
      <input type="text" v-model="state1.stu.name">
      <input type="text" v-model="state1.stu.age">
      <input type="button" value="添加" @click="addStu">
    form>
    <ul>
      <li v-for="(stu,index) in state.stus" :key="stu.id" @click="remStu(index)">
        {
    { stu.name }}-{
    { stu.age }}
      li>
    ul>
  div>
template>
<script>
import useRemoveStudent from "./rem"
import useAddStudent from "./add"
export default {
      
  name: "App",
  // setup函数是组合API的入口函数
  setup() {
      
    let {
       state, remStu } = useRemoveStudent()
    let {
       state1, addStu } = useAddStudent(state)
    // 在组合API中定义的变量或方法,要想在外界使用,必须通过return {}暴露出去
    return {
       state, remStu, state1, addStu }
  }
}
script>
<style>
style>

remove.js

import {
     reactive} from 'vue'
function useRemoveStudent() {
     
    let state = reactive({
     
        stus:[
            {
     id:1, name:'zs', age:10},
            {
     id:2, name:'ls', age:20},
            {
     id:3, name:'ww', age:30},
        ]
    })
    function remStu(index) {
     
        state.stus = state.stus.filter((stu, idx) => return idx !== index);
    }
    return {
     state, remStu}
}
export default useRemoveStudent

add.js

import {
     reactive} from 'vue'
function useAddStudent(state) {
     
    let state1 = reactive({
     
        stu:{
     
            id:'',
            name:'',
            age:''
        }
    })
    function addStu(e) {
     
        const stu = Object.assign({
     }, state1.stu)
        state.stus.push(stu)
        state1.stu.id = ''
        state1.stu.name = ''
        state1.stu.age = ''
    }
    return {
     state1, addStu}
}
export default useAddStudent

递归监听

默认情况下,无论是通过ref还是reactive都是递归监听,内部做了递归,将每一层包装成了Proxy对象,如果数据量比较大时,非常消耗性能

<template>
  <div>
    <p>{
    {state.a}}p>
    <p>{
    {state.b.c}}p>
    <p>{
    {state.b.d.e}}p>
    <button @click="handle">修改数据button>
  div>
template>
<script>
import {
      reactive} from 'vue'
export default {
      
  name: 'App',
  setup(){
      
    let state = reactive({
      
      a: 'a',
      b: {
      
        c: 'c',
        d: {
      
          e: 'e'
        }
      }
    })
    function handle(){
      
      state.a = '1',
      state.b.c = '2',
      state.b.d.e = '3'
      console.log(state) //Proxy {a: "1", b: {…}}
      console.log(state.b) //Proxy {c: "2", d: {…}}
      console.log(state.b.d) //Proxy {e: "3"}
    }
    return {
      state,handle}
  }
}
script>
<template>
  <div>
    <p>{
    {state.a}}p>
    <p>{
    {state.b.c}}p>
    <p>{
    {state.b.d.e}}p>
    <button @click="handle">修改数据button>
  div>
template>
<script>
import {
      ref} from 'vue'
export default {
      
  name: 'App',
  setup(){
      
    let state = ref({
      
      a: 'a',
      b: {
      
        c: 'c',
        d: {
      
          e: 'e'
        }
      }
    })
    function handle(){
      
      state.value.a = '1',
      state.value.b.c = '2',
      state.value.b.d.e = '3'
      console.log(state.value) //Proxy {a: "1", b: {…}}
      console.log(state.value.b) //Proxy {c: "2", d: {…}}
      console.log(state.value.b.d) //Proxy {e: "3"}
    }
    return {
      state,handle}
  }
}
script>

非递归监听

使用shallowReactive和shallowRef可以实现非递归监听

<template>
  <div>
    <p>{
    {state.a}}p>
    <p>{
    {state.b.c}}p>
    <p>{
    {state.b.d.e}}p>
    <button @click="handle">修改数据button>
  div>
template>
<script>
import {
      shallowReactive} from 'vue'
export default {
      
  name: 'App',
  setup(){
      
    let state = shallowReactive({
      
      a: 'a',
      b: {
      
        c: 'c',
        d: {
      
          e: 'e'
        }
      }
    })
    function handle(){
      
      state.a = '1',
      state.b.c = '2',
      state.b.d.e = '3'
      console.log(state) //Proxy {a: "1", b: {…}}
      console.log(state.b) //{c: "2", d: {…}}
      console.log(state.b.d) //{e: "3"}
    }
    return {
      state,handle}
  }
}
script>

从上面代码可以看出,shallowReactive只将第一层包装成了Proxy对象(只监听第一层),即只要改变了第一层的数据就会去更新UI,如果没有改变第一层的数据而改变的是第二层或第三层不会更新UI

<template>
  <div>
    <p>{
    {state.a}}p>
    <p>{
    {state.b.c}}p>
    <p>{
    {state.b.d.e}}p>
    <button @click="handle">修改数据button>
  div>
template>
<script>
import {
      shallowRef} from 'vue'
export default {
      
  name: 'App',
  setup(){
      
    let state = shallowRef({
      
      a: 'a',
      b: {
      
        c: 'c',
        d: {
      
          e: 'e'
        }
      }
    })
    function handle(){
      
      state.value.a = '1',
      state.value.b.c = '2',
      state.value.b.d.e = '3'
      console.log(state.value) //{a: "1", b: {…}}
      console.log(state.value.b) //{c: "2", d: {…}}
      console.log(state.value.b.d) //{e: "3"}
    }
    return {
      state,handle}
  }
}
script>

从上面代码可以看出,shallowRef并不是监听的第一层并且没有一层被包装成RefImpl对象(即改变每层的数据也不会更新UI)

<template>
  <div>
    <p>{
    { state.a }}p>
    <p>{
    { state.b.c }}p>
    <p>{
    { state.b.d.e }}p>
    <button @click="handle">修改数据button>
  div>
template>
<script>
import {
       shallowRef } from "vue"
export default {
      
  name: "App",
  setup() {
      
    let state = shallowRef({
      
      a: "a",
      b: {
      
        c: "c",
        d: {
      
          e: "e",
        },
      },
    })
    function handle() {
      
      //修改.value值
      state.value = {
      
        a: "1",
        b: {
      
          c: "2",
          d: {
      
            e: "3",
          },
        },
      }
      console.log(state) //RefImpl {_rawValue: {…}, _shallow: true, __v_isRef: true, _value: {…}}
      console.log(state.value) //{a: "1", b: {…}}
      console.log(state.value.b) //{c: "2", d: {…}}
      console.log(state.value.b.d) //{e: "3"}
    }
    return {
       state, handle }
  }
}
script>

从上面代码可以看出,shallowRef其实监听的是.value的变化,并不是第一层的变化。只有修改.value的值才会更新UI

从底层看shallowRef(xxx)—>shallowReactive( {value: xxx} ),通过shallowRef创建的数据,它其实监听的是.value的变化,本质上value才是第一层

<template>
  <div>
    <p>{
    { state.a }}p>
    <p>{
    { state.b.c }}p>
    <p>{
    { state.b.d.e }}p>
    <button @click="handle">修改数据button>
  div>
template>
<script>
import {
       shallowRef,triggerRef } from "vue"
export default {
      
  name: "App",
  setup() {
      
    let state = shallowRef({
      
      a: "a",
      b: {
      
        c: "c",
        d: {
      
          e: "e",
        },
      }
    })
    function handle() {
      
      //修改第二层数据只更新第二层对应的UI
      state.value.b.c="2"
      triggerRef(state)
    }
    return {
       state, handle }
  }
}
script>

通过triggerRef()来触发非递归监听属性更新UI界面。Vue3.0只提供了triggerRef方法,没有提供triggerReactive方法。如果是reactive类型的数据,那么是无法触发UI界面更新的

递归监听和非递归监听应用场景

  • 一般情况下使用reactive和ref即可
  • 只有在需要监听的数据量比较大时,才使用shallowReactive和shallowRef

toRaw

reactive和ref数据类型的特点:每次修改都会被追踪,都会更新UI界面,但这样是非常消耗性能的。如果我们有一些操作不需要追踪,不需要更新UI界面,那么这个时候就可以通过toRaw方法拿到它的原始数据,对原始数据进行修改,这样就不会被追踪和更新UI界面,性能提高了

state本质是一个Proxy对象(响应式数据),Proxy对象引用了obj

<template>
  <div>
    <p>{
    { state.id }}p>
    <p>{
    { state.name }}p>
    <p>{
    { state.age }}p>
  div>
template>
<script>
import {
       reactive,toRaw } from "vue"
export default {
      
  name: "App",
  setup() {
      
    let obj ={
      id: 1,name: "张三",age: 18}
    let state = reactive(obj)
    console.log(obj) //{id: 1, name: "张三", age: 18}
    console.log(state) //Proxy {id: 1, name: "张三", age: 18}
    console.log(obj===state) //false
    console.log(toRaw(state)) //{id: 1, name: "张三", age: 18}
    return {
       state }
  }
}
script>

如果想要通过toRaw拿到ref类型的原始数据,toRaw(state.value),.value中保存的才是当初创建时传入的原始数据

reactive和ref是否会影响原始数据:

reactive(引用obj),修改响应式数据会影响到原始数据

ref(复制obj),修改响应式数据不会影响到原始数据

<template>
  <div>
    <p>{
    { state.id }}p>
    <p>{
    { state.name }}p>
    <p>{
    { state.age }}p>
    <button @click="handle">修改数据button>
  div>
template>
<script>
import {
       reactive, toRaw } from "vue"
export default {
      
  name: "App",
  setup() {
      
    let obj = {
       id: 1, name: "张三", age: 18 }
    let state = reactive(obj)
    function handle() {
      
      state.name = "李四"
      console.log(obj) //{id: 1, name: "李四", age: 18}
      console.log(state) //Proxy {id: 1, name: "李四", age: 18}
      console.log(toRaw(state)) //{id: 1, name: "李四", age: 18}
    }
    return {
       state, handle }
  }
}
script>
<template>
  <div>
    <p>{
    { state.id }}p>
    <p>{
    { state.name }}p>
    <p>{
    { state.age }}p>
    <button @click="handle">修改数据button>
  div>
template>
<script>
import {
       ref, toRaw } from "vue"
export default {
      
  name: "App",
  setup() {
      
    let obj = {
       id: 1, name: "张三", age: 18 }
    let state = ref(obj);
    function handle() {
      
      state.name = "李四"
      console.log(obj) //{id: 1, name: "张三", age: 18}
      console.log(state) //RefImpl {_rawValue: {…}, _shallow: false, __v_isRef: true, _value: Proxy, name: "李四"}
      console.log(toRaw(state.value)) //{id: 1, name: "张三", age: 18}
    }
    return {
       state, handle }
  }
}
script>

markRaw

添加不可转为响应式数据的标记(永远不会被追踪),函数返回这个对象本身

let obj ={
     id: 1,name: "张三",age: 18}
obj = markRaw(obj)
let state = reactive(obj)

ref和toRef以及toRefs的区别和用法

  • ref(复制),修改响应式数据不会影响原始数据,数据发生改变UI界面就会自动更新

  • toRef(引用),修改响应式数据会影响原始数据,数据发生改变UI界面也不会自动更新

  • toRefs的用法:

    let obj = {
           name: '张三',age: 18}
    //let state = toRef(obj, 'name')
    //let state = toRef(obj, 'age')
    let state = toRefs(obj)
    

customRef

customRef用于自定义返回一个ref对象,可以显式地控制依赖追踪和触发响应,两个参数分别是用于追踪的 track 与用于触发响应的 trigger,并返回一个带有 get 和 set 属性的对象

import {
     customRef} from 'vue'
function myRef(value) {
     
	return customRef((track,trigger)=>{
     
        return {
     
            get(){
     
                //告诉Vue这个数据是需要追踪变化的
                track()
                return value
            },
            set(newValue){
     
                value = newValue
                //告诉Vue触发界面更新
                trigger()
            }
        }
    })
}

ref获取元素

Vue2.x中我们可以给元素添加ref="xxx",然后通过this.$refs.xxx的方式来获取元素

在Vue3.0中我们也可以通过ref来获取元素:

<template>
  <div ref="box">我是divdiv>
template>
<script>
import {
       ref,onMounted } from "vue"
export default {
      
  name: "App",
  setup() {
      
    let box = ref(null)
    onMounted(()=>{
      
      console.log(box.value) //
我是div
}) return { box} } }
script>

readonly、shallowReadonly以及isReadonly

  • readonly:用于创建一个只读的数据,并且是递归只读

    let state = readonly({
           
    	name: '张三',
    	attr: {
           
    		age: 18,
    		height: 1.88
    	}
    })
    
  • shallowReadonly:用于创建一个只读的数据,但不是递归只读的(第一层只读)

    let state = shallowReadonly({
           
    	name: '张三',
    	attr: {
           
    		age: 18,
    		height: 1.88
    	}
    })
    
  • isReadonly:判断是否是只读数据

    console.log(isReadonly(state))
    

const和readonly的区别:

  • const:赋值保护,值和内存地址不能重新赋值
  • readonly:属性保护,属性不能重新赋值

通过Proxy来实现响应式数据

let obj ={
     name: '张三',age: 18}
let state = new Proxy(obj,{
     
  get(obj,key){
     
    console.log(obj,key) //{name: "张三", age: 18} "name"
    return obj[key]
  },
  set(obj,key,value){
     
    console.log(obj,key,value) //{name: "张三", age: 18} "name" "李四"
    obj[key]=value
    return true
  }
})
console.log(state.name) //张三
state.name = "李四"
console.log(obj) {
     name: "李四", age: 18}

你可能感兴趣的:(Vue,vue,vue.js,前端)