实现原理:
Object.defineProperty()对属性的读取、修改、拦截存在问题:
解决以上问题:
// 给对象新增属性
this.$set(this.person,'sex','女')
// 删除对象的name属性
this.$delete(this.person,'name')
// 修改数组的第一个元素
this.$set(this.arr,0,'xxx')
实现原理:
<template>
<div><child v-model="isShow"></child></div>
</template>
<script lang="ts" setup>
const isShow = ref(true);
</script>
<template>
<div>{{ modelValue }}</div>
</template>
<script lang="ts" setup>
const props = defineProps<{
modelValue: boolean;
}>();
const emits = defineEmits(["update:model-value"]);
// 更新父组件的值
const submit = () => {
emits("update:model-value", false);
};
</script>
ref 定义基本数据类型,通过 Object.defineProperty()的 get、set 方法实现响应式reactive 定义复杂数据类型,通过 Proxy 实现响应式,并通过 Reflect 操作源对象内部数据ref 定义复杂数据类型,内部会自动通过 reactive 转为代理对象ref 定义的数据,操作数据要.value,在模板中直接使用,不需要.valueref获取 dom<template>
<div ref="myRef">获取单个DOM元素</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
export default {
//这里的myRef名字要保持一致
const myRef = ref(null);
console.log(myRef.value);
}
};
</script>
const arr = reactive([]);
const load = () => {
const res = [2, 3, 4, 5]; //假设请求接口返回的数据
// 方法1 失败,直接赋值丢失了响应性
// arr = res;
// 方法2 这样也是失败
// arr.concat(res);
};
解决方案
// 1.ref定义
const arr = ref([]);
arr.value = [1, 2, 3];
// 2.push方法
let list = reactive([]);
let arr = [1, 2, 3];
list.push(...arr);
// 3.创建响应式对象
let state = reactive({
list: []
});
let arr = [1, 2, 3];
state.list = arr;
import { computed } from "@vue";
// 简写
let fullName = computed(() => {
return person.firstName + " " + person.lastName;
});
// 完整
let fullName = computed({
get() {
return person.firstName + " " + person.lastName;
},
set(val) {
let names = val.split(" ");
person.firstName = names[0];
person.lastName = names[1];
}
});
reactive 定义的数据时,oldValue 无法正确获取,开启了深度监视(deep 配置失效)reactive 定义的数据的某个属性时,deep 配置有效// 监听ref定义的数据
watch(
sum,
(newVal, oldVal) => {
console.log(newVal, oldVal);
},
{ immediate: true }
);
// 监听多个ref定义的数据
watch(
[sum, msg],
(newVal, oldVal) => {
console.log(newVal, oldVal);
},
{ immediate: true }
);
// 监听多个reactive定义的数据
watch(
person,
(newVal, oldVal) => {
console.log(newVal, oldVal); //oldVal 无法正确获取
},
{ immediate: true, deep: false } //deep配置失效
);
// 监听reactive定义的数据的某个属性
watch(
() => person.job,
(newVal, oldVal) => {
console.log(newVal, oldVal);
},
{ immediate: true, deep: true } //deep配置有效
);
// 监听reactive定义的数据的多个属性
watch(
[() => person.job, () => person.job],
(newVal, oldVal) => {
console.log(newVal, oldVal);
},
{ immediate: true, deep: true }
);
watchEffect默认开启深度监听和立即执行
watchEffect 不用说明监听的是什么,只要他的回调函数用到了什么属性,就监听什么属性watchEffect(() => {
const x = sum;
const y = msg;
console.log("触发了监听");
});
| 选项式 API | Hook inside setup |
|---|---|
| beforeCreate | Not needed* |
| created | Not needed* |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount | onBeforeUnmount |
| unmounted | onUnmounted |
| errorCaptured | onErrorCaptured |
| renderTracked | onRenderTracked |
| renderTriggered | onRenderTriggered |
| activated | onActivated |
| deactivated | onDeactivated |
export default {
setup() {
// mounted
onMounted(() => {
console.log("Component is mounted!");
});
}
};
<template>
<div>
{{ list }}
</div>
</template>
<script setup lang="ts">
// 模拟http请求
const httpRequest = () => {
return new Promise((resolve) => {
setTimeout(() => {
const arr = [1, 2, 3, 4];
resolve(arr);
}, 3000);
});
};
// 在setup最外层使用await不需要用async
const list = await httpRequest();
</script>
<template>
<div class="bg-slate-50 h-5">
<!-- 使用Suspense包裹异步组件,组件A加载完才会显示 -->
<Suspense>
<!-- 组件加载成功后显示 -->
<template #default> <A></A></template>
<!-- 在A组件加载中时显示 -->
<template #fallback>
<div>loading...</div>
</template>
</Suspense>
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from "vue";
// 将组件A以异步组件的方式导入
const A = defineAsyncComponent(() => import("./components/A.vue"));
</script>
to 的值必须是有效的查询选择器或 HTMLElement (如果在浏览器环境中使用)。指定将在其中移动 <teleport> 内容的目标元素<!-- 正确 -->
<teleport to="#some-id" />
<teleport to=".some-class" />
<teleport to="[data-teleport]" />
<!-- 错误 -->
<teleport to="h1" />
<teleport to="some-string" />
<!-- 将teleport内的东西传送到id为popup的元素下 -->
<teleport to="#popup" >
<video src="./my-movie.mp4">
</teleport>
<keep-alive> 内被切换时,它的 mounted 和 unmounted 生命周期钩子不会被调用,取而代之的是 activated 和 deactivated。(这会运用在 <keep-alive> 的直接子节点及其所有子孙节点。)主要用于保留组件状态或避免重新渲染。<!-- 基本 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>
<!-- 多个条件判断的子组件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>
<!-- 和 `<transition>` 一起使用 -->
<transition>
<keep-alive>
<component :is="view"></component>
</keep-alive>
</transition>
<template>
<button @click="flag = !flag">切换按钮</button>
<transition
enter-active-class="animate__animated animate__rubberBand"
leave-active-class="animate__animated animate__rubberBand"
>
<div class="w-20 h-20 bg-red-200" v-show="flag"></div>
</transition>
</template>
<transition-group> 提供了多个元素/组件的过渡效果。默认情况下,它不会渲染一个 DOM 元素包裹器,但是可以通过 tag attribute 来定义。注意,每个 <transition-group> 的子节点必须有独立的 key,动画才能正常工作。当一个子节点被更新,从屏幕上的位置发生变化,这个子节点就会产生动画效果。<transition-group
tag="ul"
enter-active-class="animate__animated animate__rubberBand"
leave-active-class="animate__animated animate__rubberBand"
>
<li v-for="item in items" :key="item.id">
{{ item.text }}
</li>
</transition-group>
provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。
祖先组件中:<script setup lang="ts">
import { provide, ref } from "vue";
let num = ref<number>(1);
// 为xxx提供一个响应式数据
provide("xxx", num);
</script>
<script setup lang="ts">
import { inject, Ref } from "vue";
// 将数据xxx注入,并赋值给data
const data = inject("xxx");
// 点击孙组件的按钮,改变祖先组件的数据
const add = () => {
(data as Ref<number>).value++;
};
</script>
没有Prototype 属性 使用 app.config.globalProperties 代替 然后去定义变量和函数// Vue 2
Vue.prototype.$http = () => {};
// Vue3
const app = createApp({});
app.config.globalProperties.$http = () => {};
// ts中需要类型声明
declare module "@vue/runtime-core" {
export interface ComponentCustomProperties {
$hello: () => string;
}
}
// 挂载方法
app.config.globalProperties.$hello = () => {
return "hello";
};
<template>
<div>{{ $hello() }}</div>
</template>
<script lang="ts" setup>
import { getCurrentInstance, ComponentInternalInstance } from "vue";
//getCurrentInstance获取的是当前vue实例
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
console.log(proxy?.$hello());
</script>
<script setup><script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。相比于普通的 <script> 语法,它具有更多优势:<script> 代码块上:<script setup>
console.log("hello script setup");
</script>
<script setup>中的代码会在每次组件实例被创建的时候执行。
<script setup> 声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用:<script setup>
// 变量
const msg = "Hello!";
// 函数
function log() {
console.log(msg);
}
</script>
<template>
<div @click="log">{{ msg }}</div>
</template>
<script setup>
import { capitalize } from "./helpers";
</script>
<template>
<div>{{ capitalize("hello") }}</div>
</template>
APIs来创建。和从 setup() 函数中返回值一样,ref 值在模板中使用的时候会自动解包:<script setup>
import { ref } from "vue";
const count = ref(0);
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
<script setup> 范围里的值也能被直接作为自定义组件的标签名使用:<script setup>
import MyComponent from "./MyComponent.vue";
</script>
<template>
<MyComponent />
</template>
MyComponent 看做被一个变量所引用。如果你使用过 JSX,在这里的使用它的心智模型是一样的。其 kebab-case 格式的 <my-component>同样能在模板中使用。不过,我们强烈建议使用 PascalCase 格式以保持一致性。同时也有助于区分原生的自定义元素。<script setup> 中要使用动态组件的时候,就应该使用动态的 :is 来绑定:<script setup>
import Foo from "./Foo.vue";
import Bar from "./Bar.vue";
</script>
<template>
<component :is="Foo" />
<component :is="someCondition ? Foo : Bar" />
</template>
请注意组件是如何在三元表达式中被当做变量使用的。
FooBar.vue 的组件可以在其模板中用 <FooBar/> 引用它自己。请注意这种方式相比于 import 导入的组件优先级更低。如果有命名的 import 导入和组件的推断名冲突了,可以使用 import 别名导入:import { FooBar as FooBarChild } from "./components";
<Foo.Bar> 来引用嵌套在对象属性中的组件。这在需要从单个文件中导入多个组件的时候非常有用:<script setup>
import * as Form from "./form-components";
</script>
<template>
<Form.Input>
<Form.Label>label</Form.Label>
</Form.Input>
</template>
vNameOfDirective 的形式来命名本地自定义指令,以使得它们可以直接在模板中使用。do'c<script setup>
<template>
<div>
<div v-BackGround="{ background: 'red' }">1111</div>
</div>
</template>
<script setup lang="ts">
import { Directive, DirectiveBinding } from 'vue';
const vBackGround: Directive = {
// el是指令所在的html元素,binding是指令传递的值
mounted(el: HTMLElement, binding: DirectiveBinding) {
el.style.background = binding.value.background
},
updated() { },
unmounted() { }
}
</script>
<script lang="ts">
// 你可能想在 mounted 和 updated 时触发相同行为,而不关心其他的钩子函数,那么你可以通过将这个回调函数传递给指令来实现
const vBackGround2: Directive = (
el: HTMLElement,
binding: DirectiveBinding
) => {
el.style.background = binding.value.background;
};
</script>
<script setup> 中必须使用 defineProps 和 defineEmits API 来声明 props 和 emits ,它们具备完整的类型推断并且在 <script setup> 中是直接可用的:<script setup>
const props = defineProps({
foo: String
});
const emit = defineEmits(["change", "delete"]);
// setup code
</script>
defineProps 和 defineEmits 都是只在 <script setup> 中才能使用的编译器宏。他们不需要导入且会随着 <script setup> 处理过程一同被编译掉。defineProps 接收与 props 选项相同的值,defineEmits 也接收 emits 选项相同的值。defineProps 和 defineEmits 在选项传入后,会提供恰当的类型推断。defineProps 和 defineEmits 的选项会从 setup 中提升到模块的范围。因此,传入的选项不能引用在 setup 范围中声明的局部变量。这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块范围内。如果使用了 Typescript,使用纯类型声明来声明 prop 和 emits 也是可以的。
<script setup> 的组件是默认关闭的,也即通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。为了在 <script setup> 组件中明确要暴露出去的属性,使用 defineExpose 编译器宏:<script setup>
import { ref } from "vue";
const a = 1;
const b = ref(2);
defineExpose({
a,
b
});
</script>
{ a: number, b: number } (ref 会和在普通实例中一样被自动解包)<script setup> 使用 slots 和 attrs 的情况应该是很罕见的,因为可以在模板中通过 $slots 和 $attrs 来访问它们。在你的确需要使用它们的罕见场景中,可以分别用 useSlots 和 useAttrs 两个辅助函数:<script setup>
import { useSlots, useAttrs } from "vue";
const slots = useSlots();
const attrs = useAttrs();
</script>
useSlots 和 useAttrs 是真实的运行时函数,它会返回与 setupContext.slots和 setupContext.attrs 等价的值,同样也能在普通的组合式 API 中使用。<script> 一起使用<script setup> 可以和普通的 <script> 一起使用。普通的 <script> 在有这些需要的情况下或许会被使用到:<script setup> 声明的选项,例如 inheritAttrs 或通过插件启用的自定义的选项。<script>
// 普通 <script>, 在模块范围下执行(只执行一次)
runSideEffectOnce();
// 声明额外的选项
export default {
inheritAttrs: false,
customOptions: {}
};
</script>
<script setup>
// 在 setup() 作用域中执行 (对每个实例皆如此)
</script>
render 函数。请使用一个普通的 <script> 结合 setup 选项来代替。<script setup> 中可以使用顶层 await。结果代码会被编译成 async setup():<script setup>
const post = await fetch(`/api/post/1`).then((r) => r.json());
</script>
await 的表达式会自动编译成在 await 之后保留当前组件实例上下文的格式。async setup() 必须与 Suspense组合使用,Suspense 目前还是处于实验阶段的特性。defineProps 和 defineEmits 来声明:const props = defineProps<{
foo: string;
bar?: number;
}>();
const emit = defineEmits<{
(e: "change", id: number): void;
(e: "update", value: string): void;
}>();
defineProps 或 defineEmits 只能是要么使用运行时声明,要么使用类型声明。同时使用两种声明方式会导致编译报错。foo: string 类型中推断出 foo: String。如果类型是对导入类型的引用,这里的推断结果会是 foo: null (与 any 类型相等),因为编译器没有外部文件的信息。['foo', 'bar'])。defineProps 声明的不足之处在于,它没有可以给 props 提供默认值的方式。为了解决这个问题,提供了 withDefaults 编译器宏:interface Props {
msg?: string;
labels?: string[];
}
const props = withDefaults(defineProps<Props>(), {
msg: "hello",
labels: () => ["one", "two"]
});
default 选项。此外,withDefaults 辅助函数提供了对默认值的类型检查,并确保返回的 props 的类型删除了已声明默认值的属性的可选标志。