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是進行全量的對比,例如css

vue2的在線編譯 vue-template-explorer.netlify.app/html

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

vue3中的diff算法,只會給須要動態改變的標籤會打上一個flag,這樣就減小了diff算法的比對次數前端

vue3的在線編譯 vue-next-template-explorer.netlify.app/vue

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是什麼意思呢node

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中對不參與更新的元素,會作靜態提高,只會被建立一次,在渲染時直接複用便可 未被提高前react

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
複製代碼

被提高後webpack

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發生了變化web

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裏面的兩個參數)vue-cli

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 的全部引用重命名爲 p r o p s ,將 a t t r s 重命名爲 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中的 o n on, 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中能夠將一個功能的代碼邏輯抽離出來,這樣便於維護

相關文章
相關標籤/搜索