vue3源码剖析 02

学习目标


  • composition-api体验
  • Vue3响应式源码学习
  • 响应式原理:Vue2 vs Vue
  • 造轮子之旅

composition-api


文档

https://vue-composition-api-rfc.netlify.com

初体验

  1. <div id="app">
  2. <h1>composition-api</h1>
  3. <p @click="add">{{state.counter}}</p>
  4. <p>{{state.doubleCounter}}</p>
  5. </div>
  6. <script src="../dist/vue.global.js"></script>
  7. <script>
  8. const { createApp, reactive, computed } = Vue
  9. const app = createApp({
  10. setup () {
  11. const state = reactive({
  12. counter: 0,
  13. doubleCouter: computed(() => counter * 2)
  14. })
  15. const add = () => {
  16. data.counter++
  17. }
  18. return { state, add }
  19. },
  20. }).mount('#app')
  21. </script>

更好的逻辑复用和代码组织

  1. <meta charset="UTF-8">
  2. <script src="../dist/vue.global.js"></script>
  3. <div id="app">
  4. <h1>logic reuse</h1>
  5. </div>
  6. <script>
  7. const { createApp, reactive, onMounted, onUnmounted, toRefs } = Vue;
  8. // 鼠标位置侦听
  9. function useMouse () {
  10. // 数据响应化
  11. const state = reactive({ x: 0, y: 0 })
  12. const update = e => {
  13. state.x = e.pageX
  14. state.y = e.pageY
  15. }
  16. onMounted(() => {
  17. window.addEventListener('mousemove', update)
  18. })
  19. onUnmounted(() => {
  20. window.removeEventListener('mousemove', update)
  21. })
  22. // 转换所有key为响应式数据
  23. return toRefs(state)
  24. }
  25. // 事件监测
  26. function useTime () {
  27. const state = reactive({ time: new Date() })
  28. onMounted(() => {
  29. setInterval(() => {
  30. state.time = new Date()
  31. }, 1000)
  32. })
  33. return toRefs(state)
  34. }
  35. // 逻辑组合
  36. const MyComp = {
  37. template: `
  38. <div>x: {{ x }} y: {{ y }}</div>
  39. <p>time: {{time}}</p>
  40. `,
  41. setup () {
  42. // 使用鼠标逻辑
  43. const { x, y } = useMouse()
  44. // 使用时间逻辑
  45. const { time } = useTime()
  46. // 返回使用
  47. return { x, y, time }
  48. }
  49. }
  50. createApp(MyComp).mount('#app')
  51. </script>

对比mixins,好处显而易⻅:

  • x,y,time来源清晰
  • 不会与data、props等命名冲突
  • 更好的维护性

image.png

更好的类型推断

Vue最初选项API中存在大量this上下文,对TypeScript类型推断很不友好。在composition-api中仅利用纯变量和函数,规避了对this的使用,自然的拥有良好的类型推断能力。

Vue3中响应式源码学习


测试代码

  1. <div id="app">
  2. {{foo}}
  3. </div>
  4. <script src="../dist/vue.global.js"></script>
  5. <script>
  6. const { createApp } = Vue
  7. createApp({
  8. data () {
  9. return {
  10. foo: 'foo'
  11. }
  12. }
  13. }).mount('#app')
  14. </script>

整体流程

applyOptions中对data选项做 响应式处理使用的是reactive函数
image.png

setupRenderEffect函数中 使用effect函数做依赖收集
image.png

响应式原理:vue2 vs vue


数据变化可侦测,从而对使用数据的地方进行更新。

vue2的方式

Object.defineProperty()

  1. // 拦截每个key,从而可以侦测数据变化
  2. function defineReactive (obj, key, val) {
  3. Object.defineProperty(obj, key, {
  4. get () {
  5. return val
  6. },
  7. set (v) {
  8. val = v
  9. update()
  10. }
  11. })
  12. }
  13. function update () {
  14. console.log(obj.foo);
  15. }
  16. const obj = {}
  17. defineReactive(obj, 'foo', 'foo')
  18. obj.foo = 'foooooooo'

vue3的方式

Proxy

  1. // 代理整个对象,从而侦测数据变化
  2. function defineReactive (obj) {
  3. return new Proxy(obj, {
  4. get (target, key) {
  5. return target[key]
  6. },
  7. set (target, key, val) {
  8. target[key] = val
  9. update()
  10. }
  11. })
  12. }
  13. function update () {
  14. console.log(obj.foo);
  15. }
  16. const obj = {}
  17. const observed = defineReactive(obj)
  18. observed.foo = 'foooooooo'

Vue2 vs Vue3

vue2中需要递归遍历对象所有key,速度慢

  1. // 1.对象响应化:遍历每个key,定义getter、setter
  2. function observe (obj) {
  3. if (typeof obj !== 'object' || obj == null) {
  4. return
  5. }
  6. const keys = Object.keys(obj)
  7. for (let i = 0; i < keys.length; i++) {
  8. const key = keys[i]
  9. defineReactive(obj, key, obj[key])
  10. }
  11. }
  12. function defineReactive (obj, key, val) {
  13. observe(val)
  14. Object.defineProperty(obj, key, {
  15. get () {
  16. return val
  17. },
  18. set (newVal) {
  19. if (newVal !== val) {
  20. observe(newVal)
  21. val = newVal
  22. dep.notify()
  23. }
  24. }
  25. })
  26. }

数组响应式需要额外实现

  1. // 数组响应化:覆盖数组原型方法,额外增加通知逻辑
  2. const originalProto = Array.prototype
  3. const arrayProto = Object.create(originalProto);
  4. ['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(
  5. method => {
  6. arrayProto[method] = function () {
  7. originalProto[method].apply(this, arguments)
  8. dep.notify()
  9. }
  10. }
  11. )

新增或删除属性无法监听,需要使用特殊api

  1. Vue.set(obj, 'foo', 'bar')
  2. Vue.delete(obj, 'foo')

不支持Map、Set、Class等数据结构