ホームページ  >  記事  >  ウェブフロントエンド  >  vue3 の ref と reactive の包括的なインベントリ

vue3 の ref と reactive の包括的なインベントリ

青灯夜游
青灯夜游転載
2023-03-02 19:40:511861ブラウズ

Vue3を使っていると「refやrectiveでレスポンシブオブジェクトが作れるけど、どう選べばいいの?」「レスポンシブオブジェクトを分解するとレスポンシブ性がなくなるのはなぜ?どう扱えばいいの??」このような疑問をお持ちの方はいらっしゃいますか? ” 今日はrefとreactiveについて徹底的に棚卸してみます。読むとまた違った気づきが得られると思います。一緒に学びましょう!

vue3 の ref と reactive の包括的なインベントリ

#reactive()

基本的な使い方

Vue3 で使用できます

reactive() 応答オブジェクトまたは配列を作成します:

import { reactive } from 'vue'

const state = reactive({ count: 0 })

この応答オブジェクトは実際には

Proxy であり、Vue はこの Proxy を使用します。プロパティにアクセスすると収集され、プロパティが変更されると副作用がトリガーされます。

コンポーネント テンプレートで応答状態を使用するには、

setup() 関数で応答状態を定義して返す必要があります。 [関連する推奨事項: vuejs ビデオ チュートリアル Web フロントエンド開発 ]

<script>
import { reactive } from &#39;vue&#39;

export default {  setup() {    const state = reactive({ count: 0 })    return {      state    }  }}
</script>

<template>
  <div>{{ state.count }}</div>
</template>

もちろん、

8e22d5627eed85f4f7555e8fe9295d57## を使用することもできます。 #,5101c0cdbdc49998c642c71f6b6410a8 のトップレベルのインポートと変数宣言は、テンプレートで直接使用できます。 <pre class="brush:js;toolbar:false;"><script setup> import { reactive } from &amp;#39;vue&amp;#39; const state = reactive({ count: 0 }) </script> <template> <div>{{ state.count }}</div> </template></pre>

レスポンシブ プロキシと元のオブジェクト

reactive()

は元のオブジェクトの Proxy を返しますが、それらは元のオブジェクトではありません等しい: <pre class="brush:js;toolbar:false;">const raw = {} const proxy = reactive(raw) console.log(proxy === raw) // false</pre>元のオブジェクトをテンプレートで使用することもできますが、元のオブジェクトを変更しても更新はトリガーされません。したがって、Vue のリアクティブ システムを使用するには、プロキシを使用する必要があります。

<script setup>
const state = { count: 0 }
function add() {
  state.count++
}
</script>

<template>
  <button @click="add">
    {{ state.count }} <!-- 当点击button时,始终显示为 0 -->
  </button>
</template>

プロキシへのアクセスの一貫性を確保するために、同じ元のオブジェクトで

reactive()

を呼び出すと、常に同じプロキシ オブジェクトが返されますが、既存のプロキシ オブジェクトで ## を呼び出すと # reactive() はそれ自体を返します:

const raw = {}
const proxy1 = reactive(raw)
const proxy2 = reactive(raw)

console.log(proxy1 === proxy2) // true

console.log(reactive(proxy1) === proxy1) // true
このルールはネストされたオブジェクトにも適用されます。深い応答性に依存しているため、応答オブジェクト内のネストされたオブジェクトは依然としてプロキシです:
const raw = {}
const proxy = reactive({ nested: raw })
const nested = reactive(raw)

console.log(proxy.nested === nested) // true

shallowReactive()

Vue では、ステータスはデフォルトで深い応答です。ただし、シナリオによっては、最上位レベルでのみ応答性を持たせるために shallow reactive オブジェクト

を作成する必要がある場合があり、この場合は

shallowReactive() を使用できます。

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 状态自身的属性是响应式的
state.foo++

// 下层嵌套对象不是响应式的,不会按期望工作
state.nested.bar++
注: 浅いリアクティブ オブジェクトは、コンポーネントのルートレベルの状態にのみ使用してください。非常に反応性の高いオブジェクトにネストすることは避けてください。オブジェクト内のプロパティの応答動作が一貫性がなく、ネスト後の理解とデバッグが困難になるためです。

reactive() の制限

reactive() は強力ですが、次の制限もあります。オブジェクト型 (オブジェクト、配列、Map

Set

などのコレクション型)、および
    string
  • number、およびboolean このようなプリミティブ型は無効です。 Vue のリアクティブ システムはプロパティ アクセスを通じて追跡されるため、リアクティブ オブジェクトを直接「置換」すると、元の参照へのリアクティブ接続が失われます。

    <script setup>
    import { reactive } from &#39;vue&#39;
    
    let state = reactive({ count: 0 })
    function change() {  // 非响应式替换
     state = reactive({ count: 1 })}
    </script>
    
    <template>
     <button @click="change">
       {{ state }} <!-- 当点击button时,始终显示为 { "count": 0 } -->
     </button>
    </template>

    リアクティブ オブジェクトのプロパティをローカル変数に割り当てたり構造解除した​​り、プロパティを関数に渡したりすると、応答性が失われます:
  • const state = reactive({ count: 0 })
    
    // n 是一个局部变量,和 state.count 失去响应性连接
    let n = state.count
    // 不会影响 state
    n++
    
    // count 也和 state.count 失去了响应性连接
    let { count } = state
    // 不会影响 state
    count++
    
    // 参数 count 同样和 state.count 失去了响应性连接
    function callSomeFunction(count) {
     // 不会影响 state
     count++
    }
    callSomeFunction(state.count)
  • In上記の制限を解決するために、
  • ref
  • がシーンに輝きます!

  • ref()

Vue は、任意の値型を使用してリアクティブな参照を作成できる ref()

メソッドを提供します。

基本的な使用法

ref()

受信パラメータを

value 属性を持つ ref オブジェクトにパックします。
import { ref } from &#39;vue&#39;

const count = ref(0)

console.log(count) // { value: 0 }

count.value++
console.log(count.value) // 1

応答性オブジェクトのプロパティと同様に、ref の

value プロパティも応答性があります。同時に、値がオブジェクト型の場合、Vue は自動的に reactive() を使用して値を処理します。

オブジェクトを含む ref はオブジェクト全体をリアクティブに置き換えることができます:

<script setup>
import { ref } from &#39;vue&#39;

let state = ref({ count: 0 })
function change() {
  // 这是响应式替换
  state.value = ref({ count: 1 })
}
</script>

<template>
  <button @click="change">
    {{ state }} <!-- 当点击button时,显示为 { "count": 1 } -->
  </button>
</template>
ref 一般オブジェクトからプロパティを分解するとき、またはプロパティを関数に渡すときに応答性が失われることはありません: 参照フロントエンドの高度なインタビューの質問に対する詳細な回答

const state = {
  count: ref(0)
}
// 解构之后,和 state.count 依然保持响应性连接
const { count } = state
// 会影响 state
count.value++

// 该函数接收一个 ref, 和传入的值保持响应性连接
function callSomeFunction(count) {
  // 会影响 state
  count.value++
}
callSomeFunction(state.count)

ref()

を使用すると、任意の値型を使用して ref オブジェクトを作成でき、それらのオブジェクトを応答的に渡すことができます。この機能は非常に重要であり、ロジックを

結合関数 に抽出するためによく使用されます。

// mouse.js
export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  // ...
  return { x, y }
}
<script setup>
import { useMouse } from &#39;./mouse.js&#39;
// 可以解构而不会失去响应性
const { x, y } = useMouse()
</script>

ref 的解包

所谓解包就是获取到 ref 对象上 value 属性的值。常用的两种方法就是 .valueunref()unref() 是 Vue 提供的方法,如果参数是 ref ,则返回 value 属性的值,否则返回参数本身。

ref 在模板中的解包

当 ref 在模板中作为顶层属性被访问时,它们会被自动解包,不需要使用 .value 。下面是之前的例子,使用 ref() 代替:

<script setup>
import { ref } from &#39;vue&#39;

const count = ref(0)
</script>

<template>
  <div>
    {{ count }} <!-- 无需 .value -->
  </div>
</template>

还有一种情况,如果文本插值({{ }})计算的最终值是 ref ,也会被自动解包。下面的非顶层属性会被正确渲染出来。

<script setup>
import { ref } from &#39;vue&#39;

const object = { foo: ref(1) }

</script>

<template>
  <div>
    {{ object.foo }} <!-- 无需 .value -->
  </div>
</template>

其他情况则不会被自动解包,如:object.foo 不是顶层属性,文本插值({{ }})计算的最终值也不是 ref:

const object = { foo: ref(1) }

下面的内容将不会像预期的那样工作:

<div>{{ object.foo + 1 }}</div>

渲染的结果会是 [object Object]1,因为 object.foo 是一个 ref 对象。我们可以通过将 foo 改成顶层属性来解决这个问题:

const object = { foo: ref(1) }
const { foo } = object
<div>{{ foo + 1 }}</div>

现在结果就可以正确地渲染出来了。

ref 在响应式对象中的解包

当一个 ref 被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样:

const count = ref(0)
const state = reactive({ count })

console.log(state.count) // 0

state.count = 1
console.log(state.count) // 1

只有当嵌套在一个深层响应式对象内时,才会发生解包。当 ref 作为 浅层响应式对象 的属性被访问时则不会解包:

const count = ref(0)
const state = shallowReactive({ count })

console.log(state.count) // { value: 0 } 而不是 0

如果将一个新的 ref 赋值给一个已经关联 ref 的属性,那么它会替换掉旧的 ref:

const count = ref(1)
const state = reactive({ count })

const otherCount = ref(2)
state.count = otherCount

console.log(state.count) // 2
// 此时 count 已经和 state.count 失去连接
console.log(count.value) // 1

ref 在数组和集合类型的解包

跟响应式对象不同,当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包。

const books = reactive([ref(&#39;Vue 3 Guide&#39;)])
// 这里需要 .value
console.log(books[0].value)

const map = reactive(new Map([[&#39;count&#39;, ref(0)]]))
// 这里需要 .value
console.log(map.get(&#39;count&#39;).value)

toRef()

toRef 是基于响应式对象上的一个属性,创建一个对应的 ref 的方法。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, &#39;foo&#39;)

// 更改源属性会更新该 ref
state.foo++
console.log(fooRef.value) // 2

// 更改该 ref 也会更新源属性
fooRef.value++
console.log(state.foo) // 3

toRef() 在你想把一个 prop 的 ref 传递给一个组合式函数时会很有用:

<script setup>
import { toRef } from &#39;vue&#39;

const props = defineProps(/* ... */)

// 将 `props.foo` 转换为 ref,然后传入一个组合式函数
useSomeFeature(toRef(props, &#39;foo&#39;))
</script>

toRef 与组件 props 结合使用时,关于禁止对 props 做出更改的限制依然有效。如果将新的值传递给 ref 等效于尝试直接更改 props,这是不允许的。在这种场景下,你可以考虑使用带有 getsetcomputed 替代。

注意:即使源属性当前不存在,toRef() 也会返回一个可用的 ref。这让它在处理可选 props 的时候非常有用,相比之下 toRefs 就不会为可选 props 创建对应的 refs 。下面我们就来了解一下 toRefs

toRefs()

toRefs() 是将一个响应式对象上的所有属性都转为 ref ,然后再将这些 ref 组合为一个普通对象的方法。这个普通对象的每个属性和源对象的属性保持同步。

const state = reactive({
  foo: 1,
  bar: 2
})

// 相当于
// const stateAsRefs = {
//   foo: toRef(state, &#39;foo&#39;),
//   bar: toRef(state, &#39;bar&#39;)
// }
const stateAsRefs = toRefs(state)

state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3

从组合式函数中返回响应式对象时,toRefs 相当有用。它可以使我们解构返回的对象时,不失去响应性:

// feature.js
export function useFeature() {
  const state = reactive({
    foo: 1,
    bar: 2
  })

  // ...
  // 返回时将属性都转为 ref
  return toRefs(state)
}
<script setup>
import { useFeature } from &#39;./feature.js&#39;
// 可以解构而不会失去响应性
const { foo, bar } = useFeature()
</script>

toRefs 只会为源对象上已存在的属性创建 ref。如果要为还不存在的属性创建 ref,就要用到上面提到的 toRef

以上就是 ref、reactive 的详细用法,不知道你有没有新的收获。接下来,我们来探讨一下响应式原理。

响应式原理

Vue2 的限制

大家都知道 Vue2 中的响应式是采⽤ Object.defineProperty() , 通过 getter / setter 进行属性的拦截。这种方式对旧版本浏览器的支持更加友好,但它有众多缺点:

  • 初始化时只会对已存在的对象属性进行响应式处理。也是说新增或删除属性,Vue 是监听不到的。必须使用特殊的 API 处理。

  • 数组是通过覆盖原型对象上的7个⽅法进行实现。如果通过下标去修改数据,Vue 同样是无法感知的。也要使用特殊的 API 处理。

  • 无法处理像 MapSet 这样的集合类型。

  • 带有响应式状态的逻辑不方便复用。

Vue3 的响应式系统

针对上述情况,Vue3 的响应式系统横空出世了!Vue3 使用了 Proxy 来创建响应式对象,仅将 getter / setter 用于 ref ,完美的解决了上述几条限制。下面的代码可以说明它们是如何工作的:

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)
    }
  })
}

function ref(value) {
  const refObject = {
    get value() {
      track(refObject, &#39;value&#39;)
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(refObject, &#39;value&#39;)
    }
  }
  return refObject
}

不难看出,当将一个响应性对象的属性解构为一个局部变量时,响应性就会“断开连接”。因为对局部变量的访问不会触发 get / set 代理捕获。

我们回到响应式原理。在 track() 内部,我们会检查当前是否有正在运行的副作用。如果有,就会查找到存储了所有追踪了该属性的订阅者的 Set,然后将当前这个副作用作为新订阅者添加到该 Set 中。

// activeEffect 会在一个副作用就要运行之前被设置
let activeEffect

function track(target, key) {
  if (activeEffect) {
    const effects = getSubscribersForProperty(target, key)
    effects.add(activeEffect)
  }
}

副作用订阅将被存储在一个全局的 WeakMapebe7c5b3d29ca4e6c2839ac79e883868>> 数据结构中。如果在第一次追踪时没有找到对相应属性订阅的副作用集合,它将会在这里新建。这就是 getSubscribersForProperty() 函数所做的事。

trigger() 之中,我们会再次查找到该属性的所有订阅副作用。这一次我们全部执行它们:

function trigger(target, key) {
  const effects = getSubscribersForProperty(target, key)
  effects.forEach((effect) => effect())
}

这些副作用就是用来执行 diff 算法,从而更新页面的。

这就是响应式系统的大致原理,Vue3 还做了编译器的优化,diff 算法的优化等等。不得不佩服尤大大,把 Vue 的响应式系统又提升了一个台阶!

(学习视频分享:vuejs入门教程编程基础视频

以上がvue3 の ref と reactive の包括的なインベントリの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。