1147 字
6 分钟
Vue重学计划(三)

这周因为学校的事太多,耽误了好久,花了一天把Vue3的响应式数据整理了一下。

感觉太迷茫了,现在就是在岔路口做决策,摇摆不定了。。。

不知道咋办,还是先学习吧

简单用一个Demo过一下基本的使用吧!

main.ts

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

App.vue

<template>
  <RefDemo />
  <hr>
  <hr>
  <ReactiveDemo />
  <hr>
  <hr>
  <ToDemo />
</template>

<script setup lang="ts">
import ReactiveDemo from './components/reactiveDemo.vue';
import RefDemo from './components/refDemo.vue'
import ToDemo from './components/toDemo.vue';
</script>'

Ref相关#

refDemo.vue

<style lang="css" scoped>
.alive {
  color: greenyellow;
}

.dead {
  color: red;
}
</style>

<template>
  <div>
    {{ Kevin }}
  </div>
  <div>
    {{ Elysia }}
  </div>
  <hr>
  <div>
    {{ Mobius }}
  </div>
  <hr>
  <button @click="Omega">Omega Befalling</button>
  <hr>
  <button @click="Alpha">Restarting new era</button>
  <hr>
  <button @click="toggle">Toggle Kevin</button>
</template>

<script setup lang="ts">
import { ref, customRef, shallowRef, Ref, triggerRef } from 'vue'

interface Hero {
  name: string
  power: string
  weapon: string
  rank: number
  state: string
}

const Kevin: Ref<Hero> = shallowRef({
  name: 'Kevin',
  power: 'Ice',
  weapon: 'God Fire',
  rank: 1,
  state: 'Alive'
})

const Elysia: Ref<Hero> = ref({
  name: 'Elysia',
  power: 'Crystal',
  weapon: 'Key of Humanity',
  rank: 2,
  state: 'Alive'
})

const Mobius: Ref<Hero> = ref({
  name: 'Mobius',
  power: 'Snake',
  weapon: 'Shadow of passed world',
  rank: 10,
  state: 'Alive'
})

function Omega(): void {
  Elysia.value.state = 'Dead'
  Mobius.value.state = 'Dead'
  console.log('我什么都做不到')
}

function Alpha(): void {
  Kevin.value.state = 'Missing'
  // 这里不要使用下面的句子,ref和shallowRef混用会导致浅层响应式失效
  // Mobius.value.state = 'Missing'
  console.log('人类,一定会战胜崩坏')
}

function toggle(): void {
  Kevin.value.state = Kevin.value.state === 'Alive' ? 'Missing' : 'Alive'
  triggerRef(Kevin)
  console.log('Kevin triggered')
}

// 下面演示一下自定义Ref(customRef)的用法
function MyRef<T>(value: T) {
  return customRef((tracker, trigger) => {
    return {
      get() {
        tracker()
        return value
      },
      set(newValue: T) {
        value = newValue
        trigger()
      }
    }
  })
}

const Kiana = MyRef({
  name: 'Kiana',
  power: 'x',
  weapon: 'y',
  state: 'Alive'
})
</script>

PS#

shallowRef和Ref不要混用,Ref在重新更新渲染的时候,会导致shallowRef的渲染也被更新,shallow属性就失效了

Reactive相关#

reactiveDemo.vue

<style lang="css" scoped></style>

<template>
    <div>
        <ul>
            <li v-for="item in list.arr">{{ item }}</li>
        </ul>
        <button @click.prevent="add">add</button>
        <hr>
        <button @click="show">Show Numbers</button>
    </div>
</template>

<script setup lang="ts">
import { ref, reactive, readonly } from 'vue'

let list = reactive<{
    arr: string[]
}>({
    arr: []
})

let numList = reactive<{
    element: number[]
    otherElement: string[]
}>({
    element: [1, 2, 3],
    otherElement: ['a', 'b', 'c']
})
const readonlyNumList = readonly(numList)

const add = () => {
    setTimeout(() => {
        let res = ['grape', 'mango', 'strawberry']
        list.arr = res
        console.log(list)
    }, 2000)
}

const show = () => {
    console.log(numList.element, readonlyNumList.element)
}
</script>

PS#

readonly只能用在reactive数据上

调试可以发现,这个readonly的机制和响应式类似,都是使用proxy来进行

toRef相关#

toDemo.vue

<template>
    <div>
        {{ furrymonster }}
    </div>
    <hr>
    <div>
        toRef:{{ fav }}
    </div>
    <hr>
    <div>
        <button @click="change">Change</button>
    </div>
</template>

<script setup lang="ts">
import { toRef, reactive, toRefs, toRaw } from 'vue';

const furrymonster = reactive({ name: 'FurryMonster', age: '20', fav: 'jerk off' })

const fav = toRef(furrymonster, 'fav')



const change = () => {
    furrymonster.fav = 'eat'
    console.log(furrymonster)
}
</script>

<style lang="css">
* {
    padding: 0;
    margin: 0;
}
</style>

PS#

toRef和toRaw都是基于reactive的,对于普通的对象,这两个函数不会发挥任何作用

从这里也不难发现,Vue3的ref基于reactive,reactive基于对普通数据(raw data)的proxy代理,它们之间是一个相互依赖的关系

响应式原理#

Vue2 使用的是 Object.defineProperty Vue3 使用的是 Proxy

reactive和effect的实现#

export const reactive = <T extends object>(target:T) => {
    return new Proxy(target,{
        get (target,key,receiver) {
          const res  = Reflect.get(target,key,receiver) as object
 
          return res
        },
        set (target,key,value,receiver) {
           const res = Reflect.set(target,key,value,receiver)
 
           return res
        }
    })
}

Vue3 的响应式原理依赖了 Proxy 这个核心 API,通过 Proxy 可以劫持对象的某些操作。

effect track trigger#

实现effect 副作用函数

let activeEffect;
export const effect = (fn:Function) => {
     const _effect = function () {
        activeEffect = _effect;
        fn()
     }
     _effect()
}

使用一个全局变量 active 收集当前副作用函数,并且初始化的时候调用一下

实现track

const targetMap = new WeakMap()
export const track = (target,key) =>{
   let depsMap = targetMap.get(target)
   if(!depsMap){
       depsMap = new Map()
       targetMap.set(target,depsMap)
   }
   let deps = depsMap.get(key)
   if(!deps){
      deps = new Set()
      depsMap.set(key,deps)
   }
 
   deps.add(activeEffect)
}

执行完成成后我们得到一个如下的数据结构

img

实现trigger

export const trigger = (target,key) => {
   const depsMap = targetMap.get(target)
   const deps = depsMap.get(key)
   deps.forEach(effect=>effect())
}

当我们进行赋值的时候会调用 set 然后 触发收集的副作用函数

import {track,trigger} from './effect'
 
export const reactive = <T extends object>(target:T) => {
    return new Proxy(target,{
        get (target,key,receiver) {
          const res  = Reflect.get(target,key,receiver) as object
 
          track(target,key)
 
          return res
        },
        set (target,key,value,receiver) {
           const res = Reflect.set(target,key,value,receiver)
 
           trigger(target,key)
 
           return res
        }
    })
}

给 reactive 添加这两个方法

<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
 
<body>
 
    <div id="app">
 
    </div>
 
    <script type="module">
        import { reactive } from './reactive.js'
        import { effect } from './effect.js'
        const user = reactive({
            name: "小满",
            age: 18
        })
        effect(() => {
            document.querySelector('#app').innerText = `${user.name} - ${user.age}`
        })
 
        setTimeout(()=>{
            user.name = '大满很吊'
            setTimeout(()=>{
                user.age = '23'
            },1000)
        },2000)
 
    </script>
</body>
 
</html>

递归实现reactive#

import { track, trigger } from './effect'
 
const isObject = (target) => target != null && typeof target == 'object'
 
export const reactive = <T extends object>(target: T) => {
    return new Proxy(target, {
        get(target, key, receiver) {
            const res = Reflect.get(target, key, receiver) as object
 
            track(target, key)
 
            if (isObject(res)) {
                return reactive(res)
            }
 
            return res
        },
        set(target, key, value, receiver) {
            const res = Reflect.set(target, key, value, receiver)
 
            trigger(target, key)
 
            return res
        }
    })
}
Vue重学计划(三)
https://monsterstation.netlify.app/posts/old/vue重学计划三/
作者
Furry Monster
发布于
2024-10-10
许可协议
CC BY-NC-SA 4.0