响应式系统原理 vue3 api

响应式系统原理 vue3 api

核心

readonly

让一个响应式数据变成只读的。深只读深层次的也不可读。

readonly函数只能将一个对象转换为只读对象,但是不能将一个数组或者map等其他类型的数据结构转换为只读对象,如果需要将这些数据结构转换为制度对象,可以使用readonly函数和deepReadonly函数的组合、

javascript 复制代码
import { readonly, deepReadonly } from 'vue'
const state = readonly({
    items: deepReadonly([
        items: deepReadonly([
            {id: 1, name: 'item1'},
            {id: 2, name: 'item2'},
            {id: 3, name: 'item3'}
        ])
    ])
})
javascript 复制代码
<template>
  <div>
    <h1>姓名:{{ name }}</h1>
    <h2>年龄:{{ age }}</h2>
    <h3>喜欢的水果:{{ likeFood.fruits.apple }}</h3>
    <button @click="name += '~'">修改姓名</button>
    <button @click="age++">修改年龄</button>
    <button @click="likeFood.fruits.apple += '!'">修改水果</button>
  </div>
</template>
 
<script>
import { reactive, toRefs, readonly } from "vue";
export default {
  name: "App",
  setup() {
    // 定义了一段数据
    let person = reactive({
      name: "张三",
      age: 18,
      likeFood: {
        fruits: {
          apple: "苹果",
        },
      },
    });
    // 使用了readonly将对象变为只读
    let p = readonly(person)
    // 将数据返回出去
    return {
      ...toRefs(p),
    };
  },
};
</script>

属性是只读的,所有数据不可以被修改

watchEffect

watchEffect是要给侦听器,是一个副作用函数,会监听引用数据类型的所有属性。不需要监听某个属性,一旦运行就立即监听,组件卸载的时候会停止监听。使用的时候不需要指定具体监听的谁,回调函数内直接使用就行,只能访问当前最新的值,访问不到之前修改的值

javascript 复制代码
<template>
  <div>
    <input type="text" v-model="obj.name"> 
    <button @click="stopWatchEffect">停止监听</button>
  </div>
</template>

<script>
import { reactive, watchEffect } from 'vue';
export default {
  setup(){
    let obj = reactive({
      name:'zs'
    });
    const stop = watchEffect(() => {
      console.log('name:',obj.name)
    })
    const stopWatchEffect = () => {
      console.log('停止监听')
      stop();
    }

    return {
      obj,
      stopWatchEffect,
    }
  }
}
</script>

侦听副作用传入的函数可以接受一个onInvalidate函数做入参,用来注册清理失效的回调。当下面的情况失效的时候,这个失效回调会被触发。

  • 副作用即将被重新执行
  • 侦听器被停止,如果在setup或生命周期钩子函数中使用了watchEffect,则在组件卸载的时候
javascript 复制代码
<template>
    <div>
        <div id="value">{{count}}</div>
        <div @click="countAdd">增加</div>
    </div>
</template>
<script>
import {ref, watchEffect} from 'vue'
export default {
    setup() {
        let count = ref(0)
        const countAdd = () => {
            count.value++
        }
        watchEffect(() => {
            console.log(count.value)
            console.log(document.querySelector("#value")&& document.querySelector("#value").innerText)
        })
        return {
            count,
            countAdd
        }
    }
}
</script>

点击之前的输出为0 null。默认执行监听器,然后更行dom,此时dom还没有生成,所以为null

javascript 复制代码
<template>
    <div>
        <div id="value">{{count}}</div>
        <div @click="countAdd">增加</div>
    </div>
</template>
<script>
import {ref, watchEffect} from 'vue'
export default {
    setup() {
        let count = ref(0)
        const countAdd = () => {
            count.value++
        }
        watchEffect(() => {
            console.log(count.value)
            console.log(document.querySelector("#value")&& document.querySelector("#value").innerText)
        }, 
        {
            flush: 'post'
        })
        return {
            count,
            countAdd
        }
    }
}

操作更新之后的dom,就需要配置flush:'post'

  • onTrack 将在响应式property或ref作为依赖项被追踪的时候调用
  • onTrigger将在依赖项变更导致副作用被触发的时候调用
javascript 复制代码
watchEffect(
    () => {
        // 副作用
    },
    {
        onTrigger(e) {
          // 依赖变更
        }    
    }
)

watchPostEffect

watchEffect()使用post选项的别名

watchSyncEffect

watchEffect()使用sync选项的别名。在组件更新或者更新之前,立即调用它。同步侦听器不会进行批处理,每当响应式数据发生变化的时候触发。可以用来监听简单的布尔值,避免在可能多次同步修改数据源上使用。

进阶

shallowRef

ref的浅层作用形式,就是浅层响应式。

  • ref创建的数据,如果有深层,就会被reactive处理称为深层响应式
  • shallowRef创建数据,只有顶层的value属性具有响应式,深层的数据不具有响应式
javascript 复制代码
<template>
  <h1>{{ student.name }}</h1>
  <button @click="changeName">更改姓名</button>
</template>
<script setup>
import { shallowRef } from "vue";
const student = shallowRef({
  name: "Tsz",
  age: 20,
  comments: ["好文", "继续努力"]
});
function changeName() {
  student.value.name = "Alice";
  student.value.comments.push("我喜欢它");
}
</script>

这里的数据不会发生改变。

triggerRef

让shallowRef深层属性发生变化之后强制触发更行

javascript 复制代码
 <template>
    <div>
        {{ shallowObj.a }}
        <button @click="addCount"> +1</button>
    </div>
</template>
<script lang='ts' setup>
import { shallowRef, triggerRef } from "vue"
const shallowObj = shallowRef({
    a: 1
})
const addCount = () => {
    shallowObj.value.a++
    //加入triggerRef强制触发更改
    triggerRef(shallowObj)
}
</script>

customRef

用途

  • 实现防抖节流
  • 自定义计算属性
  • 优化性能
javascript 复制代码
const count = customRef((track, trigger) => {
    let num = 0
    return {
        get: () => num,
        set: (val) => {
            num = val
            trigger()
        }
    }
})
javascript 复制代码
<template>
    <p>Counter: {{counter}}</p>
    <button @click="update">Update</button>
</template>
<script setup>
import {customRef} from 'vue'
function useLocalStorage(key string, initialValue: any) {
    const value = customRef((track, trigger) => {
        return {
            get() {
                track()
                return localStorage.getItem(key) ?? initialValue
            },
            set(v) {
                localStorage.setItem(key, v)
                trigger() 
            }
        }
    })
    return value
}
const counter = useLocalStorage("counter", 0)
const update = () => {
    counter.value++
}
</script>
javascript 复制代码
<script setup>
import { watch, customRef } from "vue"
function useDebouncedRef(delay = 500) {
  return customRef((track, trigger)=>{
    let timer
    let temp
    return {
      get(){
        track()
        return temp
      },
      set(v){
        timer && clearInterval(timer)
        timer = setInterval(()=>{
          temp = v
          trigger()
        }, delay)
      }
    }
  })
}
const text = useDebouncedRef()
watch(text, (value) => {
  console.log(value)
})
<template>
  <input v-model="text" />
</template>

shallowReactive

shallowReactive创建浅层响应式数据,深层对象就是原有的不同对象,不具有响应性。

  • shallowReactive()函数创建的数据是非深度监听,只会包装包装第一个对象,也就意味着深度的ref不会被自动解包。
javascript 复制代码
<template>
  <div>
    <h3>shallowReactive</h3>
    <div>{{ user }}</div>
    <div>{{ user2 }}</div>
    <button @click="change">修改数据源</button>
  </div>
</template>

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

export default defineComponent({
  setup() {
    const count = ref(10)
    const user = reactive({ name: '张三', age: 18, count })
    const count2 = ref(20)
    const user2 = shallowReactive({ name: '张三', age: 18, count: count2 })

    // 修改数据
    const change = () => {
      // 1. reactive 操作深层ref 数据时,自动解包
      // 此时修改user.count 数据时不用添加.value
      // user.count = 20  // 修改 reactive
      // 2. shallowReactive 操作深层ref 数据时,不会自动解包
      // 此时修改user2.count 数据时必须使用.value
      user2.count.value = 40  // 修改 shallowReactive
    }
    return { user, user2, change }
  }
})
</script>

shallowReadonly

让一个响应式数据变成只读的。浅只读。只有浅层次的数据是可读的,深层次的是可以修改的

javascript 复制代码
<template>
  <div>
    <h1>姓名:{{ name }}</h1>
    <h2>年龄:{{ age }}</h2>
    <h3>喜欢的水果:{{ likeFood.fruits.apple }}</h3>
    <button @click="name += '~'">修改姓名</button>
    <button @click="age++">修改年龄</button>
    <button @click="likeFood.fruits.apple += '!'">修改水果</button>
  </div>
</template>
 
<script>
import { reactive, toRefs, readonly } from "vue";
export default {
  name: "App",
  setup() {
    // 定义了一段数据
    let person = reactive({
      name: "张三",
      age: 18,
      likeFood: {
        fruits: {
          apple: "苹果",
        },
      },
    });
    // 使用了shallowReadonly将对象变为只读
    let p = shallowReadonly(person)
    // 将数据返回出去
    return {
      ...toRefs(p),
    };
  },
};

张三,18不可以修改,应为是浅层的,苹果可以更改

shallowReadonly处理深层ref不会自动解包

javascript 复制代码
<template>
  <div>
    <h3>shallowReadonly</h3>
    <div>{{ user }}</div>
    <div>{{ user2 }}</div>
  </div>
</template>

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

export default defineComponent({
  setup() {
    const count = ref(10)
    const user = readonly({ name: '张三', age: 18, count })
    const count2 = ref(20)
    const user2 = shallowReadonly({ name: '张三', age: 18, count: count2 })

    // readonly 处理深层数据为ref 数据时, 会自动解包,不用添加.value
    console.log('user.count', user.count)

    // shallowReadonly 获取深层ref 数据时必须添加.value, 因为不会自动解包
    console.log('user2.count', user2.count.value)

   
    return { user, user2 }
  }
})
</script>

toRaw

使用reactive,readonly,shallowReactive,shallowReadonly代理的对象,使用toRawapi方法后,将会返回一个新的原始对象。

markRaw

将一个对象标记为不可代理的对象,无法标记已经被代理的对象,如果标记一个已经被代理的对象是无法生效称为一个不可代理的对象。

effectScope

javascript 复制代码
<template>
  <div>
    <h3>shallowReactive</h3>
    <div>{{ count }}</div>
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, effectScope, markRaw, reactive, ref, shallowReactive, toRaw, watch, watchEffect } from 'vue'

export default defineComponent({
  setup() {
    // 创建ref 数据
    const count = ref(10)

    // 创建副作用作用域
    const scope = effectScope()
    // 控制台输出 effect 作用域
    console.log("scope", scope);
    
    // 收集运行的副作用
    scope.run(() => {
      // 计算属性副作用
      const computedCount = computed(() => count.value * 2
      )

      // watch 侦听副作用
      watch(
        count,
        () => {
          console.log('computedCount', computedCount.value)
          console.log('watch count', count.value)
        }
      )

      // watchEffect 副作用
      watchEffect(() => {
        console.log('watchEffect count', count.value)
      })

    })


    console.log('scope', scope) 
    // 2秒以后关闭所有的副作用
    setTimeout(() => {
      scope.stop()
    }, 2000)
    
    // 修改数据
    const change = () => {
      count.value++

    }
    return { count, change }
  }
})
</script>

getCurrentScope

如果有的话,返回当前活跃的 effect 作用域。

onScopeDispose

在当前活跃的 effect 作用域上注册一个处理回调函数。当相关的 effect 作用域停止时会调用这个回调函数

javascript 复制代码
// 获取当前侦听实例
    const allScope = getCurrentScope()
    // 执行 allScope.stop()时会触发 onScopeDispose 事件
    // 当前页面或组件注销时会触发 onScopeDispose 事件
    onScopeDispose(() => {
        console.log('已停止所有侦听');
        // to do...
    })
  
    // 5秒后停止所有侦听,此时会触发 onScopeDispose 事件
    setTimeout(() => {
        allScope.stop()
    }, 5000)

工具

unref

语法糖

javascript 复制代码
val = isRef(val) ? val.value: val

可以用来对响应式对象解除响应式引用

javascript 复制代码
<template>
    <div>
        {{unRefAsCount}}
        {{count}}
        <button @click="addCount"> +1 </button>
    </div>
</template>
<script lang="ts" setup>
import {unref, ref} from 'vue'
const count = ref(1)
let unRefAsCount = unref(count)
const addCount = () => {
    count.value++
}
// unRefAsCount
</script>

toRef

toRef 可以根据一个响应式对象中的一个属性,创建一个响应式的 ref。同时这个 ref 和原对象中的属性保持同步,改变原对象属性的值这个 ref 会跟着改变,反之改变这个 ref 的值原对象属性值也会改变

javascript 复制代码
<template>
    <div>
        {{ count.a }}
        {{ a }}
        <button @click="addCount">+1</button>
    </div>
</template>
<script lang='ts' setup>
import { ref, toRef } from "vue"
const count = ref({
    a: 1,
    b: 2
})
const a = toRef(count.value, 'a')
const addCount = () => {
    a.value++
}
</script>

toValue

用来规范化一个可以是值、ref 或 getter 的参数

javascript 复制代码
toValue(1) //       --> 1
toValue(ref(1)) //  --> 1
toValue(() => 1) // --> 1

toRefs

toRefs 它可以将一个响应式对象转成普通对象,而这个普通对象的每个属性都是响应式的 ref

javascript 复制代码
<template>
    <div>
        {{ count.a }}
        {{ countAsRefs.a }}
        <button @click="addCount">+1</button>
    </div>
</template>
<script lang='ts' setup>
import { reactive, toRefs } from "vue"
const count = reactive({
    a: 1,
    b: 2
})
const countAsRefs = toRefs(count)
const addCount = () => {
    countAsRefs.a.value++
}
</script>
相关推荐
胡志辉的博客40 分钟前
深入浅出理解浏览器事件循环:从一道输出题讲到 Chrome 源码
前端·javascript·chrome·chromium·event loop
代码不加糖1 小时前
js中不会冒泡的事件有哪些?
前端·javascript·vue.js
懂懂tty1 小时前
Vue2与Vue3之间API差异
前端·javascript·vue.js
老毛肚2 小时前
软件测试期末考试
vue.js
小二·2 小时前
Next.js 15 全栈开发实战
开发语言·javascript·ecmascript
杨若瑜3 小时前
本地开发环境慢?localhost的锅!
vue.js
Rain5093 小时前
2.1 Nest.js 项目初始化与模块化架构
开发语言·前端·javascript·后端·架构·数据分析·node.js
拾年2754 小时前
从零手写 Ajax:用原生 XHR 搭建前后端交互全流程
前端·javascript·ajax
拉勾科研工作室4 小时前
区块链工程毕业论文题目【249个】
开发语言·javascript
小林ixn4 小时前
你以为你懂 + 号?看完这篇 Bun + TS 实战,才发现以前全写错了
前端·javascript·typescript