3. 响应式

3. 响应式

1. reactive

reactive() 仅适用于对象(包括数组和内置类型,如 Map Set)。

我们可以使用 reactive() 函数创建一个响应式对象或数组

import {
    
     reactive } from 'vue'

const state = reactive({
    
     count: 0 })

要在组件模板中使用响应式状态,需要在 setup() 函数中定义并返回。

import {
    
     reactive } from 'vue'

export default {
    
    
  setup() {
    
    
    const state = reactive({
    
     count: 0 })

    function increment() {
    
    
      state.count++
    }
      
    // 一定更要返回
    return {
    
    
      state,
      increment
    }
  }
}

setup() 函数中手动暴露状态和方法可能非常繁琐。

也可以通过使用构建工具来简化该操作。

当使用单文件组件(SFC)时,我们可以使用 <script setup> 来简化大量样板代码。

<script setup>
import {
      
       reactive } from 'vue'

const state = reactive({
      
       count: 0 })

function increment() {
      
      
  state.count++
}
</script>

<template>
  <button @click="increment">
    {
   
   { state.count }}
  </button>
</template>

<script setup> 中的顶层的导入和变量声明可在同一组件的模板中自动使用, 不需要 return 返回

2. DOM 更新时机

当你更改响应式数据后,DOM 也会自动更新

Vue 组件数据的更改不会立即反映在 DOM 中。相反,Vue 异步更新 DOM

<template>
  <div>
    <button @click="handleClick">Insert/Remove</button>
    <div v-if="show" ref="content">I am an element</div>
  </div>
</template>
<script>
export default {
      
      
  data() {
      
      
    return {
      
      
      show: true,
    };
  },
  methods: {
      
      
    handleClick() {
      
      
      this.show = !this.show;
      console.log(this.show, this.$refs.content);
    },
  },
};
</script>

<!-- 
点击的时候会有这么两组结果, 

false <div> I am an element </div>

true null
-->

我们可以发现, 在 v-if='true'的时候, 并没有存在当前的 DOM

若要等待一个状态改变后的 DOM 更新完成,你可以使用 nextTick 这个全局 API

Vue 允许this.$nextTick(callback)在组件实例上使用权限。callback在新数据更新到达 DOM 后立即执行。

<template>
  <div>
    <button @click="handleClick">Insert/Remove</button>
    <div v-if="show" ref="content">I am an element</div>
  </div>
</template>
<script>
export default {
      
      
  data() {
      
      
    return {
      
      
      show: true,
    };
  },
  methods: {
      
      
    handleClick() {
      
      
      this.show = !this.show;
      this.$nextTick(() => {
      
      
        console.log(this.show, this.$refs.content);
      });
    },
  },
};
</script>

<!-- 
点击的时候会有这么两组结果, 

false null
true <div>I am an element</div>
-->

this.$nextTick 函数中我们可以获取到发送改变后的 DOM

如果在没有参数的情况下调用 this.$nextTick(),则函数返回一个 promise,当组件数据更改到达 DOM 时该 promise 会被解析。

<template>
  <div>
    <button @click="handleClick">Insert/Remove</button>
    <div v-if="show" ref="content">I am an element</div>
  </div>
</template>
<script>
export default {
    
    
  data() {
    
    
    return {
    
    
      show: true,
    };
  },
  methods: {
    
    
    async handleClick() {
    
    
      this.show = !this.show;
      await this.$nextTick();
      console.log(this.show, this.$refs.content);
    },
  },
};
</script>

3. 深层响应性

Vue 中,状态都是默认深层响应式的。

这意味着即使在更改深层次的对象或数组,你的改动也能被检测到。

import {
    
     reactive } from 'vue'

const obj = reactive({
    
    
  nested: {
    
     count: 0 },
  arr: ['foo', 'bar']
})

function mutateDeeply() {
    
    
  // 以下都会按照期望工作
  obj.nested.count++
  obj.arr.push('baz')
}

4. 响应式代理

值得注意的是,reactive() 返回的是一个原始对象的 Proxy,它和原始对象是不相等的

关于 proxy es6 的知识点

const raw = {
    
    }
const proxy = reactive(raw)

// 代理和原始对象不是全等的
console.log(proxy === raw) // false

只有代理是响应式的,更改原始对象不会触发更新。因此,使用 Vue 的响应式系统的最佳实践是 仅使用你声明对象的代理版本。

为保证访问代理的一致性,对同一个对象调用 reactive() 会总是返回同样的代理,而对一个已存在代理调用 reactive() 也是返回同样的代理:

// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true

// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true

这个规则对嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理

const proxy = reactive({
    
    })

const raw = {
    
    }
proxy.nested = raw

console.log(proxy.nested === raw) // false

5. reactive 局限性

1 仅对对象类型有效(对象、数组和 MapSet 这样的数据类型有效,而对 stringnumberboolean 这样的无效。


2 因为 Vue 的响应式系统是通过 property 访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。

这意味着我们不可以随意地替换一个响应式对象,因为这将导致对初始引用的响应性连接丢失

let state = reactive({
    
     count: 0 })

// 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!)
state = reactive({
    
     count: 1 })

同时这也意味着当我们将响应式对象的 property 赋值或解构至本地变量时,或是将该 property 传入一个函数时,我们会失去响应性

const state = reactive({
    
     count: 0 })

// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count
// 不影响原始的 state
n++

// count 也和 state.count 失去了响应性连接
let {
    
     count } = state
// 不会影响原始的 state
count++

6. ref

为了解决 reactive() 带来的限制, ref()可以采用任何值类型并创建一个对象,该对象在 .value 属性下公开内部值

和响应式对象的 property 类似,ref 的 .value property 也是响应式的。

import {
    
     ref } from 'vue'

const message = ref('Hello World!')

console.log(message.value) // "Hello World!"
message.value = 'Changed'
console.log(message.value) // "Changed"

在模板中使用时我们不需要再使用 .value 的形式, 直接使用该对象就可以

<h1>{
   
   { message }}</h1>

ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性

const obj = {
    
    
  foo: ref(1),
  bar: ref(2)
}

// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo)

// 仍然是响应式的
const {
    
     foo, bar } = obj

一言以蔽之,ref() 使我们能创造一种任意值的引用并能够不丢失响应性地随意传递。

猜你喜欢

转载自blog.csdn.net/ximing020714/article/details/125140292