帶你瞭解 vue-next(Vue 3.0)之 初入茅廬

這幾天,陸續學習瞭解了關於vue-next(Vue 3.0)的一些新特性,尤爲是新的Composition API的用法。這套新的API中最重要、最核心的部分,恐怕就是實現響應式功能的這一塊了。並且,這套響應式API不只能夠在vue-next環境下使用,也能夠獨立使用。javascript

筆者在閱讀源碼看到,vue-next已所有由TypeScript構建,看來 ts 必學技能。接下來帶你瞭解vue-nexthtml

vue-next計劃並已實現的主要架構改進和新功能:vue

  • 使用模塊化架構
  • 優化 "Block tree"
  • 更激進的 static tree hoisting 功能
  • 支持 Source map
  • 內置標識符前綴(又名 "stripWith")
  • 內置整齊打印(pretty-printing)功能
  • 移除 source map 和標識符前綴功能後,使用 Brotli 壓縮的瀏覽器版本精簡了大約 10KB

運行時(Runtime)的更新主要體如今如下幾個方面:java

  • 速度顯著提高
  • 同時支持 Composition API 和 Options API,以及 typings
  • 基於 Proxy 實現的數據變動檢測
  • 支持 Fragments
  • 支持 Portals
  • 支持 Suspense w/ async setup()

最後,還有一些 2.x 的功能還沒有移植過來,以下:node

  • SFC compiler
  • Server-side rendering (服務端渲染SSR)

==目前不支持IE11==react

vue-next(Vue 3.0) 的源碼雖然發佈了,可是預計最先也須要等到 2020 年第一季度纔有可能發佈 3.0 正式版。git

目錄剖析

代碼倉庫中有個 packages 目錄,裏面主要是 vue-next 的相關源碼功能實現,具體內容以下所示。github

├── packages
│   ├── compiler-core # 全部平臺的編譯器
│   ├── compiler-dom # 針對瀏覽器而寫的編譯器
│   ├── reactivity # 數據響應式系統
│   ├── runtime-core # 虛擬 DOM 渲染器 ,Vue 組件和 Vue 的各類API
│   ├── runtime-dom # 針對瀏覽器的 runtime。其功能包括處理原生 DOM API、DOM 事件和 DOM 屬性等。
│   ├── runtime-test # 專門爲測試寫的runtime
│   ├── server-renderer # 用於SSR
│   ├── shared # 幫助方法
│   ├── template-explorer
│   └── vue # 構建vue runtime + compiler
複製代碼
  • compiler-core:平臺無關的編譯器,它既包含可擴展的基礎功能,也包含全部平臺無關的插件。暴露了 AST 和 baseCompile 相關的 API,它能把一個字符串變成一棵 ASTchrome

  • compiler-dom:基於compiler-core封裝針對瀏覽器的compilervue-cli

  • runtime-core:與平臺無關的運行時環境。支持實現的功能有虛擬 DOM 渲染器、Vue 組件和 Vue 的各類API, 能夠用來自定義 renderer ,vue2中也有

  • runtime-dom:針對瀏覽器的 runtime。其功能包括處理原生 DOM API、DOM 事件和 DOM 屬性等, 暴露了重要的render和createApp方法

    const { render, createApp } = createRenderer<Node, Element>({
      patchProp,
      ...nodeOps
    })
    
    export { render, createApp }
    複製代碼
  • runtime-test:一個專門爲了測試而寫的輕量級 runtime。好比對外暴露了renderToString方法,在此感慨和react愈來愈像了

  • server-renderer:用於 SSR,還沒有實現。

  • shared:沒有暴露任何 API,主要包含了一些平臺無關的內部幫助方法。

  • vue:「完整」版本,引用了上面提到的 runtime 和 compiler目錄。入口文件代碼以下

    'use strict'
    
    if (process.env.NODE_ENV === 'production') {
      module.exports = require('./dist/vue.cjs.prod.js')
    } else {
      module.exports = require('./dist/vue.cjs.js')
    }
    複製代碼

    因此想閱讀源碼,仍是要看構建流程,這個和vue2也是一致的

回顧 Vue2.0 響應式原理機制 - defineProperty

這個原理老生常談了,就是攔截對象,給對象的屬性增長setget方法,由於核心是defineProperty因此還須要對數組的方法進行攔截

對對象進行攔截

function observer(target){
  // 若是不是對象數據類型直接返回便可
  if(typeof target !== 'object'){
    return target
  }
  // 從新定義key
  for(let key in target){
    defineReactive(target,key,target[key])
  }
}

function update(){
  console.log('update view')
}

function defineReactive(obj,key,value){
  observer(value); // 有可能對象類型是多層,遞歸劫持
  Object.defineProperty(obj,key,{
    get(){
      // 在get 方法中收集依賴
      return value
    },
    set(newVal){
      if(newVal !== value){
        observer(value);
        update(); // 在set方法中觸發更新
      }
    }
  })
}

const obj = {name:'zhuanzhuan'}
observer(obj);

obj.name = 'new-name';
複製代碼
輸出:
update view
複製代碼

數組方法劫持

const oldProtoMehtods = Array.prototype
const proto = Object.create(oldProtoMehtods)

function update(){
  console.log('update view')
}

function defineReactive(obj,key,value){
  observer(value) // 有可能對象類型是多層,遞歸劫持
  Object.defineProperty(obj,key,{
    get(){
      // 在get 方法中收集依賴
      return value
    },
    set(newVal){
      if(newVal !== value){
        observer(value)
        update() // 在set方法中觸發更新
      }
    }
  })
}

['push','pop','shift','unshift'].forEach(method=>{
  Object.defineProperty(proto, method,{
    get(){
      update()
      return oldProtoMehtods[method]
    }
  })
})

function observer(target){
  if(typeof target !== 'object'){
    return target
  }
  // 若是不是對象數據類型直接返回便可
  if(Array.isArray(target)){
    Object.setPrototypeOf(target, proto)
    // 給數組中的每一項進行observr
    for(let i = 0 ; i < target.length; i++){
      observer(target[i])
    }
    return
  }
  // 從新定義key
  for(let key in target){
    defineReactive(target, key, target[key])
  }
}

let obj = {hobby:[{name:'zhuanzhuan'}]}
observer(obj)
// 使用['push','pop','shift','unshift'] 方法,更改數組會觸發視圖更新
obj.hobby.push('轉轉')
// 更改數組中的對象也會觸發視圖更新
obj.hobby[0].name = 'new-name'
console.log(obj.hobby)

複製代碼
輸出:
update view
update view
[ { name: [Getter/Setter] }, '轉轉' ]
複製代碼

Object.defineProperty缺點:

  • 沒法監聽數組的變化
  • 須要深度遍歷,浪費內存

vue-next 預備知識

不管是閱讀這篇文章,仍是閱讀 vue-next 響應式模塊的源碼,首先有兩個知識點是必備的:

  • Proxy:對象用於定義基本操做的自定義行爲(如屬性查找,賦值,枚舉,函數調用等)。ES6 中新的代理內建工具類。
  • Reflect:是一個內置的對象,它提供攔截 JavaScript 操做的方法。這些方法與proxy handlers的方法相同。Reflect不是一個函數對象,所以它是不可構造的。ES6 中新的反射工具類

Proxy

let data = [1,2,3]
let p = new Proxy(data, {
  get(target, key) {
    console.log('獲取值:', key)
    return target[key]
  },
  set(target, key, value) {
    console.log('修改值:', key, value)
    target[key] = value
    return true
  }
})

p.push(4)
複製代碼
輸出:
獲取值: push
獲取值: length
修改值: 3 4
修改值: length 4
複製代碼

defineproperty優秀的 就是數組和對象均可以直接觸發gettersetter, 可是數組會觸發兩次,由於獲取push和修改length的時候也會觸發

Proxy 取代 deineProperty 除了性能更高之外,還有如下缺陷,也是爲啥會有set,delete的緣由 :

  1. 屬性的新加或者刪除也沒法監聽;
  2. 數組元素的增長和刪除也沒法監聽

Reflect

let data = [1,2,3]
let p = new Proxy(data, {
  get(target, key) {
    console.log('獲取值:', key)
    return Reflect.get(target,key)
  },
  set(target, key, value) {
    console.log('修改值:', key, value)
    return Reflect.set(target,key, value)
  }
})

p.push(4)
複製代碼
輸出:
獲取值: push
獲取值: length
修改值: 3 4
修改值: length 4
複製代碼

屢次觸發和深層嵌套問題

let data = {name:{ title:'zhuanzhuan'}}
let p = new Proxy(data, {
  get(target, key) {
    console.log('獲取值:', key)
    return Reflect.get(target,key)
  },
  set(target, key, value) {
    console.log('修改值:', key, value)
    return Reflect.set(target,key, value)
  }
})

p.name.title = 'xx'
複製代碼
輸出:
獲取值: name
複製代碼

以後會帶你看下vue-next是怎麼解決的。

初始化項目

依賴 項目 vue.global.js【推薦】

  1. clone 項目

    $ git clone https://github.com/vuejs/vue-next.git
    複製代碼
  2. 編輯文件

    $ npm run dev
    複製代碼
  3. 拷貝文件

    運行上面命令後,就會生成 [項目根路徑]/packages/vue/dist/vue.global.js 文件

依賴 @vue/composition-api

  1. 安裝 vue-cli

    $ npm install -g @vue/cli
    # OR
    $ yarn global add @vue/cli
    複製代碼
  2. 建立項目

    $ vue create my-project
    # OR
    $ vue ui
    複製代碼
  3. 在項目中安裝 composition-api 體驗 vue-next 新特性

    $ npm install @vue/composition-api --save
    # OR
    $ yarn add @vue/composition-api
    複製代碼
  4. 在使用任何 @vue/composition-api 提供的能力前,必須先經過 Vue.use() 進行安裝

    import Vue from 'vue'
    import VueCompositionApi from '@vue/composition-api'
    
    Vue.use(VueCompositionApi)
    複製代碼

    安裝插件後,您就可使用新的 Composition API 來開發組件了。

vue-next 嚐鮮

直接拷貝下面代碼,去運行看效果吧。推薦使用高版本的chrome瀏覽器,記得打開F12調試工具哦!

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="https://s1.zhuanstatic.com/common/js/vue-next-3.0.0-alpha.0.js"></script>
</head>
<body>
<div id='app'></div>
</body>
<script> const { createApp, reactive, computed, effect } = Vue; const RootComponent = { template: ` <button @click="increment"> {{ state.name }}今年{{state.age}}歲了,乘以2是{{state.double}} </button> `, setup() { const state = reactive({ name: '轉轉', age: 3, double: computed(() => state.age * 2) }) effect(() => { console.log(`effect 觸發了! - ${state.name}今年${state.age}歲了,乘以2是${state.double}`) }) function increment() { state.age++ } return { state, increment } } } createApp().mount(RootComponent, '#app') </script>
</html>
複製代碼

這個reactive和react-hooks愈來愈像了, 你們能夠去Composition API RFC這裏看細節。

  1. template和以前同樣,一樣vue-next也支持手寫render的寫法,templaterender同時存在的狀況,優先render

  2. setup選項是新增的主要變更,顧名思義,setup函數會在組件掛載前(beforeCreatecreated生命週期之間)運行一次,相似組件初始化的做用,setup須要返回一個對象或者函數。返回對象會被賦值給組件實例的renderContext,在組件的模板做用域能夠被訪問到,相似data的返回值。返回函數會被當作是組件的render。具體能夠細看文檔。

  3. reactive的做用是將對象包裝成響應式對象,經過Proxy代理後的對象。

  4. 上面的計數器的例子,在組件的setup函數中,建立了一個響應式對象state包含一個age屬性。而後建立了一個increment遞增的函數,最後將stateincrement返回給做用域,這樣template裏的button按鈕就能訪問到increment函數綁定到點擊的回調,age。咱們點擊按鈕,按鈕上的數值就能跟着遞增。

參考


相信你們已經對 vue-next(Vue 3.0) 有了初步認識,而且已經成功運行嚐鮮代碼了吧。

下一章vue-next(Vue 3.0)之 小試牛刀繼續帶你掌握 vue-next 函數式的API。

相關文章
相關標籤/搜索