Skip to content

组件状态

分类Composition API(Vue3)Options API(Vue3)Options API(Vue2)
声明ref() v3.0data v3.0data v2.0
读取-$data v3.0$data v2.0
更新computed() v3.0computed v3.0computed v2.0
监听watch() v3.0watch v3.0watch v2.0
--$watch() v3.0$watch() v2.0
-watchEffect() v3.0--
-watchPostEffect() v3.0--
-watchSyncEffect() v3.0--
-onWatcherCleanup() v3.5--
-effectScope() v3.0--
-getCurrentScope() v3.0--
-onScopeDispose() v3.0--

大纲

  • 状态
    • 声明
      • 初始值
        • 默认值来源
        • 初始化函数-避免重复创建初始状态
    • 读取
    • 更新
      • 派生状态/计算
      • 更新后立即获取
      • 状态变更立即更新 DOM
      • 更新状态中的对象和数组
      • 性能优化
        • 避免重复创建初始状态
        • 使用 key 重置状态
        • 存储前一次渲染的信息
    • 监听
      • 异步监听
      • 同步监听
      • 深度监听和立即执行
      • 监听依赖自动收集

状态声明

  • ref 响应式默认是深度展开的。避免深度转换,请使用 shallowRef
vue
<script setup lang="ts">
import { ref } from 'vue'

// 声明状态
const count = ref(0) 
</script>

<template>
  <div>
      <button type="button" @click="count--">-</button>
      {{ count }}
      <button type="button" @click="count++">+</button>
  </div>
</template>
vue
<script lang="ts">
export default {
  data() { 
    return {
      count: 0 
    };
  },
}
</script>

<template>
  <div>
      <button type="button" @click="count--">-</button>
      {{ count }}
      <button type="button" @click="count++">+</button>
  </div>
</template>

状态读取

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

// 声明状态
const count = ref(0)

// 脚本读取状态值
console.log(count.value)
</script>

<template>
  <div>
      <button type="button" @click="count--">-</button>
      <!-- 模版读取状态值 -->
      {{ count }}
      <button type="button" @click="count++">+</button>
  </div>
</template>
vue
<script lang="ts">
export default {
  data() {
    return {
      count: 0,
    };
  },
  mounted() {
    // 脚本读取状态值
    console.log(this.count)
  }
}
</script>

<template>
  <div>
      <button type="button" @click="count--">-</button>
      <!-- 模版读取状态值 -->
      {{ count }}
      <button type="button" @click="count++">+</button>
  </div>
</template>

状态更新

状态值的更新是同步的,但状态值更新后的 DOM 渲染是异步的。

计算

computed 计算属性允许我们以声明方式计算派生值。

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

// 声明状态
const count = ref(0)

// 计算
const newCount = computed(() => count.value * 2); 
</script>

<template>
  <div>
      <button type="button" @click="count--">-</button>
      {{ count }}
      <button type="button" @click="count++">+</button>
      {{ newCount }}
  </div>
</template>
vue
<script lang="ts">
export default {
  data() {
    return {
      count: 0
    };
  },
  computed: { 
    newCount: function() {
      return this.count * 2;
    }
  }
}
</script>

<template>
  <div>
      <button type="button" @click="count--">-</button>
      {{ count }}
      <button type="button" @click="count++">+</button>
      {{ newCount }}
  </div>
</template>

状态变更立即更新 DOM

状态更新后立即获取应用新状态后的 DOM 实例的值。

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

// 声明状态
const count = ref(0)
const counter = ref(null)

const cb = () => {
  // 同步更新 state
  console.log('count.value', count.value)
  // 异步更新 DOM ,state 为更新前的值
  console.log('counter', counter.value?.textContent)

  nextTick(() => {
    // 立即执行 DOM 更新,state 为更新后的值
    console.log('counter', counter.value?.textContent)
  })
}

const handleIncrease = () => {
  count.value++
  cb();
}
const handleDecrease = () => {
  count.value--;
  cb();
}
</script>

<template>
  <div ref="counter">{{ count }}</div>
  <div>
      <button type="button" @click="handleDecrease">-</button>
      {{ count }}
      <button type="button" @click="handleIncrease">+</button>
  </div>
</template>
vue
<script lang="ts">
export default {
  data() {
    return {
      count: 0,
    };
  },
  mounted() {
    // 脚本读取状态值
    console.log(this.count)
  },
  methods: {
    cb: function () {
      // 同步更新 state
      console.log('count', this.count)
      // 异步更新 DOM ,state 为更新前的值
      console.log('counter', this.$refs.counter.textContent)

      this.$nextTick(function () {
        // 立即执行 DOM 更新,state 为更新后的值
        console.log('counter', this.$refs.counter.textContent)
      })
    },
    handleIncrease: function () {
      this.count++;
      this.cb();
    },
    handleDecrease: function () {
      this.count--;
      this.cb();
    },
  }
}
</script>

<template>
  <div ref="counter">{{ count }}</div>
  <div>
      <button type="button" @click="handleDecrease">-</button>
      {{ count }}
      <button type="button" @click="handleIncrease">+</button>
  </div>
</template>

状态监听

watch 函数在一段响应式状态发生变化时触发回调。比如:执行 “副作用” 来响应状态更改,更改 DOM 或根据异步操作的结果更改另一个状态。

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

// 声明状态
const count = ref(0)
const total = ref(0)

// 计算
const newCount = computed(() => count.value * 2);

// 监听状态值
watch(count, (newVal, oldVal) => {
  // 更改状态值
  total.value = newVal
})

// 监听计算属性
watch(newCount, (newVal, oldVal) => {
})
</script>

<template>
  <div>
      <button type="button" @click="count--">-</button>
      {{ count }}
      <button type="button" @click="count++">+</button>
      {{ newCount }}
  </div>
  <div>{{ total }}</div>
</template>
vue
<script lang="ts">
export default {
  data() {
    return {
      count: 0,
      total: 0
    };
  },
  computed: {
    newCount: function() {
      return this.count * 2;
    }
  },
  // 监听状态值
  watch: { 
    count: function(newVal, oldVal) {
      // 更改状态值
      this.total = newVal
    },
    newCount: function(newVal, oldVal) {
    }
  }
}
</script>

<template>
  <div>
      <button type="button" @click="count--">-</button>
      {{ count }}
      <button type="button" @click="count++">+</button>
      {{ newCount }}
  </div>
  <div>{{ total }}</div>
</template>

深度监听和立即执行

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

// 声明状态
const count = ref(0)
const total = ref(0)

// 监听状态值
watch(count, (newVal, oldVal) => {
  total.value = newVal
}, {
  deep: true, // 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
  immediate: true // 该回调将会在侦听开始之后被立即调用
})
</script>

<template>
  <div>
      <button type="button" @click="count--">-</button>
      {{ count }}
      <button type="button" @click="count++">+</button>
  </div>
  <div>{{ total }}</div>
</template>
vue
<script lang="ts">
export default {
  data() {
    return {
      count: 0,
      total: 0
    };
  },
  watch: {
    count: {
      handler: function(newVal, oldVal) {
        this.total = newVal
      }, 
      deep: true, // 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
      immediate: true // 该回调将会在侦听开始之后被立即调用
    }
  }
}
</script>

<template>
  <div>
      <button type="button" @click="count--">-</button>
      {{ count }}
      <button type="button" @click="count++">+</button>
  </div>
  <div>{{ total }}</div>
</template>

自动收集监听依赖

  • watchEffect:默认情况下,watcher 回调会进行批处理避免重复调用,在当前组件的 DOM 更新之前,调用 watcher 回调。这意味着,watcher 回调中访问当前组件的 DOM,DOM 将处于更新前的状态。
  • watchPostEffect:支持在当前组件的 DOM 更新之后,调用 watcher 回调。
  • watchSyncEffect:没有批处理功能,并且每次检测到响应式更新时都会触发,谨慎使用
vue
<script setup lang="ts">
import { ref } from 'vue'

// 声明状态
const count = ref(0)
const total = ref(0)

// 脚本读取状态值
console.log(count.value)

watchEffect(() => {
  total.value = count.value
})
</script>

<template>
  <div>
      <button type="button" @click="count--">-</button>
      <!-- 模版读取状态值 -->
      {{ count }}
      <button type="button" @click="count++">+</button>
  </div>
  <div>total: {{ total }}</div>
</template>

watch 和 watchEffect 的共同点和区别:

  • 共同点
    • 两者都允许我们被动的执行副作用
    • 都支持清理副作用(onCleanup/onWatcherCleanup)
    • 都支持编排回调执行的时机,DOM 更新前执行(flush: 'pre'),DOM更新后执行(flush: 'post')
    • 都支持停止、暂停、恢复监视程序
  • 区别
    • 跟踪响应式依赖关系的方式不同
      • watch 仅跟踪指定的源,它不跟踪回调中访问的任何内容。回调仅在源实际更改时触发。watch 将依赖跟踪和副作用分开,使我们能够更精确地控制回调何时触发。
        • watch 默认是懒惰的,在被监视的源发生更改之前,不会调用回调
        • watch 更具体的说明什么状态应该触发回调及重新允许,比如:深度跟踪(deep)、立即执行(immediate)、仅触发一次(once)
        • watch 回调函数具有 newValue 和 oldValue,可以通过他们更精确的控制回调的逻辑
      • watchEffect 自动跟踪(类似于计算属性)在同步执行期间访问的每个响应式属性。watchEffect 将依赖跟踪和副作用结合到一个阶段中,通常这更方便,导致代码更简洁。
        • watchEffect 默认会立即执行
        • watchEffect 在深度跟踪场景比 deep 更有效,因为它只会跟踪回调中使用的属性,而不是跟 deep 那样递归跟踪的所有属性

问题

Options API 中 data 为什么不能用纯对象来定义?

因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。