响应式状态(vue3入门)

时间:2024-12-13 06:57:58

1. 组合式API(Composition API)

在 Vue 3 中,Options APIComposition API 是两种不同的编程方式,各有优缺点和适用场景。

a. 定义方式

Options API

  • 通过一组固定的选项(如 datamethodscomputed 等)定义组件逻辑。
  • 常见于 Vue 2 时代,逻辑是按功能分块的。组件较大时,相关逻辑可能被分散,阅读和维护较难。

示例:

export default {
  data() {
    return {
      count: 0
    };
  },
  methods: {
    increment() {
      this.count++;
    }
  }
};

Composition API

  • 使用 JavaScript 函数(如 setup)来组织组件逻辑。
  • 逻辑可以按功能分组,增强了代码的可重用性和灵活性。适合复杂场景,例如跨组件共享逻辑或处理状态。

示例:

import { ref } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const increment = () => count.value++;
    
    return { count, increment };
  }
};

b. 类型支持(TypeScript)

Options API

  • TypeScript 支持有限,难以对 dataprops 进行静态推断。
  • 方法和属性的类型需要手动指定。

Composition API

  • 原生支持 TypeScript,类型推断更强大。
  • refreactive 的类型定义清晰,开发体验更好。

c. 响应式系统

Options API

  • 响应式由 Vue 内部自动处理,基于 this 的属性访问。
  • 不够显式,难以跟踪响应式行为。

Composition API

  • 显式使用 refreactive 创建响应式数据。
  • 更直观,开发者可以完全掌控响应式逻辑。

总结

特性 Options API Composition API
代码组织 按选项分块,逻辑可能分散 按功能分块,逻辑集中
类型支持 TypeScript 支持有限 原生支持 TypeScript
响应式系统 隐式响应式,基于 this 显式响应式,更灵活
适用场景 简单项目,易上手 复杂项目,高级功能需求

Vue 3 更推荐 Composition API,尤其是在需要模块化逻辑或使用 TypeScript 的项目中。然而,如果项目较小或团队成员熟悉 Options API,继续使用它也没有问题。


2. setup

setup 是 Vue 3 中 Composition API 的核心函数,用于定义组件的逻辑。它在组件实例创建之前执行,作为一个更灵活、更简洁的入口,整合了响应式状态、生命周期钩子以及依赖注入等功能。

a. 基本语法

<script lang="ts">
	export default {
	  setup() {
	    // 组件逻辑
	    return {
	      // 暴露给模板使用的变量和方法
	    };
	  }
	};
</script>

【特点】

  • setup函数返回的对象中的内容,可直接在模板中使用。
  • setup中访问this是undefined。
  • setup函数会在beforeCreate之前调用,它是“领先”所有钩子执行的。(可以先不管)

b. 返回值

  1. 若返回一个对象:则对象中的:属性、方法等,在模板中均可以直接使用。
  2. 若返回一个函数:则可以自定义渲染内容。

c. 与Options API的关系

  • vue2的配置可以访问到setup的属性和方法,反之不可以
  • 若两者冲突,setup优先

d. setup独立

<template>
  <div class="person">
    <h2>姓名:{{name}}</h2>
    <button @click="changName">修改名字</button>
  </div>
</template>

<script lang="ts">
  export default {
    name:'Person',
  }
</script>

<!-- 下面的写法是setup语法糖 -->
<script setup lang="ts">
  console.log(this) //undefined
  // 数据(注意:此时的name、age、tel都不是响应式数据)
  let name = '张三'

  // 方法
  function changName(){
    name = '李四'//注意:此时这么修改name页面是不变化的
  }
}
</script>

扩展:上述代码,还需要编写一个不写setup的script标签,去指定组件名字,比较麻烦,我们可以借助vite中的插件简化

第一步:终端输入npm i vite-plugin-vue-setup-extend -D
第二步:修改vite.config.ts文件,修改如下
第三步:<script setup lang="ts" name="Person">直接这样写

// 修改vite.config.ts文件
import { defineConfig } from 'vite'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'

export default defineConfig({
  plugins: [ VueSetupExtend() ]
})

e. 基本功能

  • 定义响应式数据
  • 访问 Props 和 Context
  • 使用计算属性利用 computed 创建依赖于其他状态的计算值
  • 监听数据变化,使用 watchwatchEffect 监听数据变化
  • 使用生命周期钩子,通过 onXXX 函数注册生命周期钩子

基本的功能会在后面有具体的展开

f. 常见问题

1. 为什么没有 this

setup 中,this 不再指向组件实例,而是完全通过函数作用域管理状态。这使得逻辑更清晰,也避免了传统 Options APIthis 指向问题。

2. 如何暴露方法给模板?

通过 return 将需要在模板中使用的变量和方法显式暴露:

export default {
  setup() {
    const msg = 'Hello, Vue 3!';
    const sayHello = () => console.log(msg);

    return { msg, sayHello }; // 暴露给模板
  }
};

3. 如何复用逻辑?

可以通过自定义 Hook(composables)实现逻辑复用:

// useCounter.js
import { ref } from 'vue';

export function useCounter() {
  const count = ref(0);
  const increment = () => count.value++;
  return { count, increment };
}

// 在组件中使用
import { useCounter } from './useCounter';

export default {
  setup() {
    const { count, increment } = useCounter();
    return { count, increment };
  }
};

g. 适用场景

  • 需要更强的逻辑复用能力。
  • 希望使用显式声明的响应式系统。
  • 项目使用 TypeScript。

setup 是 Vue 3 的核心设计之一,通过简化组件逻辑、增强灵活性,为现代应用开发提供了更强大的工具。


3. 两种方式表示响应式数据

在 Vue 3 中,refreactive 是创建响应式数据的两种主要方式。虽然它们的功能类似,都是为了让数据变得响应式,但在使用场景和实现细节上有所不同。响应式数据就是当数据发生变化时,视图会自动更新以反映数据的变化,使得我们能够将数据和视图关联起来。

a. 基本定义

ref

  • 用于创建单个响应式数据,更轻量
  • 返回一个带有 .value 属性的对象,数据存储在 .value 中。在模版中不需要.value来使用。
  • 适合处理原始数据类型(如字符串、数字、布尔值)或独立的值。也可以定义对象。

示例:

import { ref } from 'vue';

const count = ref(0);
console.log(count.value); // 0
count.value++;
console.log(count.value); // 1
// count不是响应式的,count.value才是响应式的

reactive

  • 用于将整个对象或数组变成响应式。
  • 返回一个 Proxy 对象,可以直接操作其属性。不需要.value来使用。
  • 适合复杂数据结构(如对象或嵌套数据)。不可定义基本类型。

示例:

import { reactive } from 'vue';

const user = reactive({ name: 'Alice', age: 25 });
console.log(user.name); // Alice
user.age++;
console.log(user.age); // 26

b. 使用场景对比

特性 ref reactive
适合的数据类型 原始值(如字符串、数字等) 对象或数组
嵌套响应性 需要手动处理嵌套对象 自动处理嵌套对象的响应性
模板中使用 需要通过 .value 访问 直接访问属性即可

c. 响应式行为的区别

ref 的响应式行为

  • 对原始值类型(如 numberstring)直接处理。
  • 对复杂对象(如数组、对象)时,仍然需要通过 .value 使用。
  • 嵌套属性不是自动响应式,需额外处理。

示例:

const nestedRef = ref({ name: 'Alice' });
nestedRef.value.name = 'Bob'; // 响应式工作
console.log(nestedRef.value.name); // Bob

注意:
如果需要更深层嵌套的响应式行为,推荐使用 reactive
可使用TypeScript Vue Plugin(Volar)插件实现自动 .value(需要在设置中开启)
子组件Person.vue中要使用defineExpose暴露内容

<script lang="ts" setup name="Person">
  import {ref,defineExpose} from 'vue'
	// 数据
  let name = ref('张三')
  let age = ref(18)

  // 使用defineExpose将组件中的数据导出交给外部
  defineExpose({name,age})
</script>

reactive 的响应式行为

  • 自动处理嵌套属性的响应性。
  • 对整个对象的修改都会触发视图更新。
  • 重新分配一个新对象,会失去响应式(可以使用Object.assign去整体替换)。

示例:

const user = reactive({ name: 'Alice', details: { age: 25 } });
user.details.age++; // 响应式生效
console.log(user.details.age); // 26

d. 嵌套结构和组合使用

ref 配合 reactive 使用

对于需要单独管理某些原始值或嵌套结构时,可以组合使用:

setup() {
  const user = reactive({
    name: 'Alice',
    age: ref(25) // 嵌套使用 ref
  });

  user.age.value++; // 修改嵌套的 ref
  return { user };
}

reactive 嵌套响应式

reactive 会自动处理嵌套对象的响应性:

setup() {
  const data = reactive({
    user: { name: 'Alice', age: 25 },
    settings: { theme: 'dark' }
  });
  data.user.age++; // 自动响应
  return { data };
}

e. 选择指南

使用场景 推荐方式
原始数据或单个值 ref
多属性的对象或数组 reactive
嵌套结构(深层次对象) reactive
组合简单数据和复杂数据 二者结合

4. toRefs

在 Vue 中,reactive 返回的是一个代理对象,而该对象是不可直接解构的。如果你尝试解构一个 reactive 对象,它的属性会失去响应性。因此,toRefs 就是为了解决这个问题,它能将 reactive 对象转换成每个属性都作为 ref 的普通对象,从而使得你可以解构使用。

a. 基本用法

假设你有一个 reactive 对象,其中包含多个属性。如果你直接解构这个对象,属性会丧失响应性,但使用 toRefs 可以保留每个属性的响应性。

示例 1:解构响应式对象

import { reactive, toRefs } from 'vue';

export default {
  setup() {
    const state = reactive({
      count: 0,
      name: 'Alice'
    });

    // 使用 toRefs 将 state 解构,并保持响应性
    const { count, name } = toRefs(state);

    // 返回解构后的响应式引用
    return { count, name };
  }
};

解释:

  • state 是一个响应式对象。
  • toRefs(state) 会将 state 的每个属性转换成 ref,这样你可以解构并保持每个属性的响应性。
  • countname 都是 ref,你可以像访问 ref 一样通过 .value 来访问它们。

模板中的使用:

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Name: {{ name }}</p>
  </div>
</template>

在模板中,你可以直接访问 countname,Vue 会自动解包 ref.value,所以不需要显式地使用 .value

b. 为什么使用 toRefs 而不是直接解构 reactive 对象?

  • 1. 保持响应性

如前所述,直接解构 reactive 对象会丧失响应性。这是因为解构是对对象的浅拷贝,而不是引用传递。通过 toRefs,你能够确保每个属性仍然是响应式的 ref

  • 2. 与 ref 一致性

refreactive 都是响应式数据。ref 适用于单一数据项,而 reactive 适用于对象或数组。toRefs 允许你将 reactive 对象的每个属性转成 ref,这样就能像处理 ref 那样,灵活地处理对象中的单个属性。

c. 与组件外部数据共享

toRefs 还可以用于在多个组件或函数之间共享响应式数据。在这种情况下,你可以将响应式对象传递给外部函数,并通过 toRefs 保证其属性的响应性。

import { reactive, toRefs } from 'vue';

function useFormState() {
  const state = reactive({
    username: '',
    email: ''
  });

  // 将 state 转换为 ref
  return toRefs(state);
}

export default {
  setup() {
    const { username, email } = useFormState();
    return { username, email };
  }
};

下篇文章会进一步详细地介绍更多vue3的核心基础知识,如果对你有帮助不妨点个赞支持一下~
参考:张天禹老师b站课程