vue3
2024年1月13日大约 5 分钟
简介
一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型
响应式数据
ref()可以用于如 string、number 或 boolean 这样的原始类型,也能用于对象类型。reactive()只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)。
ref() 与 reactive() 区别
- ref()包裹的数据必须使用.value 才能得到,reactive() 包裹的对象可直接使用;
- ref()包裹的对象可以直接赋值;reactive()包裹的对象不能二次赋值,否则响应式失效;可使用 assign 方法完成二次赋值。
以下为常见的响应式数据操作
赋值<script setup lang="ts"> import { ref, reactive } from "vue"; let person_ref = ref({ name: "zs", age: 18 }); let person_re = reactive({ name: "zs", age: 18 }); function changePerson() { // ref 赋值 person_ref.value = { name: "ls", age: 18 }; // reactive 赋值 // person_re = { name: "ls", age: 18 }; 错误写法,响应式失效 // person_re = reactive({ name: "ls", age: 18 }); 错误写法,响应式失效 // 以下是正确示例 person_re = Object.assign(person_re, { name: "ls", age: 18 }); } </script>解构<script setup lang="ts"> import { reactive, toRefs } from "vue"; let person_re = reactive({ name: "zs", age: 18 }); // reactive 解构示例 // let { name, age } = person_re; 错误写法,结构响应式失效 let { name, age } = toRefs(person_re); // 正确示例 function changePerson() { name.value += "666"; age.value += 1; } </script>使用原则
- 基本类型的响应式数据,必须用 ref
- 层级不深的响应式对象,ref 和 reactive 都行
- 层级较深的响应式对象,使用 reactive
自定义 Hooks
功能描述:若一个页面逻辑比较复杂,将所有的变量和方法声明在同一个 vue 文件中会比较臃肿,可以将每个功能点分离出来,放在单独的文件中,有点类似 vue2 的 mixin。
以下为一个简单示例,有吃饭和学习两大功能,将他们分离,演示 vue3 中 hooks 用法(一般的,分离的功能命名为 useXXX.ts):
原始版
<!-- Animal.vue -->
<template>
<div>
<!-- 吃饭 -->
<h1>{{ result }}</h1>
<button @click="eating">吃饭</button>
<!-- 学习 -->
<h1>{{ sum }}</h1>
<button @click="study">学习</button>
</div>
</template>
<script setup lang="ts" name="Animal">
import { ref } from "vue";
// 吃饭
let count = ref(0);
let result = ref("");
function eating() {
count.value += 1;
result.value = "早上吃了" + count.value + "个包子";
}
// 学习
let sum = ref(0);
function study() {
breakfast.value += 1;
}
</script>Hooks 版
<!-- Animal.vue -->
<template>
<div>
<!-- 吃饭 -->
<h1>{{ result }}</h1>
<button @click="eating">吃饭</button>
<!-- 学习 -->
<h1>{{ sum }}</h1>
<button @click="study">学习</button>
</div>
</template>
<script setup lang="ts" name="Animal">
import useEat from "@/hooks/xxx/useEat";
import useStudy from "@/hooks/xxx/useStudy";
// 吃饭
const { count, result, eating } = useEat();
// 学习
const { sum, study } = useStudy();
</script>吃饭
// useEat.ts
import { ref } from "vue";
// 吃饭
export default function () {
let count = ref(0);
let result = ref("");
function eating() {
count.value += 1;
result.value = "早上吃了" + count.value + "个包子";
}
// 向外部提供数据
return { count, result, eating };
}学习
// useStudy.ts
import { ref } from "vue";
// 学习
export default function () {
let sum = ref(0);
function study() {
breakfast.value += 1;
}
// 向外部提供数据
return { sum, study };
}提示
可以在每个 useXXX.ts 中单独使用 vue 生命周期函数,他们互不影响。
状态管理工具
vuex、redux、pinia【/piːnjʌ/】 等都属于集中式状态(数据)管理工具,在 vue2 中使用 vuex,在 vue3 中推荐使用 pinia,更加简洁。
操作 store
一个 store 由 state、getters、action 三部分构成,可以认为 state 是 store 的数据,getters 是 store 的计算属性,actions 则是方法。
定义 store (选项式)
// user.ts -- 选项式
export const useMyUserStore = defineStore("myUser", {
state: () => ({ age: 18, name: "zs" }),
getters: {
doubleAge: (state) => state.age * 2,
},
actions: {
// 修改 state
incrementAge(value: number) {
this.age += value;
},
},
});定义 store (组合式)
// user.ts -- 组合式
export const useMyUserStore = defineStore("myUser", () => {
const age = ref(18);
const name = ref("zs");
const doubleAge = computed(() => {
age.value * 2;
});
function incrementAge(value: number) {
this.age += value;
}
return { age, name, doubleAge, incrementAge };
});读取 store
<!-- User.vue -->
<template>
<h1>用户:{{ name }}--年龄:{{ age }}--2倍年龄:{{ doubleAge }}</h1>
</template>
<script setup lang="ts" name="User">
import { ref } from "vue";
import { storeToRefs } from "pinia"
import { useMyUserStore } from "@/store/xxx/user";
const myUserStore = useMyUserStore()
// 获取响应式state数据;
// 这里不用toRefs包裹,因为他会将所有数据,包含方法等无关数据封装为响应式对象。
// 而pinia提供的storeToRefs只会将state中的数据包装为响应式。
const { name, age, doubleAge } = storeToRefs(myUserStore)
</script>修改 store
<!-- User.vue -->
<template>
<div>
<h1>用户:{{ name }}--年龄:{{ age }}</h1>
<button @click="changeAge">长大啦</button>
</div>
</template>
<script setup lang="ts" name="User">
import { ref } from "vue";
import { useMyUserStore } from "@/store/xxx/user";
import { storeToRefs } from "pinia"
const myUserStore = useMyUserStore()
const { name, age } = storeToRefs(myUserStore)
// 修改年龄
function changeAge() {
myUserStore.age += 1; // 方式一:直接修改,适合简单业务逻辑
myUserStore.$patch({age: 18}); // 方式二:批量变更,适合修改多个属性值
myUserStore.incrementAge(1); // 方式三:使用action修改,非常优美
}
</script>订阅 store
<!-- User.vue -->
<template>
<div>
<h1>用户:{{ name }}--年龄:{{ age }}</h1>
</div>
</template>
<script setup lang="ts" name="User">
import { ref } from "vue";
import { useMyUserStore } from "@/store/xxx/user";
import { storeToRefs } from "pinia"
const myUserStore = useMyUserStore()
const { name, age } = storeToRefs(myUserStore)
// subscribe方法可以监听state中数据的变化,数据改变,会自动调用传递给他的方法
myUserStore.$subscribe((mutate,state)=>{
console.log("store中数据发生了变化!",state)
})
</script>插槽
提示
插槽指定名称可用#号简写
<!-- 组件1 -->
<template>
<Two>
<templete v-slot:s1>
<span>第一个插槽</span>
</templete>
<templete #s2>
<span>第二个插槽</span>
</templete>
<templete v-slot:s3="mydata">
<span>第三个插槽,可拿到子组件数据:{{ mydata }}</span>
</templete>
</Two>
</template>
<script setup lang="ts" name="One">
import Two from "./components/xxx/Two.vue";
</script>
<!-- 组件2 -->
<template>
<div>
<slot name="s1">插槽一默认内容</slot>
<span>我是通用内容</span>
<slot name="s2">插槽二默认内容</slot>
<slot name="s3" :mydata="myData">插槽三默认内容</slot>
</div>
</template>
<script setup lang="ts" name="Two">
import { reactive } from "vue";
let myData = reactive([
{ id: 1, name: "zs" },
{ id: 2, name: "ls" },
]);
</script>
