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'
复制代码
相关文章
相关标签/搜索