Vue3.0 新特性探索

Vue3官網(wǎng)文檔

1、Vue3.0 新變化

  • Proxy:不只是解決了 defineProperty 的局限性
  • Performance:性能比Vue 2.x快1.2~2倍
  • Tree shaking support:可以將無用模塊“剪輯”,僅打包需要的,按需編譯,體積比Vue2.x更小
  • Composition API: 組合API(類似React Hooks)
  • Better TypeScript support:更優(yōu)秀的 Ts 支持
  • Custom Renderer API:暴露了自定義渲染API
  • Fragment, Teleport(Protal), Suspense:更先進(jìn)的組件,“碎片”,Teleport 即 Protal 傳送門,“懸念”。

2、Vue3.0是如何變快

2.1 雙向綁定

2.0現(xiàn)有限制:

  • 無法檢測(cè)到新的屬性添加/刪除
  • 無法監(jiān)聽數(shù)組的變化
  • 需要深度遍歷,浪費(fèi)內(nèi)存

3.0優(yōu)化:

  • 使用 ES6的Proxy 作為其觀察者機(jī)制,取代之前使用的Object.defineProperty。Proxy默認(rèn)可以支持?jǐn)?shù)組
  • 允許框架攔截對(duì)象上的操作
  • 多層對(duì)象嵌套,使用懶代理

Object.defineProperty -> Proxy

Object.defineProperty是一個(gè)相對(duì)比較昂貴的操作,因?yàn)樗苯硬僮鲗?duì)象的屬性,顆粒度比較小。將它替換為es6的Proxy,在目標(biāo)對(duì)象之上架了一層攔截,代理的是對(duì)象而不是對(duì)象的屬性。這樣可以將原本對(duì)對(duì)象屬性的操作變?yōu)閷?duì)整個(gè)對(duì)象的操作,顆粒度變大。

javascript引擎在解析的時(shí)候希望對(duì)象的結(jié)構(gòu)越穩(wěn)定越好,如果對(duì)象一直在變,可優(yōu)化性降低,proxy不需要對(duì)原始對(duì)象做太多操作。

2.2 虛擬DOM

https://vue-next-template-explorer.netlify.app/
2.0 虛擬 DOM性能瓶頸:

  • 雖然vue能夠保證觸發(fā)更新的組件最小化,但單個(gè)組件部分變化需要遍歷該組件的整個(gè)vdom樹
  • 傳統(tǒng)vdom性能跟模版大小正相關(guān),跟動(dòng)態(tài)節(jié)點(diǎn)的數(shù)量無關(guān)

3.0優(yōu)化工作

  • 在 vue 3.0 中重新推翻后重寫了虛擬 DOM 的實(shí)現(xiàn),官方宣稱渲染速度最快可以翻倍。更多編譯時(shí)的優(yōu)化以減少運(yùn)行時(shí)的開銷

diff算法優(yōu)化

  • Vue2中的虛擬dom是進(jìn)行全量的對(duì)比,即數(shù)據(jù)更新后在虛擬DOM中每個(gè)標(biāo)簽內(nèi)容都會(huì)對(duì)比有沒有發(fā)生變化

  • Vue3新增了靜態(tài)標(biāo)記(PatchFlag)

    • 在創(chuàng)建虛擬DOM的時(shí)候會(huì)根據(jù)DOM中的內(nèi)容會(huì)不會(huì)發(fā)生變化添加靜態(tài)標(biāo)記

    • 數(shù)據(jù)更新后,只對(duì)比帶有patch flag的節(jié)點(diǎn)

      <div>
        <p>我是一個(gè)標(biāo)題</p>
        <p>{{msg}}</p> <!-- 雙向綁定 -->
      </div>
      
      
      // 虛擬DOM
      export function render(_ctx, _cache, $props, $setup, $data, $options) {
        return (_openBlock(), _createBlock("div", null, [
          _createVNode("p", null, "我是一個(gè)標(biāo)題"), // 不標(biāo)記
          _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */) // 標(biāo)記
        ]))
      }
      
      
    • 并且可以通過flag的信息得知當(dāng)前節(jié)點(diǎn)要對(duì)比的具體內(nèi)容

      export const enum PatchFlags {
        TEXT = 1,// 動(dòng)態(tài)文本節(jié)點(diǎn)
        CLASS = 1 << 1, // 2  // 動(dòng)態(tài) class
        STYLE = 1 << 2, // 4 // 動(dòng)態(tài) style
        PROPS = 1 << 3, // 8 // 動(dòng)態(tài)屬性,但不包含類名和樣式
        FULL_PROPS = 1 << 4, // 16 // 具有動(dòng)態(tài) key 屬性,當(dāng) key 改變時(shí),需要進(jìn)行完整的 diff 比較。
        HYDRATE_EVENTS = 1 << 5, // 32 // 帶有監(jiān)聽事件的節(jié)點(diǎn)
        STABLE_FRAGMENT = 1 << 6, // 64 // 一個(gè)不會(huì)改變子節(jié)點(diǎn)順序的 fragment
        KEYED_FRAGMENT = 1 << 7, // 128 // 帶有 key 屬性的 fragment 或部分子字節(jié)有 key
        UNKEYED_FRAGMENT = 1 << 8, // 256 // 子節(jié)點(diǎn)沒有 key 的 fragment
        NEED_PATCH = 1 << 9, // 512 // 一個(gè)節(jié)點(diǎn)只會(huì)進(jìn)行非 props 比較
        DYNAMIC_SLOTS = 1 << 10, // 1024 // 動(dòng)態(tài) slot
        HOISTED = -1, // 靜態(tài)節(jié)點(diǎn)
        // 指示在 diff 過程應(yīng)該要退出優(yōu)化模式
        BAIL = -2
      }
      
      

2.2 hoistStatic 靜態(tài)提升

  • Vue2中無論元素是否參與更新, 每次都會(huì)重新創(chuàng)建, 然后再渲染
  • Vue3中對(duì)于不參與更新的元素, 會(huì)做靜態(tài)提升, 只會(huì)被創(chuàng)建一次, 在渲染時(shí)直接復(fù)用即可
<div>
  <p>我是一個(gè)標(biāo)題</p>
  <p>{{msg}}</p>
</div>

// 靜態(tài)提升之前
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", null, "我是一個(gè)標(biāo)題"), // 每次都會(huì)創(chuàng)建一個(gè)虛擬節(jié)點(diǎn)
    _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */) 
  ]))
}

// 靜態(tài)提升之后
const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "我是一個(gè)標(biāo)題", -1 /* HOISTED */) // 定義了一個(gè)全局變量,只會(huì)創(chuàng)建一次

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 事件監(jiān)聽緩存

默認(rèn)情況下onClick會(huì)被視為動(dòng)態(tài)綁定, 所以每次都會(huì)去追蹤它的變化,但是因?yàn)槭峭粋€(gè)函數(shù),所以沒有追蹤變化, 直接緩存起來復(fù)用即可

<div>
  <button @click="onClick">按鈕</button>
</div>

// 開啟事件監(jiān)聽緩存之前
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("button", { onClick: _ctx.onClick }, "按鈕", 8 /* PROPS */, ["onClick"])
  ])) // 有靜態(tài)標(biāo)記 
}

// 開啟事件監(jiān)聽緩存之后
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)))
    }, "按鈕")
  ])) // 沒有靜態(tài)標(biāo)記
}

值得注意的新API

1.創(chuàng)建vue3.0項(xiàng)目的方法

(1)使用vite
npm init vite-app 項(xiàng)目名
cd 項(xiàng)目名
npm run dev

使用這種方式的優(yōu)缺點(diǎn):
① 創(chuàng)建速度快,不通過webpack編譯
② 還需要自己安裝vue-router、vuex (vue-router以及vuex都要相應(yīng)的升級(jí)為4.0版本)

vite是什么?

vite是基于vue3單文件組件的非打包開發(fā)服務(wù)器
vite 是一個(gè)基于 Vue3 單文件組件的非打包開發(fā)服務(wù)器,vite具有以下的優(yōu)點(diǎn):
可以快速的冷啟動(dòng),不需要等待打包;
即時(shí)的熱模塊更新;
不用等待整個(gè)項(xiàng)目編譯完成。

原理:ES module/.vue文件拆分為http請(qǐng)求+type?=參數(shù)/熱更新(koa/websocket/watcher)
vite使用ES module,代碼以模塊的方式引入到文件;即在瀏覽器使用import/export方式導(dǎo)入/導(dǎo)出,webpack使用require方式導(dǎo)入。
vite使用koa構(gòu)建的服務(wù)端,webpack使用webpack-dev-server構(gòu)建服務(wù)端。

(2)利用vue-cli
npm install -g @vue/cli
vue create 項(xiàng)目名
cd 項(xiàng)目名
vue add vue-next //重點(diǎn) 執(zhí)行這行,會(huì)把2.0代碼改為3.0的, vue-router, vuex會(huì)變成4.0的
npm run serve

介紹完安裝vue3.0,接下來,咱們就正式進(jìn)入咱們今天的重點(diǎn)了~

vue3.0的主要變化

響應(yīng)式基本原理:Object.defineProperty -> Proxy,提高性能
初始化方式:Options api -> composition api,提供代碼復(fù)用,更好的tree-shaking
初始化項(xiàng)目:vue-cli -> vite,提高開發(fā)效率
擴(kuò)展方法:Vue.property.xxx -> config.globalProperties.xxx,更好的tree-shaking
實(shí)例化:new Vue -> createApp。
原來的寫法,如果一個(gè)項(xiàng)目有多個(gè)Vue實(shí)例,那么多個(gè)實(shí)例造成污染:

Vue.use(plugin)
new Vue({el:'app1'});
new Vue({el:'app2'});

而createApp這種方式,每次創(chuàng)建一個(gè)vue實(shí)例,然后use不同插件

app1 = createApp({});
app2 = createApp({});
app1.use(plugin1)
app2.use(plugin2)

setup內(nèi)部不支持this,因?yàn)閟etup時(shí)組件實(shí)例還沒有創(chuàng)建,通過setup的第二個(gè)參數(shù)context獲取實(shí)例
支持ts -> ts的優(yōu)點(diǎn):類型檢查/編譯器提示/ts擁抱ES6規(guī)范及部分起草規(guī)范

2.相比于vue2.0,vue3.0的新變化

1、main.js,新增createApp方法

// vue2.0
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import './App.scss'
 
new Vue({
   el: '#app',
   router,
   store,
   template: '<App/>',
   components: { App }
 })
 
 
 
// vue3.0
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import './App.scss'
 
const app = createApp(App)
app.use(router).use(store).mount('#app');

注意: 由于vue3.0 使用的是 import {createApp} from 'vue' 而不是像vue2.0的import Vue from 'vue'。因此之前使用的ui框架以及第三方包可能會(huì)因?yàn)椴恢С講ue3.0而報(bào)錯(cuò)。

2、template模板(Fragment)

vue2.0里template只支持單一根節(jié)點(diǎn),在vue3.0里可以使用多個(gè)根節(jié)點(diǎn)

<template>
    <!-- vue3.0組件的根節(jié)點(diǎn)可以有多個(gè),或者使用<Fragment> 空標(biāo)簽 -->
    <div class="login"></div>
    <div class="main"></div>
    <div></div>
</template>

3、組合式API(Composition API)

composition api是什么?

組合api:將組件屬性公開為函數(shù)api
data -> reactive()/ref()
computed -> computed():創(chuàng)建計(jì)算屬性,返回的是ref實(shí)例
watch -> watch()
provide/inject -> provide()/inject()
lifeCicle -> on+xxx

options api的優(yōu)點(diǎn):保證代碼的下限,在指定的地方寫指定的代碼
options api的缺點(diǎn):同一個(gè)邏輯代碼比較分散

composition api的優(yōu)點(diǎn):自由度高,復(fù)用性提高
composition api的缺點(diǎn):對(duì)編碼人員的要求比較高

composition API的設(shè)計(jì)動(dòng)機(jī)

組合API實(shí)戰(zhàn)教程

  1. 邏輯復(fù)用及代碼整理
    vue2.x中代碼復(fù)用是通過mixin提取公共邏輯和通過作用域插槽編寫復(fù)用性組件
    mixin的缺點(diǎn):名稱一樣時(shí)會(huì)被覆蓋
    作用域插槽的缺點(diǎn):只能在模板中使用
    vue3.0中代碼復(fù)用通過composition API。對(duì)于業(yè)務(wù)邏輯相同的代碼,可以抽取到一個(gè)js文件,然后導(dǎo)入到不同到組件
    composition的缺點(diǎn):自由度高,需要編碼人員有比較高的抽象能力
  2. 更好的typescript支持

使用傳統(tǒng)的option配置方法寫組件的時(shí)候問題,隨著業(yè)務(wù)復(fù)雜度越來越高,代碼量會(huì)不斷的加大;由于相關(guān)業(yè)務(wù)的代碼需要遵循option的配置寫到特定的區(qū)域,導(dǎo)致后續(xù)維護(hù)非常的復(fù)雜,同時(shí)代碼可復(fù)用性不高,而composition-api就是為了解決這個(gè)問題而生的。

在vue2.0里我們都把.vue文件里的js部分叫做Options API, 而在3.0里變?yōu)镃omposition API。

注:Composition API 里有vue3.0的新增的主要新特性:

(1)一個(gè)全新的屬性 setup ,這是一個(gè)組件的入口,讓我們可以運(yùn)用 Vue3.0 暴露的新接口,它運(yùn)行在組件被實(shí)例化時(shí)候,props 屬性被定義之后,實(shí)際上等價(jià)于 Vue2.0 版本的 beforeCreate 和 Created 這兩個(gè)生命周期,setup 返回的是一個(gè)對(duì)象,里面的所有被返回的屬性值,都會(huì)被合并到 Vue2.0 的 render 渲染函數(shù)里面,在單文件組件中,它將配合 模板的內(nèi)容,完成Model到View之間的綁定,在未來版本中應(yīng)該還會(huì)支持返回 JSX 代碼片段。

  • 組合API的入口函數(shù), 在 beforeCreate 之后、created 之前執(zhí)行 ,主要是實(shí)現(xiàn)數(shù)據(jù)和業(yè)務(wù)邏輯不分離
  • 無法使用data和methods,所以為了避免錯(cuò)誤使用,直接將內(nèi)部的this改成了undefined
  • 內(nèi)部的方法只能是同步,不能是異步

(2)setup函數(shù)的第一個(gè)參數(shù) props 是父組件傳進(jìn)來的值,在 Vue2.0 中我們可以使用 props 屬性值完成父子通信,在這里我們需要定義 props 屬性去定義接受值的類型,然后我們可以利用 setup 的第一個(gè)參數(shù)獲取 props 使用。

(3) setup 函數(shù)的第二個(gè)參數(shù)context是一個(gè)上下文對(duì)象,這個(gè)上下文對(duì)象中包含了一些有用的屬性,這些屬性在 Vue2.0 中需要通過 this 才能訪問到,在 vue3.0 中,訪問他們變成以下形式:

context.parent--> this.$parent

context.root--> this

context.emit-->this.$emit

context.refs-->this.$refs

context.slots --> this.$slots
程序執(zhí)行setup時(shí),組件尚未被創(chuàng)建,因此能訪問到的有用屬性有: root、parent、refs、attrs、listeners、isServer、ssrContext、emit 于此同時(shí) data、computed、methods等是訪問不到的.

(4)setup()不單可以return 對(duì)象,還可以返回方法。

(5)利用watchEffect可以監(jiān)聽props。

// 父組件
<template>
    <Child val="測(cè)試"/>
</template>
<script>
    import {setup} from 'vue';
    import Child from './Child';
    export default {
        name: 'Parent',
        components: {
            Child
        },
        setup(props, ctx) {
            console.log(ctx); // 在setup()中無法訪問到this
            console.log(this); // undefined
        }
    }
</script>
 
// 子組件 Child
<template></tempalte>
<script>
    import {setup, watchEffect} from 'vue';
    export default {
        name: 'Child',
        // props: ['val'], // 跟vue2.0相同 也可以采用對(duì)象形式
        props: {
            val: String
        },
        setup(props, context) {
            console.log(props); // 在這里可以獲取父組件傳過來的屬性值 
            watchEffect(() => { // 利用watchEffect監(jiān)聽props 
                console.log(props.val); // 首次以及props改變才會(huì)執(zhí)行這里的代碼
            })
            return { // setup 返回的對(duì)象/方法  可以在模板中被訪問得到
            }
        }
    }
    </script>

vue2.0 里數(shù)據(jù)都是在data里定義, 而在vue3.0可以使用reactive, ref

state更名為reactive
reactive等價(jià)于 Vue 2.x 的Vue.observable(),用于獲取一個(gè)對(duì)象的響應(yīng)性代理對(duì)象
const obj = reactive({ count: 0 });

value更名為ref,并提供isRef和toRefs
const unwrapped = isRef(foo) ? foo.value : foo;

reactive 函數(shù)
  • 可以監(jiān)聽復(fù)雜類型(json/Array)的變化
  • 實(shí)現(xiàn)響應(yīng)式數(shù)據(jù)的方法,Vue2中通過Object.defineProperty實(shí)現(xiàn)的,Vue3通過ES6的Proxy實(shí)現(xiàn)
  • 注意點(diǎn):如果給 reactive 傳遞了其他對(duì)象,即不是 json/Array
    • 默認(rèn)情況下修改對(duì)象界面不會(huì)自動(dòng)更新
    • 如果要更新,可以通過重新賦值的方式
reactive 傳遞基本數(shù)據(jù)類型
import { reactive } from 'vue'

setup() {
  let state = reactive(123) // 傳遞基本數(shù)據(jù)類型
  function fn() {
    state = 666 // 界面不會(huì)更新,不是 Proxy 對(duì)象
    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 傳遞其他對(duì)象
import { reactive } from 'vue'

setup() {
  let state = reactive({time: new Date()}) // 其他對(duì)象
  function fn() {
    state.time.setDate(state.time.getDate() + 1) // 界面不會(huì)更新
    console.log(state) // Proxy{...}
  }
}

import { reactive } from 'vue'

setup() {
  let state = reactive({time: new Date()}) // 其他對(duì)象
  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 函數(shù)
  • 本質(zhì)還是 reactive ,當(dāng)給 ref 函數(shù)傳遞一個(gè)值后,底層會(huì)自動(dòng)轉(zhuǎn)成 reactive,所以一般都是使用 ref 函數(shù)創(chuàng)建響應(yīng)式數(shù)據(jù)

    • ref(10) -> reactive({value: 18})
    • 在 setup 函數(shù)內(nèi)部修改 ref 的值時(shí)必須通過 .value 的方式
    import { ref } from 'vue'
    
    setup() {
      let state = ref(10)
      function fn() {
        state.value = 20
        console.log(state) // RefImp{...}
      }
    }
    
    
    • 在 template 中使用不用添加 .value,Vue內(nèi)部已經(jīng)進(jìn)行了轉(zhuǎn)換
3.1.3 reactive 和 ref 的區(qū)別

Vue在解析數(shù)據(jù)之前,會(huì)通過當(dāng)前數(shù)據(jù)的__v_ref這個(gè)私有屬性判斷這個(gè)數(shù)據(jù)是否是ref類型,如果值為true就是一個(gè)ref類型的數(shù)據(jù)

  • 在template里使用的是ref類型的數(shù)據(jù),Vue會(huì)自動(dòng)添加.value
  • 在template里使用的是reactive類型的數(shù)據(jù),Vue不會(huì)自動(dòng)添加.value

注意: compisition-api 的本質(zhì)也是將 return 出去的值注入到 option-api 中

return {state, remStu}

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

3.1.4 markRaw 函數(shù)

數(shù)據(jù)永遠(yuǎn)不會(huì)被追蹤

setup() {
  let obj = {name: 'zs', age: 18}
  obj = markRaw(obj) // 此時(shí)無論怎么修改都不會(huì)更新數(shù)據(jù)
  let state = reactive(obj) 
}

3.1.5 toRef 函數(shù)

如果利用ref將某一個(gè)對(duì)象中的屬性變成響應(yīng)式的數(shù)據(jù),修改響應(yīng)式的數(shù)據(jù)是不會(huì)影響到原始數(shù)據(jù)的

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 將一個(gè)對(duì)象的某一個(gè)屬性變成響應(yīng)式的數(shù)據(jù),修改響應(yīng)式數(shù)據(jù)會(huì)影響原始數(shù)據(jù),但不會(huì)更新視圖,只是引用了原始數(shù)據(jù)

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
  }
}

應(yīng)用場(chǎng)景:

如果想讓響應(yīng)式數(shù)據(jù)和原始數(shù)據(jù)關(guān)聯(lián)起來,并且修改數(shù)據(jù)之后不想更新UI界面,那么就可以使用

3.1.6 toRefs 函數(shù)

將對(duì)象的全部屬性變成響應(yīng)式數(shù)據(jù),修改后不更新視圖

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.1.7 toRaw 函數(shù)

ref 和 reactive 數(shù)據(jù)類型每次修改都會(huì)被追蹤,都會(huì)更新UI界面,這樣會(huì)非常消耗性能,有時(shí)候一些數(shù)據(jù)不需要追蹤就可以通過toRaw方法拿到它的原始數(shù)據(jù),對(duì)原始數(shù)據(jù)進(jìn)行修改就不會(huì)被追蹤

setup() {
  let obj = {name: 'zs', age: 18}
  let state = reactive(obj) // 包裝后的對(duì)象
  let obj2 = toRaw(state)
  /* 
  * let state = ref(obj)
  * let obj2 = toRaw(state.value)
  */
  console.log(obj === obj2) // true
  /* 
  * state 和 obj 的關(guān)系:
  * state 本質(zhì)是一個(gè) Proxy 對(duì)象,只是引用了 obj
  */
  console.log(obj === state) // false

  // 修改原始數(shù)據(jù)
  function fn() {
    obj2.name = 'ls' // 這里只是在內(nèi)存里發(fā)生了改變,并不會(huì)更新視圖
    // obj.name = 'ls' 這樣寫才會(huì)更新視圖
  }
}

data 函數(shù)

在 rfcs 中就有提到 data() 將不在支持作為一個(gè)對(duì)象,只能作為一個(gè) function 返回一個(gè)對(duì)象。雖然在 Vue 2.x 中可以返回一個(gè)狀態(tài)進(jìn)行狀態(tài)共享,但是這勢(shì)必會(huì)引發(fā)一些問題,而且如果要實(shí)現(xiàn)這種狀態(tài)共享,function 完全可以替代,通過 function 返回對(duì)象,v-bind 給子組件就可以實(shí)現(xiàn)狀態(tài)共享,使用 object 會(huì)對(duì)邏輯造成紊亂,并且需要示例去說明,更加重了學(xué)習(xí)者的心智負(fù)擔(dān)!所以在 Vue 3.0 中不再支持 object 方式,強(qiáng)行使用編譯不會(huì)通過,并且給出警告:[Vue warn]: data() should return an object.

watch函數(shù)

  import { reactive, watch } from "vue";
    import store from "../stores";
    export default {
      setup() {
        const state = reactive({
          searchValue: '',
        });
        // 監(jiān)聽搜索框的值
        watch(
          () => {
            return state.searchValue;
          },
          () => {
            // 存儲(chǔ)輸入框到狀態(tài) store 中心,用于組件通信
            store.setSearchValue(state.searchValue);
          }
        );
        return {
          ...toRefs(state)
        };
      }
    };

computed

跟 Vue2.0 的使用方式很相近,同樣需要按需導(dǎo)入該模塊 , 計(jì)算屬性分為兩種,只讀 以及可讀可寫

<tempalte>
    <div>{{addCount}}</div>
    <div>{{addCount2}}</div>
</tempalte>
 
<script>
    import {setup, reactive, computed} from 'vue';
    export default {
        setup(props, context) {
            const count = ref(0);
            const addCount = coomputed(() => count.value + 1); // 只讀
            const addCount2 = computed(() => {
                get: () => count.value + 1,  // 取值函數(shù)
                set: (value) => count.value = value; // 賦值函數(shù)
            })
            return {
                count,
                addCount,
                addCount2
            }
        }
    }
</script>

生命周期lifecycle hooks

① vue2.0的生命周期的鉤子函數(shù)有: beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed

② vue3.0的生命周期在vue2.0的基礎(chǔ)上做了些修改, 新版本的都是以onXxx()函數(shù)注冊(cè)使用,其中 beforeCreate、created 這兩個(gè)函數(shù)可以由setup()代替

    import { onMounted, onUpdated, onUnmounted } from "@vue/composition-api";//引入鉤子
    export default {
      setup(props, ctx) {
         // `props` 屬性被定義之后,實(shí)際上等價(jià)于 `Vue2.0` 版本的 `beforeCreate` 和 `Created` 這兩個(gè)生命周期
        const loadMore = () => {};
        onMounted(() => {
          loadMore();
        });
        onUpdated(() => {
          console.log('updated!')
        })
        onUnmounted(() => {
          console.log('unmounted!')
        })
        return {
          loadMore
        };
      }
    };

新舊版本生命周期對(duì)比 :
Options API Hook inside inside setup
beforeCreate Not needed*
created Not needed*

beforeCreate --> use setup()

created --> use setup()

beforeMount --> onBeforeMount

mounted --> onMounted

beforeUpdate --> onBeforeUpdate

updated --> onUpdated

beforeDestory --> onBeforeUnmount

destoryed --> onUnmounted

errorCaptured --> onErrorCaptured

同時(shí)vue3.0 還提供了2個(gè)全新的生命周期幫助我們?nèi)フ{(diào)試代碼:

onRenderTracked

onRenderTriggered

3.2 遞歸監(jiān)聽

默認(rèn)情況下,ref和reactive創(chuàng)建的數(shù)據(jù)都是遞歸監(jiān)聽,即無論里面套多少層都可以監(jiān)聽得到

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

ref.b.c.value = 3 // 可以監(jiān)聽,且會(huì)更新界面

缺點(diǎn):當(dāng)數(shù)據(jù)量比較大時(shí),會(huì)非常消耗性能,因?yàn)槊恳粚佣际且粋€(gè)Proxy對(duì)象

3.3 非遞歸監(jiān)聽

只能監(jiān)聽第一層,不能監(jiān)聽其它層,只有第一層數(shù)據(jù)改變了,其它層才會(huì)被監(jiān)聽,可以通過 shallowReactive、shallRef 創(chuàng)建非遞歸監(jiān)聽數(shù)據(jù)

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

// 這樣可以被監(jiān)聽
state.a = 'a'  /* 如果第一層不變,則視圖不會(huì)更新 */
state.b.c = 'c'
state.b.d.e = 'e'

3.3.2 shallowRef

本質(zhì)是 shallowReactive ,當(dāng)給 shallowRef 函數(shù)傳遞一個(gè)值后,底層會(huì)自動(dòng)轉(zhuǎn)成 shallowReactive,所以 shallowRef 的第一層是.value

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

// shallowRef 的第一層是 state.value,只有state.value 發(fā)生變化才會(huì)更新視圖
state.value = {
  a: 2,
  b: {
    c: 3,
    d: {
      e: 4
    }
  }
}

3.3.2 triggerRef

自動(dòng)觸發(fā)一次shallowRef的數(shù)據(jù)更新,沒有triggerReactive函數(shù)

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

state.b.d.e = 'e'
triggerRef(state) // 此時(shí)視圖會(huì)更新

3.1.1 刪除數(shù)據(jù)
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 新增數(shù)據(jù)
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>

Tree-shaking Global API

Tree-shaking的本質(zhì)是消除無用的js代碼。無用代碼消除在廣泛存在于傳統(tǒng)的編程語(yǔ)言編譯器中,編譯器可以判斷出某些代碼根本不影響輸出,然后消除這些代碼,這個(gè)稱之為DCE(dead code elimination)。
Tree-shaking 是 DCE 的一種新的實(shí)現(xiàn),Javascript同傳統(tǒng)的編程語(yǔ)言不同的是,javascript絕大多數(shù)情況需要通過網(wǎng)絡(luò)進(jìn)行加載,然后執(zhí)行,加載的文件大小越小,整體執(zhí)行時(shí)間更短,所以去除無用代碼以減少文件體積,對(duì)javascript來說更有意義。
Tree-shaking 和傳統(tǒng)的 DCE的方法又不太一樣,傳統(tǒng)的DCE 消滅不可能執(zhí)行的代碼,而Tree-shaking 更關(guān)注宇消除沒有用到的代碼。

在 Vue2.x 的版本中,在 Vue.prototype 定義了全局 API,如下:這會(huì)導(dǎo)致 Vue 庫(kù)的整體體積較大,部署生產(chǎn)時(shí)即使未用到的 API ,也會(huì)被打包。

import Vue from 'vue'

Vue.nextTick(() => {})

const obj = Vue.observable({})

Vue 3.0 在平衡功能和包體積大小做了一定的努力,力圖在 Vue 包做到更新并且不限制其功能。Vue 3.0 中使用了 組合式 API,通過 ES Modules 的靜態(tài)分析的設(shè)計(jì),與現(xiàn)代的 捆綁器 webpack 和 rollup 相結(jié)合,Tree-shaking 中剔除了那些未在項(xiàng)目使用卻導(dǎo)出 ES Module API,如此,用戶只會(huì)使用到那些 import 的 API.

需要注意的是: 在使用模塊打包器構(gòu)建 ES Module 包時(shí)可以 Tree-shaking,在打包 UMD 構(gòu)建包時(shí)還是會(huì)全局打包 API 至 Vue 全局變量中.

teleport 傳送

<teleport> 組件的實(shí)現(xiàn)動(dòng)機(jī)時(shí),解決了組件樹的一個(gè)弱點(diǎn), 能夠?qū)⒔M件中的模板遷移帶 DOM 的其他位置,在沒有我們的DOM樹的情況下,將其從深層嵌套的位置中分解出來。

example:

<body>
  <div id="app">
    <h1>Move the #content with the portal component</h1>
    <teleport to="#endofbody">
      <div id="content">
        <p>
          this will be moved to #endofbody.<br />
          Pretend that it's a modal
        </p>
        <Child />
      </div>
    </teleport>
  </div>
  <div id="endofbody"></div>
  <script>
    new Vue({
      el: "#app",
      components: {
        Child: { template: "<div>Placeholder</div>" }
      }
    });
  </script>
</body>

result:

<div id="app">
  <!-- -->
</div>
<div id="endofbody">
  <div id="content">
    <p>
      this will be moved to #endofbody.<br />
      Pretend that it's a modal
    </p>
    <div>Placeholder</div>
  </div>
</div>

渲染函數(shù)

地址

const app = Vue.createApp({})

app.component('anchored-heading', {
  render() {
    const { h } = Vue

    return h(
      'h' + this.level, // tag name
      {}, // props/attributes
      this.$slots.default() // array of children
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

遷移升級(jí)v3 地址

注意:vue3.0兼容vue2.0 vue版本升級(jí)之后不需要更改關(guān)于vue部分的代碼,但是項(xiàng)目中使用的相關(guān)的插件就不一定了~

醬醬醬~目前vue3.0的新特性就這些,后期有更新的話,繼續(xù)補(bǔ)充

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容