Vue3新特性

vue3的六大亮点

  1. Performance: 性能比vue2.x快1.2-2倍
  2. Tree shaking support: 按需编译,体积比vue2更小
  3. Composition API: 组合api(类似于react hooks)
  4. Better Typescript: 更好的ts的支持
  5. Custom Renderer api: 暴露了自定义的api
  6. Fragment Teleport(Protal) ,suspense: 更先进的组件

vue3是如何变化更快的

diff算法的优化

vue2中的diff算法
vue2.x中的虚拟dom是进行全量的对比,例如

图片[1]-Vue3新特性-一一网
vue2的在线编译 vue-template-explorer.netlify.app/

function render() {
  with(this) {
    return _c('div', [_c('p', [_v("hello world ")]), _c('p', [_v(_s(msg) +
      " ")])])
  }
}
复制代码

vue3中的diff算法,只会给需要动态改变的标签会打上一个flag,这样就减少了diff算法的比对次数

图片[2]-Vue3新特性-一一网
vue3的在线编译 vue-next-template-explorer.netlify.app/

import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"

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

// Check the console for the AST
复制代码

那么这个flag为1是什么意思呢

TEXT = 1 // 动态文本节点
CLASS=1<<1,1 // 2//动态class
STYLE=1<<2,// 4 //动态style
PROPS=1<<3,// 8 //动态属性,但不包含类名和样式
FULLPR0PS=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 // 静态节点
复制代码

静态提升(hoistStatic)

Vue2.x中虚拟dom每次都会重新创建,进行比对渲染
Vue3.0中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
未被提升前

import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"

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

// Check the console for the AST
复制代码

被提升后

import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"

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

// Check the console for the AST
复制代码

事件侦听器缓存

在vue2中,在元素身上绑定事件后,因为事件是动态变化的,所以会认为dom发生了变化

import { toDisplayString as _toDisplayString, createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", { onClick: _ctx.handleClick }, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["onClick"]))
}

// Check the console for the AST
复制代码

而在vue3中。会默认给事件加上缓存

import { toDisplayString, createVNode, openBlock, createBlock, withScopeId } from "vue"

// Binding optimization for webpack code-split
const _toDisplayString = toDisplayString, _createVNode = createVNode, _openBlock = openBlock, _createBlock = createBlock, _withScopeId = withScopeId
const _withId = /*#__PURE__*/_withScopeId("scope-id")

export const render = /*#__PURE__*/_withId(function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", {
    onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.handleClick(...args)))
  }, _toDisplayString(_ctx.msg), 1 /* TEXT */))
})

// Check the console for the AST
复制代码

vue3 项目初始化

  1. 方法一:利用vite
npm init vite-app hello-vue3   (# OR yarn create vite-app hello-vue3)
复制代码
  1. 方法二:利用vue-cli
npm install -g @vue/cli (# OR yarn global add @vue/cli)
vue create hello-vue3
# select vue 3 preset
复制代码

Composition API

setup

setup 函数是一个新的组件选项。它作为在组件内部使用组合 API 的入口点。
调用时间:在创建组件实例时,在初始 prop 解析之后立即调用 setup。在生命周期方面,它是在 beforeCreate 钩子之前调用的。
其他可以参考官方文档(比如setup里面的两个参数)

ref

在vue2中只需要在data里定义数据,就可以实现数据层-视图层的双向绑定,而在vue3中使用ref接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property.value
例如:

<template>
 <div>
   {{num}}
 </div>
 <button @click="handleAdd">按钮</button>
</template>
<script>
import { ref } from 'vue'
export default {
  name: 'App',
  setup() {
    let num = ref(0)
    function handleAdd() {
      num.value= 2  
      // num = 2  这种写法是错误的。因为ref把里面的数据包装成了一个对象,但是在template中不需要.value vue会根据__v_isRef进行处理
    }
    console.log(num) 
   /*{ __v_isRef: true
   _rawValue: 0
   _shallow: false
   _value: 2
   value: 2
   }
   */
    return {num,handleAdd}
  }
}
</script>
复制代码

reactive

reactive的作用和ref的作用是类似的,都是将数据变成可相应的对象,其实ref的底层其实利用了reactive。
两者的区别,ref包装的对象需要.value ,而reactive中的不需要

<template>
 <div>
   {{num.person.name}}----{{num.person.age}}
 </div>
 <button @click="handleAdd">按钮</button> 
</template>
<script>
import { ref,reactive } from 'vue'
export default {
  name: 'App',
  setup() {
    let num = reactive({
      person:{
        name:'yj',
        age:18,
        
      }
    })
    function handleAdd() {
       num.person.age = num.person.age+1  
    }
    return {num,handleAdd}
  }
}
</script>
复制代码

shallowRef 和 triggerRef

创建一个 ref,它跟踪自己的 .value 更改,但不会使其值成为响应式的。
使用trigger主动出发视图更新

<template>
 <div>
   {{num.person.name}}----{{num.person.age}}---{{num.person.origin.aa.gf}}---{{num.a}}
 </div>
 <button @click="handleAdd">按钮</button>
</template>

<script>
import { shallowRef, triggerRef } from 'vue'
export default {
  name: 'App',
  setup() {
    let num = shallowRef({
      a:1,
      person:{
        name:'yj',
        age:18,
        origin:{
          aa:{
            gf:"bb"
          }
        }
      }
    })
    function handleAdd() {
       num.value.a = 2
       triggerRef(num)
    }
    return {num,handleAdd}
    
  }
}
复制代码

shallowReactive

shallowReactive 只监听第一层值的变化,深层次的不监听(值会发生改变,但是视图不更新)

<template>
 <div>
   {{num.person.name}}----{{num.person.age}}---{{num.person.origin.aa.gf}}---{{num.a}}
 </div>
 <button @click="handleAdd">按钮</button>
</template>

<script>
import { shallowReactive } from 'vue'
export default {
  name: 'App',
  setup() {
    let num = shallowReactive({
      a:1,
      person:{
        name:'yj',
        age:18,
        origin:{
          aa:{
            gf:"bb"
          }
        }
      }
    })
    function handleAdd() {
       num.a++  
    }
    return {num,handleAdd}
  }
}
</script>

复制代码

toRaw

返回 reactive 或 readonly 代理的原始对象。这是一个转义口,可用于临时读取而不会引起代理访问/跟踪开销,也可用于写入而不会触发更改。不建议保留对原始对象的持久引用。请谨慎使用。

<template>
 <div>
  {{num}}
 </div>
 <button @click="handleAdd">按钮</button>
</template>

<script>
import { reactive,toRaw } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj ={
      a:1,
      b:2,
      c:3
    }
    let num = reactive(obj)
    let obj1 = toRaw(num)   // 暴露出原对象
    console.log(obj1===obj)  //  true  
    function handleAdd() {
     // num 这里是对obj进行了地址引用,但是改变obj的值,并不会出发视图更新
      obj.a = '222233' 
      console.log(num)
    }

    return {num,handleAdd}
    
  }
}
</script>

复制代码
<template>
 <div>
  {{num.a}}
 </div>
 <button @click="handleAdd">按钮</button>
</template>

<script>
import { ref,toRaw } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj ={
      a:1,
      b:2,
      c:3
    }
    let num = ref(obj)
    let obj1 = toRaw(num.value)
    console.log(obj1===obj)
    function handleAdd() {
        obj1.a = '22233333'
        console.log(num)
      // obj.a = '222233' 
      // console.log(num)
    }
    return {num,handleAdd}
    
  }
}
</script>
复制代码

markRaw

标记一个对象,使其永远不会转换为代理。返回对象本身。

<template>
 <div>
  {{num}}
 </div>
 <button @click="handleAdd">按钮</button>
</template>

<script>
import { markRaw, reactive,toRaw } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj ={
      a:1,
      b:2,
      c:3
    }
    let obj1 = markRaw(obj)
    let num = reactive(obj1)
    function handleAdd() {
        num.a = '2222'
        console.log(num)
    }
    return {num,handleAdd}
    
  }
}
</script>
复制代码
<template>
 <div>
  {{num}}
 </div>
 <button @click="handleAdd">按钮</button>
</template>

<script>
import { markRaw, ref, } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj ={
      a:1,
      b:2,
      c:3
    }
    let obj1 = markRaw(obj)
    let num = ref(obj1)
    function handleAdd() {
        num.value.a = '2222'
        console.log(num.value)
    }
    return {num,handleAdd}
    
  }
}
</script>
复制代码

toRef

如果利用toRef将一个数据变成响应式数据,是会影响到原始数据,但是响应式数据通过toRef。并不回出发ui界面更新(ref式改变,不会影响到原始数据)

<template>
 <div>
  {{state}}
 </div>
 <button @click="handleAdd">按钮</button>
</template>

<script>
import { ref, toRef } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj ={
      a:1,
      b:2,
      c:3
    }
    // let state = ref(obj.a)
    let state = toRef(obj,'a')
     console.log(state)
    function handleAdd() {
      // 修改响应式数据,并不回改变原始数据
       state.value= "2222222"
       console.log(state)  
       console.log(obj) 

      // 如果利用toref将一个数据变成响应式数据,是会影响到原始数据,但是响应式数据通过toref。并不回出发ui界面更新
    }
    return {state,handleAdd}
    
  }
}
</script>
复制代码

toRefs

类似toRef,只是一次性处理多次toRef

<template>
 <div>
  {{num.a}}
 </div>
 <button @click="handleAdd">按钮</button>
</template>

<script>
import { toRefs,toRaw } from 'vue'
export default {
  name: 'App',
  setup() {
    let obj ={
      a:1,
      b:2,
      c:3
    }
    let num = toRefs(obj)
  
    function handleAdd() {
       num.a.value="1111"
       num.b.value="222"
       num.c.value="3333"
       console.log(obj)
    }

    return {num,handleAdd}
    
  }
}
</script>

复制代码

customRef

创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并应返回一个带有 get 和 set 的对象。

<template>
 <div>
   {{msg}}
 </div>
 <ul>
   <li v-for="(item,index) in state" :key="index">{{item.name}}----{{item.age}}</li>
 </ul>
 <button @click="handleAdd">按钮</button>
</template>

<script>
import { customRef,ref} from 'vue'
export default {
  name: 'App',
  setup() {
  let state = getData('../public/ab.json',[])
  console.log(state)
  let msg = initState(5) 
  function  handleAdd(){
    msg.value ++
   } 
   return {msg,handleAdd,state}
  }
}

function getData(url,value){
  return customRef((track,trigger) => {
    fetch(url).then(res=>{
     return res.json()
    }).then(data=>{
     value = data.data
     trigger()
    })
     return {
       get() {
         track()
         return value
       },
       set(newValue) {
         value = newValue
         trigger()  // 出发视图更新
       }
     }
   })
}

function initState(value) {
  return customRef((track,trigger) => {
     return {
       get() {
         track()
         return value
       },
       set(newValue) {
         value = newValue
         trigger()  // 出发视图更新
       }
     }
   })
   } 
</script>
复制代码

ref 获取dom


<template>
 <div ref="box">
  我是div
 </div>
</template>

<script>
import {onMounted, ref} from 'vue'
export default {
  name: 'App',
  setup() {
      onMounted(()=>{
         console.log(box.value)
      })
     let box = ref(null)
     console.log(box.value)
  
    return {box}
  }
}
</script>
复制代码

readonly,shallowReadonly,isReadonly

<template>
 <div>
  {{state.list.b.b1}}
  <button @click="handleAdd">按钮</button>
 </div>

</template>

<script>
import { readonly,shallowReadonly,isReadonly } from 'vue'
export default {
  name: 'App',
  setup() {
    // let state = readonly({
    //   list:{
    //     a:'1111',
    //     b:{
    //       b1:"2222",
    //       c:{
    //         c2:"3333"
    //       }
    //     }
    //   }
    // })
    let state = shallowReadonly({
      list:{
        a:'1111',
        b:{
          b1:"2222",
          c:{
            c2:"3333"
          }
        }
      }
    })
    function handleAdd() {
      state.list= '3333333333'
         console.log(state)
    }
    return {state,handleAdd}
  }
}
</script>
复制代码

实现自己的_shallowreactive _shallowref

<template>
 <div>
  {{state.name}}
  <button @click="handleAdd">按钮</button>
 </div>

</template>

<script>

export default {
  name: 'App',
  setup() {
    let obj = {
      name:"1111",
      age:19,
      info:{
        bb:"55555"
      }
    }
    // let state = _shallowreactive(obj)
    // function  handleAdd(){
    //   state.name ='fghhrt'
    //   state.info.bb ='bdfgsfsd'
    // }
     let state = _shallowref(obj)
    function  handleAdd(){
     state.value = '222'
    }
    return {state,handleAdd}
  }
}

function _shallowreactive(obj){
  return new Proxy(obj,{
    get(obj,prop){
      return obj[prop]
    },
    set(obj,prop,value){
        obj[prop] = value 
        console.log("更新ui界面")
        console.log(obj)
        return true
    }
  })
}

function _shallowref(val) {
  return  _shallowreactive({value:val})
}
</script>

复制代码

实现自己的_reactive

<template>
 <div>
  {{state.name}}
  <button @click="handleAdd">按钮</button>
 </div>

</template>

<script>

export default {
  name: 'App',
  setup() {
    let obj = {
      name:"1111",
      age:19,
      info:{
        bb:"55555"
      }
    }
    let state = _reactive(obj)
    function  handleAdd(){
      state.name = '444444'
     state.info.bb = '222'
    }
    return {state,handleAdd}
  }
}

function _reactive(obj){
   if(typeof obj === 'object'){
     if(obj instanceof Array){
      //  如果是数组,取出遍历
       obj.forEach((item,index)=>{
        //   如果数组里面的值是还是对此安好
         if(typeof item === 'object'){
           obj[index] = _reactive(item)
         }
       })
     }else{
       // 如果是对象
       for(let key in obj){
         let item = obj[key]
          if(typeof item === 'object'){
           obj[key] = _reactive(item)
         }
       }
     }

   }else{
     console.warn("不是对象")
   }
  return new Proxy(obj,{
    get(obj,prop){
      return obj[prop]
    },
    set(obj,prop,value){
        obj[prop] = value 
        console.log("更新ui界面")
        console.log(obj)
        return true
    }
  })
}

</script>
复制代码

实现自己的_shallowReadonly

<template>
 <div>
  <!-- {{state.name}} -->
  <button @click="handleAdd">按钮</button>
 </div>

</template>

<script>

export default {
  name: 'App',
  setup() {
    let obj = {
      name:"1111",
      age:19,
      info:{
        bb:"55555"
      }
    }
  
     let state = _shallowReadonly(obj)
    function  handleAdd(){
     state.name = '222'
    //  state.info.bb = '123123'
    }
    return {state,handleAdd}
  }
}

function _shallowReadonly(obj){
  return new Proxy(obj,{
    get(obj,prop){
      return obj[prop]
    },
    set(obj,prop,value){
      console.warn("不能修改")
      return true
        // obj[prop] = value 
        // console.log("更新ui界面")
        // console.log(obj)
        // return true
    }
  })
}

</script>
复制代码

Teleport

有时组件模板的一部分逻辑上属于该组件,而从技术角度来看,最好将模板的这一部分移动到 DOM 中 Vue app 之外的其他位置
最常见的就是类似于element的dialog组件 dialog是fixed定位,而dialog父元素的css会影响dialog。因此要将dialog放在body下

<teleport to="body">
  <div class="modal__mask">
    <div class="modal__main">
      <div class="modal__header">
        <h3 class="modal__title">弹窗标题</h3>
        <span class="modal__close">x</span>
      </div>
      <div class="modal__content">
        弹窗文本内容
      </div>
      <div class="modal__footer">
        <button>取消</button>
        <button>确认</button>
      </div>
    </div>
  </div>
</teleport>
复制代码

在同一目标上使用多个 teleport
一个常见的用例场景是一个可重用的 组件,它可能同时有多个实例处于活动状态。对于这种情况,多个 组件可以将其内容挂载到同一个目标元素。顺序将是一个简单的追加——稍后挂载将位于目标元素中较早的挂载之后。

<teleport to="#modals">
  <div>A</div>
</teleport>
<teleport to="#modals">
  <div>B</div>
</teleport>

<!-- result-->
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>
复制代码

Suspense

前端开发中异步请求是非常常见的事情,比如远程读取图片,调用后端接口等等
Suspense是有两个template插槽的,第一个default代表异步请求完成后,显示的模板内容。fallback代表在加载中时,显示的模板内容。
子组件 child

<template>
  <h1>{{result}}</h1>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
  setup() {
    return new Promise((resolve) => {
      setTimeout(() => {
        return resolve({
          result: 1000
        })
      }, 5000)
    })
  }
})
</script>
复制代码

父组件
当异步没有执行完的时候。使用fallback里面的组件,当执行成功之后使用default

<Suspense>
  <template #default>
    <Child />
  </template>
  <template #fallback>
    <h1>Loading !...</h1>
  </template>
</Suspense>
复制代码

处理异步中的错误

在vue3.x的版本中,可以使用onErrorCaptured这个钩子函数来捕获异常。
当上面的异步发生错误的时候,onErrorCaptured 会捕获错误

const app = {
  name: "App",
  components: { Child},
  setup() {
    onErrorCaptured((error) => {
      return true  
    })
    return {};
  },
};
复制代码

Fragment

在vue2中,我们创建一个组件,他只有有一个根节点

<template>
  <div>
    <div>hello</div>
    <div>world</div>
  </div>
</template>
复制代码

为什么vue2需要这样写,为了底层diff算法
在vue3中可以不用这样写

<template>
    <div>hello</div>
    <div>world</div>
</template>
复制代码

vue3中默认创建了一个Fragment,尽管Fragment看起来像一个普通的DOM元素,但它是虚拟的,根本不会在DOM树中呈现。这样我们可以将组件功能绑定到一个单一的元素中,而不需要创建一个多余的DOM节点。

Emits

Vue 3 目前提供一个 emits 选项,和现有的 props 选项类似。这个选项可以用来定义组件可以向其父组件触发的事件。

在vue2中

  1. 当emits为数组的时候

emits默认要写上,否则当自定义事件和原生事件重名的时候,事件会默认被调用两次

<template>
  <div>
    <p>{{ text }}</p>
    <button v-on:click="$emit('accepted')">OK</button>
  </div>
</template>
<script>
  export default {
    props: ['text']
  }
</script>
复制代码

在vue3中

<template>
  <div>
    <p>{{ text }}</p>
    <button v-on:click="$emit('accepted')">OK</button>
  </div>
</template>
<script>
  export default {
    props: ['text'],
    emits: ['accepted']
  }
</script>
复制代码
  1. 当emits为object的时候,可以对emit进行校验。只有符合条件的才会被触发
<template>
  <div>
    <button @click="handleClick">按钮</button>
  </div>
</template>

<script>

export default{
    emits: {'click':(type)=>{
      return type===1
    }},
  setup() {
    
  },
  methods:{
    handleClick() {
      this.$emit('click',1)
    }
  }
}
</script>
复制代码

v-model

在vue2中使用v-model,一般我们会写成这样

<div id="app">
     <input v-model="price">
 </div>
<script>
    new Vue({
        el: '#app',
        data: {
            price: ''
        }
    });
</script>
复制代码

其实v-model只是一个语法糖,内部实现是这样

<input type="text" :value="price" @input="price=$event.target.value">
复制代码

在vue中父子组件传值的方法

方法一
父组件
<template>
    <div>
        <!--在子组件中用emit("test")传达给父组件的getData方法中-->
        <child @test="getData" :keywords="keywords"></child>
        <button @click="submit">提交</button>
    </div>
</template>
<script>
import child from './child.vue'
export default {
    data() {
        return {
            keywords: '123143'
        }
    },
    components: {
        child
    },
    methods: {
        // 在这里实现更改父组件的值
        getData(val){
            this.keywords = val
        },
        submit() {
            console.log('keywords:', this.keywords)
        }
    }
}
</script>
子组件
<template>
    <div>
        <input @input="inputChange" type="text" :value="keywords">
    </div>
</template>
<script>
export default {
    props: ['keywords'],
    methods: {
        inputChange(e) {
          	// 传给父元素的test函数
            this.$emit('test', e.target.value)
        }
    }
}
</script>
复制代码

这种办法一相对麻烦一点,需要定义在父组件中也要定义一个事件

方法二 v-model

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:

Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})
复制代码

现在在这个组件上使用 v-model 的时候:

<base-checkbox v-model="lovingVue"></base-checkbox>
复制代码

这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新。

方法三 .sync
<template>
  <div class="details">
    <myComponent
        :show.sync='valueChild'
        style="padding: 30px 20px 30px 5px;border:1px solid #ddd;margin-bottom: 10px;"
    >
    </myComponent>
    <button @click="changeValue">toggle</button>
  </div>
</template>
<script>
import Vue from 'vue'

Vue.component(
    'myComponent', {
      template: `
        <div v-if="show">
        <p>默认初始值是{{ show }},所以是显示的</p>
        <button @click.stop="closeDiv">关闭</button>
        </div>`,
      props: ['show'],
      methods: {
        closeDiv() {
          this.$emit('update:show', false); //触发 input 事件,并传入新值
        }
      }
    })
export default {
  data() {
    return {
      valueChild: true,
    }
  },
  methods: {
    changeValue() {
      this.valueChild = !this.valueChild
    }
  }
}
</script>
复制代码

在vue3中将v-model和.sync进行了统一

父组件

<model02
	v-model:age="age02"
	v-model:name="name02"
 ></model02>

复制代码

子组件

<template>
  <div class="custom-input">
    <h1>vue3中的v-model</h1>
    <input type="text" :value="age" @input="onAgeInput"/>
    <br>
    <input type="text" :value="name" @input="onNameInput"/>
  </div>
</template>

<script>
export default {
  name: "Model02",
  props: [
    'age',
    'name'
  ],
  methods: {
    onAgeInput(e) {
      this.$emit('update:age', parseFloat(e.target.value));
    },
    onNameInput(e) {
      this.$emit('update:name', e.target.value)
    }
  }
}
</script>
复制代码

渲染函数api

api的变化

在vue2中

export default {
  render(h) {
    return h('div')
  }
}
复制代码

在vue3中 从vue中导出h

import { h, reactive } from 'vue'

export default {
  setup(props, { slots, attrs, emit }) {
    const state = reactive({
      count: 0
    })

    function increment() {
      state.count++
    }
    // 返回render函数
    return () =>
      h(
        'div',
        {
          onClick: increment
        },
        state.count
      )
  }
}
复制代码

h函数传参的变化

vue2中
domProps 包含 VNode props 中的嵌套列表 点击事件是在on里面

{
  class: ['button', 'is-outlined'],
  style: { color: '#34495E' },
  attrs: { id: 'submit' },
  domProps: { innerHTML: '' },
  on: { click: submitForm },
  key: 'submit-button'
}
复制代码

在vue3中

{
  class: ['button', 'is-outlined'],
  style: { color: '#34495E' },
  id: 'submit',
  innerHTML: '',
  onClick: submitForm,
  key: 'submit-button'
}
复制代码

data

在vue2中data可以是一个函数或者一个对象,在vue3中data只能是一个函数

函数组件

在vue2中,函数组件的两种写法

export default {
  functional: true,
  props: ['level'],
  render(h, { props, data, children }) {
    return h(`h${props.level}`, data, children)
  }
}
复制代码
<template functional>
  <component
    :is="`h${props.level}`"
    v-bind="attrs"
    v-on="listeners"
  />
</template>

<script>
export default {
  props: ['level']
}
</script>
复制代码

在vue3中 不支持函数组件,你可以像下面这样书写

import { h } from 'vue'

const DynamicHeading = (props, context) => {
  return h(`h${props.level}`, context.attrs, context.slots)
}

DynamicHeading.props = ['level']

export default DynamicHeading
复制代码

在 3.x 中,有状态组件和函数式组件之间的性能差异已经大大减少,并且在大多数用例中是微不足道的。因此,在 SFCs 上使用 functional 的开发人员的迁移路径是删除该 attribute,并将 props 的所有引用重命名为 props,将attrs重命名为props,将 attrs 重命名为 attrs。

<template>
  <component
    v-bind:is="`h${$props.level}`"
    v-bind="$attrs"
  />
</template>

<script>
export default {
  props: ['level']
}
</script>
复制代码

异步组件

vue2中使用异步组件的两种方式

Vue.component(
  'async-webpack-example',
  // 这个动态导入会返回一个 `Promise` 对象。
  () => import('./my-async-component')
)
复制代码
const AsyncComponent = () => ({
  // 需要加载的组件 (应该是一个 `Promise` 对象)
  component: import('./MyComponent.vue'),
  // 异步组件加载时使用的组件
  loading: LoadingComponent,
  // 加载失败时使用的组件
  error: ErrorComponent,
  // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  delay: 200,
  // 如果提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 3000
})
复制代码

在vue3中使用异步组件

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
  import('./components/AsyncComponent.vue')
)

app.component('async-component', AsyncComp)
复制代码
  1. 这里和vue2有点区别 component 替换成了loader
 AsyncPageWithOptions: defineAsyncComponent({
	  loader: () => import(".NextPage.vue"),
	  delay: 200, 
	  timeout: 3000,
	  errorComponent: () => import("./ErrorComponent.vue"),
	  loadingComponent: () => import("./LoadingComponent.vue"),
	})

复制代码

event

事件

vue2中的onon,off 和 $once 实例方法已被移除,应用实例不再实现事件触发接口
在vue3中如果还想用,就使用第三方库mitt

事件修饰符

vue3中不在支持
非兼容:不再支持使用数字 (即键码) 作为 v-on 修饰符
非兼容:不再支持 config.keyCodes

vue2中
<!-- 键码版本 -->
<input v-on:keyup.13="submit" />

<!-- 别名版本 -->
<input v-on:keyup.enter="submit" />
复制代码

此外,你可以通过全局 config.keyCodes 选项。

Vue.config.keyCodes = {
  f1: 112
}
复制代码
<!-- 键码版本 -->
<input v-on:keyup.112="showHelpText" />

<!-- 自定别名版本 -->
<input v-on:keyup.f1="showHelpText" />
复制代码
vue3中
<!-- Vue 3 在 v-on 上使用 按键修饰符 -->
<input v-on:keyup.delete="confirmDelete" />
复制代码

指令

  1. 首先写一个简单的指令(和vue2类似) 生命周期发生了变化(请看后续)
const app = Vue.createApp({})
// 注册一个全局自定义指令 `v-focus`
app.directive('focus', {
  // 当被绑定的元素挂载到 DOM 中时……
  mounted(el) {
    // 聚焦元素
    el.focus()
  }
})
复制代码

生命周期(和组件的生命周期一致)

created:在绑定元素的 attribute 或事件监听器被应用之前调用。在指令需要附加须要在普通的 v-on 事件监听器前调用的事件监听器时,这很有用。
beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用。
mounted:在绑定元素的父组件被挂载后调用。
beforeUpdate:在更新包含组件的 VNode 之前调用。
updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用。
beforeUnmount:在卸载绑定元素的父组件之前调用
unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次。
每一个钩子函数都有如下参数:
el: 指令绑定的元素,可以用来直接操作DOM
binding: 数据对象,包含以下属性
instance: 当前组件的实例,一般推荐指令和组件无关,如果有需要使用组件上下文ViewModel,可以从这里获取
value: 指令的值
oldValue: 指令的前一个值,在beforeUpdate和Updated 中,可以和value是相同的内容。
arg: 传给指令的参数,例如v-on:click中的click。
modifiers: 包含修饰符的对象。例如v-on.stop:click 可以获取到一个{stop:true}的对象
vnode: Vue 编译生成的虚拟节点,
prevVNode: Update时的上一个虚拟节点

动态指令参数

假设我们现在需要开发一个定位指令,就是把某个元素固定在页面的某个位置。

  1. 首先让元素固定
<div v-fix:left="20">
     {{count}}
</div>

 directives: {
   fix: {
     mounted(el) {
        el.style.position = 'fixed'
     },
   }
 }
复制代码
  1. 让元素固定在指定位置
 directives: {
   fix: {
     mounted(el) {
        el.style.position = 'fixed'
        const s = binding.arg || 'top'
        el.style[s] = binding.value + 'px'
     },
   }
 }
复制代码

这样我们就完成了一个自定义指令的开发

指令的value可以是一个对象

<div v-demo="{ color: 'white', text: 'hello!' }"></div>
app.directive('demo', (el, binding) => {
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text) // => "hello!"
})
复制代码

watchEffect 和 watch

watchEffect和watch的不同之处

  1. watchEffect 不需要指定监听的属性,他会自动的收集依赖
  2. 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行,而 watch 只能监听指定的属性而做出变更(v3开始可以同时指定多个)
  3. 第二点就是 watch 可以获取到新值与旧值(更新前的值),而 watchEffect 是拿不到的
  4. 第三点是 watchEffect 如果存在的话,在组件初始化的时候就会执行一次用以收集依赖(与computed同理),而后收集到的依赖发生变化,这个回调才会再次执行,而 watch 不需要,因为他一开始就指定了依赖。

watwatchEffect的使用

<template>
 <div>
   <div>
     123123123
   </div>
   <div>
     {{count}}
   </div>
   <button @click="handleClick">增加</button>
 </div>
</template>

<script>
import { ref,watchEffect } from 'vue'


export default {
emits:['click'],
 setup() {
  let count = ref(0)
  const handleClick = () =>{
    count.value++
  }
  watchEffect(() => console.log(count.value))
  return {
    count,
    handleClick
  }
 },
}
</script>
复制代码
watchEffect的停止

watchEffect 会返回一个用于停止这个监听的函数

const stop = watchEffect(() => {
  /* ... */
})

// later
stop()
复制代码

清除副作用

假设我们现在用一个用户ID去查询用户的详情信息,然后我们监听了这个用户ID, 当用户ID 改变的时候我们就会去发起一次请求,这很简单,用watch 就可以做到。 但是如果在请求数据的过程中,我们的用户ID发生了多次变化,那么我们就会发起多次请求,而最后一次返回的数据将会覆盖掉我们之前返回的所有用户详情。这不仅会导致资源浪费,还无法保证 watch 回调执行的顺序。而使用 watchEffect 我们就可以做到。

// 当id改变的时候,之前的异步没有执行完成,会取消之前的异步
watchEffect((onInvalidate) => {
  const token = asyncOperation(id.value);
  onInvalidate(() => {
    // run if id has changed or watcher is stopped
    token.cancel();
  });
});
复制代码

副作用刷新时机

Vue 的响应性系统会缓存副作用函数,并异步地刷新它们,这样可以避免同一个“tick” 中多个状态改变导致的不必要的重复调用。
如果你希望副作用函数在组件更新前发生,可以将flush设为’post’(默认是’pre’)
一般用于获取dom之后的值,会用到post

watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'post'//默认值为 'pre'
  }
)
复制代码

watch的使用

和watchEffect相比较

  1. 懒执行副作用;
  2. 更具体地说明什么状态应该触发侦听器重新运行;
  3. 访问侦听状态变化前后的值。
监听单个数据源
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})
复制代码
监听多个数据源
  watch ([countObj, count], ([oneNewName, twoNewName], [oneOldName, twoOldName]) => {
      console.log(oneNewName, oneOldName, twoNewName, twoOldName)
    })
复制代码
停止侦听,清除副作用

watch 与 watchEffect共享停止侦听,清除副作用 (相应地 onInvalidate 会作为回调的第三个参数传入)、副作用刷新时机和侦听器调试行为。

mixin

在vue2中使用mixin

把 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

export default{
	data(){
		return{
		}
	},
	created() {
    	// do something...
  	},
	methods:{...}
}

// vue页面中引入
import mixin from 'mixin.js'
export default{
	data(){},
	mixins: [mixin]
}
复制代码

在vue3中使用mixin

  1. 数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
const myMixin = {
  data() {
    return {
      message: 'hello',
      foo: 'abc'
    }
  }
}

const app = Vue.createApp({
  mixins: [myMixin],
  data() {
    return {
      message: 'goodbye',
      bar: 'def'
    }
  },
  created() {
    console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "def" }
  }
})
复制代码
  1. 当同名钩子函数将合并为一个数组,因此都将被调用 (两个create都会被调用)
const myMixin = {
  data() {
    return {
      message: 'hello',
      foo: 'abc'
    }
  }
  created() {
    console.log(this.$.message) // => { message: "goodbye", foo: "abc", bar: "def" }
  }
}

const app = Vue.createApp({
  mixins: [myMixin],
  data() {
    return {
      message: 'goodbye',
      bar: 'def'
    }
  },
  created() {
    console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "def" }
  }
})
复制代码
  1. 值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。(这个和vue2一样)

  2. 自定义合并策略

const app = Vue.createApp({
  custom: 'hello!'
})

app.config.optionMergeStrategies.custom = (toVal, fromVal) => {
  console.log(fromVal, toVal)
  // => "goodbye!", undefined
  // => "hello", "goodbye!"
  return fromVal || toVal
}

app.mixin({
  custom: 'goodbye!',
  created() {
    console.log(this.$options.custom) // => "hello!"
  }
})
复制代码

全局api

  1. vue3对于全局api有了很大的调整。 vue2中没有app的概念,我们定义的应用只是通过new Vue()创建vue实例,而且所有的方法都是静态方法绑定在Vue的身上,
    这样会导致两个问题。 全局配置容易污染其他测试用例,2. 经过webpack打包之后会有很多冗余代码
  2. createApp
vue3中
createApp(Main).mount('#app')
vue2中
new Vue({

})
复制代码

其他api的调整

Vue.config	                app.config
Vue.config.productionTip	  removed 
Vue.config.ignoredElements	app.config.isCustomElement
Vue.component	              app.component
Vue.directive	              app.directive
Vue.mixin	                  app.mixin
Vue.use	                    app.use
Vue.prototype	              app.config.globalProperties
复制代码

全部调整为 import {nextTick,observable} from ‘vue’

Vue.nextTick
Vue.observable (用 Vue.reactive 替换)
Vue.version
Vue.compile (仅完整构建版本)
Vue.set (仅兼容构建版本)
Vue.delete (仅兼容构建版本)

复制代码

slot

后期更新

自定义渲染器

后期更新

vue写一个todoList(后续详细讲解,体会vue3的优势)

<template>
 <ul>
   <input type="text" placeholder="id" v-model="addform.form.id">
    <input type="text" placeholder="姓名" v-model="addform.form.name">
     <input type="text" placeholder="年龄" v-model="addform.form.age">
     <button @click="handleAdd">按钮</button>
   <li v-for="(item,index) in todoList.list" :key="item.id" @click="handleDelete(index)">{{item.name}}---{{item.age}}</li>
 </ul> 
</template>

<script>
import { reactive } from 'vue'

export default {
  name: 'App',
  setup() {
    let {todoList,handleDelete} = DeleteTodo()
    let {addform,handleAdd} = AddTodo(todoList)
    return {todoList,addform,handleDelete,handleAdd}
  },
}

function DeleteTodo() {
let obj = [
        {id:1,name:'zs',age:10},
        {id:2,name:'ls',age:20},
        {id:3,name:'ww',age:30}
       ]
    let todoList = reactive({list: obj});
    function handleDelete(i){
      todoList.list = todoList.list.filter((item,index)=>{
        return index !== i
      })
    }
    return {todoList,handleDelete }
}

function AddTodo(todoList) {
 let form = {
      id: '',
      name: '',
      age: ''
    }
  let addform = reactive({form:form})
  function handleAdd() {
      todoList.list.push(form)
    }
  return { addform, handleAdd}
}
</script>
复制代码

如上图: 在vue2中如果我们需要实现一个todolist,首先我们需要在data里面定义数据,然后在methods里面定义方法,可以还需要computed,或者watch,这样一个功能的代码逻辑在多处使用到了,不利于维护。而vue3中可以将一个功能的代码逻辑抽离出来,这样便于维护

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享