Vue 3.0初步使用和原理

調試環境搭建

  1. 遷出Vue3源碼: git clone github.com/vuejs/vue-n…
  2. 安裝依賴: yarn --ignore-scripts
  3. 生成sourcemap文件,package.json
"dev": "node scripts/dev.js --sourcemap"
複製代碼
  1. 編譯: yarn dev,生成結果在:packages\vue\dist\vue.global.js

源碼結構

源碼位置是在package文件件內,實際上源碼主要分爲兩部分,編譯器和運行時環境。

編譯器

  1. compiler-core 核心編譯邏輯
  2. compiler-dom 針對瀏覽器平臺編譯邏輯
  3. compiler-sfc 針對單文件組件編譯邏輯
  4. compiler-ssr 針對服務端渲染編譯邏輯

運行時環境

  1. runtime-core 運行時核心
  2. runtime-dom 運行時針對瀏覽器的邏輯
  3. runtime-test 瀏覽器外完成測試環境仿真
  4. reactivity 響應式邏輯
  5. template-explorer 模板瀏覽器
  6. vue 代碼入口,整合編譯器和運行時
  7. server-renderer 服務器端渲染
  8. share 公用方法

初步使用

一. 建立方式變化,之前以$引入的全局方法,變成實例方法

<div id="app">
  <h1>{{message}}</h1>
  <comp></comp>
</div>

<script src="../dist/vue.global.js"></script>
<script>
  // 建立實例方式變化了
  const {createApp} = Vue
  const app = createApp({
    data() {
      return {
        'message': 'hello, vue3'
      }
    },
  })
  // 之前全局方法,變成實例方法
  app.component('comp', {
    template: '<div>comp</div>'
  })
  app.mount('#app')
</script>
複製代碼

Composition API

Composition API字面意思是組合API,它是爲了實現基於函數的邏輯複用機制而產生的。html

一. setup方法基礎用法,主要依賴reactive作數據響應化處理

  1. 能夠徹底摒棄掉this,
  2. 對ts更友好,
  3. 更好的分模塊,寫代碼的時候沒必要反覆橫跳
<div id="app">
  <h1>{{message}}</h1>
  <p @click="add">{{count}}</p>
  <p>{{doubleCount}}</p>
  <p>{{num}}</p>
</div>

<script src="../dist/vue.global.js"></script>
<script>
  // 引入使用的函數方法
  const {createApp, reactive, computed, ref, toRefs} = Vue
  const app = createApp({
    // setup在beforeCreated以前執行
    setup() {
      // reactivity api
      // message相關
      const data = reactive({
        message: 'hello,vue3',
      })

      setTimeout(() => {
        data.message = 'vue3,hello'
      }, 1000);

      // count相關
      const counter = reactive({
        count: 0,
        doubleCount: computed(() => counter.count * 2)
      })

      function add() {
        counter.count++
      }

      // 單值響應式, ref()返回Ref對象,若是要修改它的值,訪問value屬性
      const num = ref(0)
      setInterval(() => {
        num.value++
      }, 1000);

      return {...toRefs(data), add, ...toRefs(counter), num}
    }
  })
  app.mount('#app')
</script>
複製代碼

二. 邏輯組合

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>composition api</title>
  <script src="../dist/vue.global.js"></script>
</head>

<body>
<div>
  <h1>邏輯組合</h1>
  <div id="app"></div>
</div>

<script>
  const {createApp, reactive, onMounted, onUnmounted, toRefs} = Vue;

  // 鼠標位置偵聽
  function useMouse() {
    // 數據響應化
    const state = reactive({x: 0, y: 0})
    const update = e => {
      state.x = e.pageX
      state.y = e.pageY
    }
    onMounted(() => {
      window.addEventListener('mousemove', update)
    })
    onUnmounted(() => {
      window.removeEventListener('mousemove', update)
    })
    // 轉換全部key爲響應式數據
    return toRefs(state)
  }

  // 時間監測
  function useTime() {
    const state = reactive({time: new Date()})
    onMounted(() => {
      setInterval(() => {
        state.time = new Date()
      }, 1000)
    })
    return toRefs(state)
  }

  // 邏輯組合
  const MyComp = {
    template: `
        <div>x: {{ x }} y: {{ y }}</div>
        <p>time: {{time}}</p>
      `,
    setup() {
      // 使用鼠標邏輯
      const {x, y} = useMouse()
      // 使用時間邏輯
      const {time} = useTime()
      // 組合返回使用
      return {x, y, time}
    }
  }
  createApp(MyComp).mount('#app')
</script>
</body>

</html>

複製代碼

以上可見vue

  1. 變量來源清晰
  2. 不會與data, props命名衝突
  3. 提升了複用性和可維護性

Vue3響應式原理

Vue2響應式原理回顧

// 1.對象響應化:遍歷每一個key,定義getter、setter
// 2.數組響應化:覆蓋數組原型方法,額外增長通知邏輯
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto)
['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(
  method => {
    arrayProto[method] = function() {
      originalProto[method].apply(this, arguments)
      notifyUpdate()
    }
  })
function observe(obj) {
  if (typeof obj !== 'object' || obj == null) {
    return
  }
  // 增長數組類型判斷,如果數組則覆蓋其原型
  if (Array.isArray(obj)) {
    Object.setPrototypeOf(obj, arrayProto)
  } else {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      defineReactive(obj, key, obj[key])
    }
  }
}
function defineReactive(obj, key, val) {
  observe(val) // 解決嵌套對象問題
  Object.defineProperty(obj, key, {
    get() {
      return val
    },
    set(newVal) {
      if (newVal !== val) {
        observe(newVal) // 新值是對象的狀況 val = newVal
        notifyUpdate()
      }
    }
  })
}
function notifyUpdate() {
  console.log('頁面更新!')
}
複製代碼

vue2響應式弊端:node

  1. 響應化過程須要遞歸遍歷,消耗較大
  2. 新加或刪除屬性沒法監聽
  3. 數組響應化須要額外實現 Map、Set、Class等沒法響應式
  4. 修改語法有限制

Vue3響應式原理

  1. 原理使用ES6的Proxy特性來解決數據劫持和數據響應
// Proxy
// 提取幫助方法
const isObject = v => v !== null && typeof v === 'object'

function reactive(obj) {
  // 判斷是否對象
  if (!isObject(obj)) {
    return obj
  }

  return new Proxy(obj, {
    get(target, key, receiver) {
      const ret = Reflect.get(target, key, receiver)
      console.log('獲取', key);
      track(target, key)
      // 若是是對象須要遞歸
      return isObject(ret) ? reactive(ret) : ret
    },
    set(target, key, value, receiver) {
      const ret = Reflect.set(target, key, value, receiver)
      console.log('設置', key);
      trigger(target, key)
      return ret
    },
    deleteProperty(target, key) {
      const ret = Reflect.deleteProperty(target, key)
      console.log('刪除', key);
      trigger(target, key)
      return ret
    },

  })
}
複製代碼
  1. 收集依賴
// 大概結構以下所示
// target | depsMap 
//    obj | key  |  Dep
//          k1   |  effect1,effect2...
//          k2   |  effect3,effect4...
// {target: {key: [effect1,...]}}
複製代碼
// 保存當前活動響應函數做爲getter和effect之間橋樑
const effectStack = []

// effect任務:執行fn並將其入棧
function effect(fn) {
  const rxEffect = function () {
    // 1.捕獲異常
    // 2.fn入棧出棧
    // 3.執行fn
    // 4.執行結束,出棧
    try {
      effectStack.push(rxEffect)
      return fn()
    } finally {
      effectStack.pop()
    }
  }

  // 默認執行一次響應函數
  rxEffect()
  // 返回響應函數
  return rxEffect

}

// 響應函數觸發某個響應式數據,開始作依賴收集(映射過程)
// {target: {key: [fn1,fn2]}}
const targetMap = new WeakMap()

function track(target, key) {
  // 從棧中取出響應函數
  const effect = effectStack[effectStack.length - 1]
  if (effect) {
    // 獲取target對應依賴表
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      // 首次訪問不存在,建立一個
      depsMap = new Map()
      targetMap.set(target, depsMap)
    }
    // 獲取key對應的響應函數集
    let deps = depsMap.get(key)
    if (!deps) {
      deps = new Set()
      depsMap.set(key, deps)
    }
    // 將響應函數加入到對應集合
    deps.add(effect)
  }
}

// 觸發target.key對應響應函數,根據映射關係執行對應cb
function trigger(target, key) {
  // 獲取依賴表
  const depsMap = targetMap.get(target)
  if (depsMap) {
    // 獲取響應函數集合
    const deps = depsMap.get(key)
    if (deps) {
      // 執行全部響應函數
      deps.forEach(effect => effect())
    }
  }
}
複製代碼
  1. 測試
// state就是Proxy實例
const state = reactive({foo: 'foo', bar: {a: 1}, arr: [1, 2, 3]})

// 測試代碼
// state.foo
// state.foo = 'foooooo'
// // 設置不存在屬性
// state.bar = 'bar'

// state.bar.a = 10

// state.arr.push(4)
// state.arr.pop()

effect(() => {
  console.log('effect', state.foo);

})

state.foo = 'fooooooooo'
複製代碼
相關文章
相關標籤/搜索