前端学习篇 -- Vue 3.0 新特性

1、Vue3.0 六大两点

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

2、Vue3.0是如何变快

2.1 diff算法优化

https://vue-next-template-explorer.netlify.app/

  • Vue2中的虚拟dom是进行全量的对比,即数据更新后在虚拟DOM中每个标签内容都会对比有没有发生变化

  • Vue3新增了静态标记(PatchFlag)

    • 在创建虚拟DOM的时候会根据DOM中的内容会不会发生变化添加静态标记

    • 数据更新后,只对比带有patch flag的节点

      <div>
        <p>知播渔</p>
        <p>{
             
             {msg}}</p> <!-- 双向绑定 -->
      </div>
      
      // 虚拟DOM
      export function render(_ctx, _cache, $props, $setup, $data, $options) {
              
              
        return (_openBlock(), _createBlock("div", null, [
          _createVNode("p", null, "知播渔"), // 不标记
          _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */) // 标记
        ]))
      }
      
    • 并且可以通过flag的信息得知当前节点要对比的具体内容

      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
      }
      

2.2 hoistStatic 静态提升

  • Vue2中无论元素是否参与更新, 每次都会重新创建, 然后再渲染
  • Vue3中对于不参与更新的元素, 会做静态提升, 只会被创建一次, 在渲染时直接复用即可
<div>
  <p>知播渔</p>
  <p>{
   
   {msg}}</p>
</div>
// 静态提升之前
export function render(_ctx, _cache, $props, $setup, $data, $options) {
    
    
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", null, "知播渔"), // 每次都会创建一个虚拟节点
    _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */) 
  ]))
}
// 静态提升之后
const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "知播渔", -1 /* HOISTED */) // 定义了一个全局变量,只会创建一次

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

2.3 事件监听缓存

默认情况下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(...args)))
    }, "按钮")
  ])) // 没有静态标记
}

3、 组合API( compisition-api )

3.1 setup() 函数

  • 组合API的入口函数, 在 beforeCreate 之后、created 之前执行 ,主要是实现数据和业务逻辑不分离

  • 无法使用datamethods,所以为了避免错误使用,直接将内部的this改成了undefined

  • 内部的方法只能是同步,不能是异步

3.1.1 reactive 函数
  • 可以监听复杂类型(json/Array)的变化
  • 实现响应式数据的方法,Vue2中通过Object.defineProperty实现的,Vue3通过ES6的Proxy实现
  • 注意点:如果给 reactive 传递了其他对象,即不是 json/Array
    • 默认情况下修改对象界面不会自动更新
    • 如果要更新,可以通过重新赋值的方式
reactive 传递基本数据类型
import {
    
     reactive } from 'vue'

setup() {
    
    
  let state = reactive(123) // 传递基本数据类型
  function fn() {
    
    
    state = 666 // 界面不会更新,不是 Proxy 对象
    console.log(state) // 666
  }
}

import {
    
     reactive } from 'vue'

setup() {
    
    
  let state = reactive({
    
    age: 13}) 
  function fn() {
    
    
    state.age = 20 // 界面更新
    console.log(state) // Proxy{age: 20}
  }
}

reactive 传递其他对象
import {
    
     reactive } from 'vue'

setup() {
    
    
  let state = reactive({
    
    time: new Date()}) // 其他对象
  function fn() {
    
    
    state.time.setDate(state.time.getDate() + 1) // 界面不会更新
    console.log(state) // Proxy{...}
  }
}
import {
    
     reactive } from 'vue'

setup() {
    
    
  let state = reactive({
    
    time: new Date()}) // 其他对象
  function fn() {
    
    
    const newTime = new Date(state.time.getTime())
    newTime.setDate(state.time.getDate() + 1)
    state.time = newTime // 界面更新
    console.log(state) // Proxy{...}
  }
}
3.1.2 ref 函数
  • 本质还是 reactive ,当给 ref 函数传递一个值后,底层会自动转成 reactive,所以一般都是使用 ref 函数创建响应式数据

    • ref(10) -> reactive({value: 18})
    • 在 setup 函数内部修改 ref 的值时必须通过 .value 的方式
    import {
          
           ref } from 'vue'
    
    setup() {
          
          
      let state = ref(10)
      function fn() {
          
          
        state.value = 20
        console.log(state) // RefImp{...}
      }
    }
    
    
    • 在 template 中使用不用添加 .value,Vue内部已经进行了转换
3.1.3 reactive 和 ref 的区别

Vue在解析数据之前,会通过当前数据的__v_ref这个私有属性判断这个数据是否是ref类型,如果值为true就是一个ref类型的数据

  • 在template里使用的是ref类型的数据,Vue会自动添加.value
  • 在template里使用的是reactive类型的数据,Vue不会自动添加.value

注意: compisition-api 的本质也是将 return 出去的值注入到 option-api 中

return {
    
    state, remStu}

// 等同
data() {
    
    
  return {
    
    
    state: ''
  }
},
methods: {
    
    
  remStu() {
    
    }
}

3.1.4 toRaw 函数

ref 和 reactive 数据类型每次修改都会被追踪,都会更新UI界面,这样会非常消耗性能,有时候一些数据不需要追踪就可以通过toRaw方法拿到它的原始数据,对原始数据进行修改就不会被追踪

setup() {
    
    
  let obj = {
    
    name: 'zs', age: 18}
  let state = reactive(obj) // 包装后的对象
  let obj2 = toRaw(state)
  /* 
  * let state = ref(obj)
  * let obj2 = toRaw(state.value)
  */
  console.log(obj === obj2) // true
  /* 
  * state 和 obj 的关系:
  * state 本质是一个 Proxy 对象,只是引用了 obj
  */
  console.log(obj === state) // false
  
  // 修改原始数据
  function fn() {
    
    
    obj2.name = 'ls' // 这里只是在内存里发生了改变,并不会更新视图
    // obj.name = 'ls' 这样写才会更新视图
  }
}

3.1.5 markRaw 函数

数据永远不会被追踪

setup() {
    
    
  let obj = {
    
    name: 'zs', age: 18}
  obj = markRaw(obj) // 此时无论怎么修改都不会更新数据
  let state = reactive(obj) 
}

3.1.6 toRef 函数

如果利用ref将某一个对象中的属性变成响应式的数据,修改响应式的数据是不会影响到原始数据的

setup() {
    
    
  let obj = {
    
    name: 'zs'}
  let state = ref(obj.name)
  function fn() {
    
    
    state.value = 'ls'
    console.log(obj) // {name: 'zs'}
    console.log(state.value) // ls
  }
}

利用 toRef 将一个对象的某一个属性变成响应式的数据,修改响应式数据会影响原始数据,但不会更新视图,只是引用了原始数据

setup() {
    
    
  let obj = {
    
    name: 'zs'}
  let state = toRef(obj, 'name')
  function fn() {
    
    
    state.value = 'ls'
    console.log(obj) // {name: 'ls'}
    console.log(state.value) // ls
  }
}

应用场景:

如果想让响应式数据和原始数据关联起来,并且修改数据之后不想更新UI界面,那么就可以使用

3.1.7 toRefs 函数

将对象的全部属性变成响应式数据,修改后不更新视图

setup() {
    
    
  let obj = {
    
    name: 'zs', age: 18}
  let state = toRefs(obj)
  function fn() {
    
    
    state.name.value = 'ls'
    state.age.value = 20
    console.log(obj) // {name: 'ls', age: 20}
    console.log(state.name.value) // ls
  }
}

3.2 递归监听

默认情况下,ref和reactive创建的数据都是递归监听,即无论里面套多少层都可以监听得到

const state = ref({
    
    
  a: 1,
  b: {
    
    
    c: 2,
    d: {
    
    
      e: 3
    }
  }
})

ref.b.c.value = 3 // 可以监听,且会更新界面

缺点:当数据量比较大时,会非常消耗性能,因为每一层都是一个Proxy对象

3.3 非递归监听

只能监听第一层,不能监听其它层,只有第一层数据改变了,其它层才会被监听,可以通过 shallowReactive、shallRef 创建非递归监听数据

3.3.1 shallowReactive
import {
    
     shallowReactive } from 'vue'
const state = shallowReactive({
    
    
  a: 1,
  b: {
    
    
    c: 2,
    d: {
    
    
      e: 3
    }
  }
})

// 这样可以被监听
state.a = 'a'  /* 如果第一层不变,则视图不会更新 */
state.b.c = 'c'
state.b.d.e = 'e'

3.3.2 shallowRef

本质是 shallowReactive ,当给 shallowRef 函数传递一个值后,底层会自动转成 shallowReactive,所以 shallowRef 的第一层是.value

import {
    
     shallowRef } from 'vue'
const state = shallowRef({
    
    
  a: 1,
  b: {
    
    
    c: 2,
    d: {
    
    
      e: 3
    }
  }
})

// shallowRef 的第一层是 state.value,只有state.value 发生变化才会更新视图
state.value = {
    
    
  a: 2,
  b: {
    
    
    c: 3,
    d: {
    
    
      e: 4
    }
  }
}
3.3.2 triggerRef

自动触发一次shallowRef的数据更新,没有triggerReactive函数

import {
    
     shallowRef, triggerRef } from 'vue'
const state = shallowRef({
    
    
  a: 1,
  b: {
    
    
    c: 2,
    d: {
    
    
      e: 3
    }
  }
})

state.b.d.e = 'e'
triggerRef(state) // 此时视图会更新
3.1.1 删除数据
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:'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) => idx !== index)
  }
  return {
    
    state, remStu}
}
<ul>
  <li v-for="(stu, index) in state.stus"
      :key="stu.id"
      @click="remStu(index)">
    {
   
   {stu.name}} - {
   
   {stu.age}}
  </li>
</ul>
3.1.2 新增数据
import {
    
     reactive } from 'vue'
export default {
    
    
  name: 'App',
  setup() {
    
    
    let {
    
    state, remStu} = useRemoveStudent();
    let {
    
    state2, addStu} = useAddStudent(state);
    return {
    
    state, remStu, state2, addStu}
  }
}

function useAddStudent(state) {
    
    
  let state2 = reactive({
    
    
    stu:{
    
    
      id:'',
      name:'',
      age:''
    }
  })
  function addStu() {
    
    
    const stu = Object.assign({
    
    }, state2.stu)
    state.stus.push(stu)
    state2.stu.id = ''
    state2.stu.name = ''
    state2.stu.age = ''
  }
  return {
    
    state2, addStu}
}
<form>
  <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.prevent="addStu">
</form>

猜你喜欢

转载自blog.csdn.net/weixin_44257930/article/details/109068656