VUE从入门到精通二:ref、reactive、computed计算属性、watch监听、组件之间的通信

目录

一、ref、reactive创建响应式对象

1、ref()

2、reactive()

3、ref和reactive的区别

二、computed计算属性

1、什么是计算属性computed

2、计算属性computed和函数方法的区别

3、计算属性computed的优势

三、watch监听函数

1、什么是watch?

2、基本语法

3、深度监听

4、使用字符串路径监听嵌套属性

5、动态添加watcher

6、立即执行watcher

7、一次性监听

8、停止watcher

[9、watch vs watchEffect监听方式对比](#9、watch vs watchEffect监听方式对比)

10、watch与computed的区别

[11、watch vue3与vue2的对比](#11、watch vue3与vue2的对比)

12、性能优化建议

13、完整示例

四、组件之间的通信

1、Props(父传子)

2、Emit(子传父)

[3、Provide / Inject(祖先传后代)](#3、Provide / Inject(祖先传后代))

4、Vuex全局事件总线(状态管理)


一、ref、reactive创建响应式对象

1、ref()

在Vue 3中,ref是一个函数,用于创建一个响应式引用。它可以定义基本数据类型(如字符串、数字、布尔值等)。虽然也可用于创建对象和数组,但更推荐使用reactive。使用ref时,需要通过.value属性来访问和修改数据。

复制代码
const copy_address_type = ref('')  // 地址同步文案
const exemption_fax = ref(0.00) //消费税
const exemption_no_show = ref(false)

2、reactive()

reactive它更适用于创建对象和数组。reactive会将整个对象或数组转换为响应式的,这意味着对象或数组中的每个属性都会被代理。

复制代码
const form_data = reactive({
                id: '', type:'', first_name: '',last_name: '',email: '',phone: '',institution: '',
                postcode: '',address: '',city: '',state_input: '',state: '',country: '', set_copy_address: ''
})
const country_data = ref([])

3、ref和reactive的区别

a.数据类型:ref适用于基本数据类型及复杂对象,而reactive主要用于复杂对象及嵌套数据结构。

****b.****访问方式:ref通过.value属性访问,而reactive直接通过属性访问。

****c.****响应性追踪:ref追踪单个独立的引用,reactive追踪整个对象及其内部属性。

****d.****可变性:ref的引用值可以重新赋值,而reactive对象本身是不可重新赋值的,只能修改其内部属性。

二、computed计算属性

1、什么是计算属性computed

computed‌是用于基于其他响应式数据动态计算新数据的工具,具有缓存机制,仅在依赖数据变化时重新计算,可显著提升性能,如果依赖的数据不变化,computed永远不会改变。

核心特性

‌依赖缓存‌:仅在依赖数据(如响应式引用)变化时重新计算,避免重复执行。 ‌

‌声明式定义‌:通过computed函数定义,支持复杂逻辑复用。 ‌

‌性能优化‌:减少模板渲染时的计算开销,提升渲染效率。

复制代码
<template>
  <div>{{ double }}</div>
  <input v-model="count" />
</template>

<script setup>
  import { ref, computed } from 'vue';
  const count = ref(1);
  const double = computed(() => count.value * 2);
</script>

上面的例子,当文本框的count发生改变时,double数据也会发生变化。

2、计算属性computed和函数方法的区别

复制代码
function doubleFunc(count) {
    let double = count * 2
    return double
  }

若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 author.books 不改变,无论多少次访问 double 都会立即返回先前的计算结果,而不用重复执行doubleFunc函数。

这也很好的解释了下面的计算属性永远不会更新,因为 Date.now() 并不是一个响应式依赖,它不会变化。

复制代码
const now = computed(() => Date.now())

3、计算属性computed的优势

A.简洁高效:通过计算属性computed可以简洁高效地实现基于其他属性计算的属性,避免了重复计算和代码冗余。

B.响应式更新:计算属性computed会自动响应依赖的变化而更新,保持界面和数据的同步。

C.缓存机制:计算属性computed会缓存计算结果,只有在相关依赖发生改变时才会重新计算,提高了性能和效率。

三、watch监听函数

1、什么是watch?

watch是Vue中用于监听数据的变化并执行相应的操作。

当被监听的数据发生变化时,会触发一个回调函数,我们可以在这个回调函数中执行一些逻辑操作。

watch适用于需要在数据变化时执行异步或较复杂的场景。

2、基本语法

复制代码
export default {
  setup() {
    const count = ref(0);
    watch(count, (newVal, oldVal) => {
      console.log(`count changed from ${oldVal} to ${newVal}`);
    });
    return { count,};
  }
};

3、深度监听

对于对象或数组的监视,默认情况下,Vue不会递归地监视对象内部属性的变化。如果你需要深度监视,vue2中可以设置deep选项为true。Vue3中你可以使用watchEffect或者watch函数,并通过提供一个回调函数来访问和监视这些深层属性。

Vue2深度监听

复制代码
export default {
  data() {
    return {
      user: {
        name: '张三',
        age: 25
      }
    }
  },
  watch: {
    user: {
      handler(newValue, oldValue) {
        console.log('user对象变化了', newValue, oldValue)
      },
      deep: true // 开启深度监听
    }
  }
}

VUE3深度监听

复制代码
import { ref, watchEffect } from 'vue';
 
const obj = ref({
  nested: {
    prop: 'value'
  }
});
 
watchEffect(() => {
  console.log(obj.value.nested.prop); // 访问深层属性
});
 
// 更新深层属性时,控制台将显示新值
obj.value.nested.prop = 'new value';

深度监听原理:

4、使用字符串路径监听嵌套属性

复制代码
const city = computed(() => state.user.address.city);
watch(city, (newValue, oldValue) => {
  console.log(`City changed from ${oldValue} to ${newValue}`);
});

5、动态添加watcher

如果你需要在组件的生命周期内动态添加watcher,可以使用Vue实例的$watch方法

复制代码
export default {
  mounted() {
    this.$watch('message', (newVal, oldVal) => {
      console.log(`message changed from ${oldVal} to ${newVal}`);
    });
  }

6、立即执行watcher

默认情况下,watch只会在数据变化时触发回调函数,而不会在初始化时执行。

如果需要在初始化时就执行一次回调函数,可以设置immediate: true。

复制代码
watch(
  () => obj.value.nested.prop, // 监视深层属性
  (newValue, oldValue) => {
    console.log(`Prop changed from ${oldValue} to ${newValue}`);
  },
  { immediate: true } // 立即执行一次回调
);

7、一次性监听

可以使用once: true选项来设置只监听一次变化

// 使用 once 选项让 watch 只监听一次

复制代码
watch(count, (newValue, oldValue) => {
  console.log(`Count changed from ${oldValue} to ${newValue}`);
}, { once: true });

8、停止watcher

当你使用$watch方法添加watcher时,可以通过返回的取消函数来停止watcher:

复制代码
const count = ref(0);
let stopWatch;
stopWatch = watch(count, (newVal, oldVal) => {
  console.log(`Count changed from ${oldVal} to ${newVal}`);
});
onUnmounted(() => {
  stopWatch(); // 确保在组件卸载时停止watcher
});

9、watch vs watchEffect监听方式对比

watch需要明确指定要监听的数据源,而watchEffect会自动收集其内部所使用的所有响应式依赖。

watch可以访问被监听状态的前一个值和当前值,而watchEffect只能访问当前值。

watchEffect会在组件初始化时立即执行一次,相当于设置了immediate: true的watch。

复制代码
const count = ref(0)
const message = ref('Hello')

// watchEffect会自动监听回调函数中使用的所有响应式数据
watchEffect(() => {
  console.log(`count: ${count.value}, message: ${message.value}`)
})
// 等价于以下watch写法
watch([count, message], ([newCount, newMessage]) => {
  console.log(`count: ${newCount}, message: ${newMessage}`)
}, { immediate: true })

10、watch与computed的区别

|------------|------------------|------------------|
| 特性 | watch | computed |
| 用途 | 监听数据变化并执行副作用 | 计算并返回新值 |
| 缓存 | 无缓存机制 | 有缓存,只在依赖变化时重新计算 |
| 返回值 | 无返回值 | 有返回值 |
| 适用场景 | 数据变化时执行异步操作或复杂逻辑 | 依赖其他数据计算出新值 |
| 执行时机 | 数据变化后执行 | 依赖变化时立即计算新值 |

11、watch vue3与vue2的对比

复制代码
// Vue 2 选项式 API
watch: {
  user: {
    handler(newVal) { /*...*/ },
    deep: true
  }
}

// Vue 3 组合式 API(更灵活)
const user = reactive({/*...*/})
watch(user, (newVal) => {/*...*/}, { deep: true })

12、性能优化建议

避免过度深度监听:只对必要对象开启

使用精确监听路径

复制代码
watch(() => user.address.city, (newCity) => {...})

及时清理监听
const stopWatch = watch(...)
onUnmounted(stopWatch) // 组件卸载时停止监听

13、完整示例

复制代码
const user = reactive({
  id: 1,
  info: {
    name: '张三',
    address: {
      city: '北京',
      street: '朝阳区'
    }
  }
})

// 深度监听整个用户对象
watch(
  user,
  (newUser) => {
    console.log('用户信息已修改,自动保存...')
    autoSave(newUser)
  },
  { deep: true, immediate: true }
)

// 精确监听城市变化
watch(
  () => user.info.address.city,
  (newCity) => {
    updateMap(newCity)
  }
)

四、组件之间的通信

1、Props(父传子)

父组件通过props向下传递数据给子组件。这是最常见的父子组件通信方式。

父组件

复制代码
<template>
  <ChildComponent :message="parentMessage" />
</template>
 
<script setup>
import ChildComponent from './ChildComponent.vue';
import { ref } from 'vue';
 
const parentMessage = ref('Hello from parent');
</script>

子组件

复制代码
<template>
  <div>{{ message }}</div>
</template>
 
<script setup>
defineProps({
  message: String
});
</script>

2、Emit(子传父)

子组件通过emit向父组件发送事件和数据。

子组件

复制代码
<template>
  <button @click="sendMessage">Send Message</button>
</template>
 
<script setup>
import { defineEmits } from 'vue';
 
const emit = defineEmits(['updateMessage']);
 
function sendMessage() {
  emit('updateMessage', 'Hello from child');
}
</script>

父组件

复制代码
<template>
  <ChildComponent @updateMessage="handleMessage" />
</template>
 
<script setup>
import ChildComponent from './ChildComponent.vue';
import { ref } from 'vue';
 
const childMessage = ref('');
function handleMessage(msg) {
  childMessage.value = msg;
}
</script>

3、Provide / Inject(祖先传后代)

provide和inject可以用于跨多级组件传递数据,非常适合在深层嵌套的组件结构中通信。

祖先组件

复制代码
<template>
  <DescendantComponent />
</template>
 
<script setup>
import { provide, ref } from 'vue';
import DescendantComponent from './DescendantComponent.vue';
 
const message = ref('Hello from ancestor');
provide('message', message); // 提供数据给后代组件使用
</script>

后代组件

复制代码
<template>
  <div>{{ message }}</div>
</template>
 
<script setup>
import { inject } from 'vue';
const message = inject('message'); // 注入数据从祖先组件接收数据
</script>

4、Vuex 全局事件总线 (状态管理)

对于更复杂的应用,可以使用Vuex进行状态管理,实现任意跨组件的通信。Vuex提供了一个集中存储管理应用所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

使用:

第一步:#src/store/index.js 创建vuex模块文件,开始使用vuex

在一个模块化的打包系统中,您必须显式地通过 `Vue.use()` 来安装 Vuex:

复制代码
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

第二步:#实例化Vuex.store ,并进行相关配置

复制代码
export default new Vuex.Store({
  state: {
      //存储状态
  },
  mutations: {
      //变更store中的状态
  },
  actions: {
      //类似于mutation,
      //action提交的是mutation,而不是直接变更状态
      //action可以包含异步操作
  },
  getters:{
    //state的派生状态  
  },
  modules: {
      //将store分割成模块
  }
})

第三步:#在main.js中,vue实例对象中注册store

复制代码
import store from './store'
new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

每一个 Vuex 应用的核心就是 store(仓库)。"store"基本上就是一个容器,它包含着你的应用中大部分的状态 (state)

相关推荐
IT_陈寒3 分钟前
「JavaScript 性能优化:10个让V8引擎疯狂提速的编码技巧」
前端·人工智能·后端
ObjectX前端实验室1 小时前
【react18原理探究实践】scheduler原理之Task 完整生命周期解析
前端·react.js
ObjectX前端实验室1 小时前
【react18原理探究实践】调度器(Scheduler)原理深度解析
前端·react.js
路漫漫心远1 小时前
音视频学习笔记十八——图像处理之OpenCV检测
前端
摸着石头过河的石头1 小时前
从零开始玩转前端:一站式掌握Web开发基础知识
前端·javascript
sniper_fandc2 小时前
关于Mybatis-Plus的insertOrUpdate()方法使用时的问题与解决—数值精度转化问题
java·前端·数据库·mybatisplus·主键id
10岁的博客2 小时前
技术博客SEO优化全攻略
前端
南屿im3 小时前
别再被引用坑了!JavaScript 深浅拷贝全攻略
前端·javascript
想要一辆洒水车3 小时前
vuex4源码分析学习
前端
sophie旭3 小时前
一道面试题,开始性能优化之旅(6)-- 异步任务和性能
前端·javascript·性能优化