vue3 基础知识点
WARNING
主要记录 vue3 的一些使用技巧
基本概念
- 顶层的绑定会被暴露给模板
<script setup>
// 导入的模块
import { capitalize } from "./helpers";
// 变量
const msg = "Hello!";
// 函数
function log() {
console.log(msg);
}
</script>
<template>
<button @click="log">{{ msg }}</button>
<div>{{ capitalize("hello") }}</div>
</template>
所以,在使用 setup 语时,对于不需要暴露给模板使用的变量,比如用于计算、比较等场景的临时变量或和业务逻辑无关的变量,需要注意下变量所在的作用域是否正确
- 组件使用
<script setup>
import Foo from "./Foo.vue";
import Bar from "./Bar.vue";
// 命名空间组件,form-components 对外导出 input 和 label 两个组件
import * as Form from "./form-components";
</script>
<template>
<!-- 常规组件使用 -->
<Foo />
<!-- 动态组件使用 -->
<component :is="someCondition ? Foo : Bar" />
<!-- 命名空间组件 -->
<Form.Input>
<Form.Label>label</Form.Label>
</Form.Input>
</template>
- 顶层 await
async setup() 必须与 Suspense 组合使用
<!-- vue 3.0 -->
<script>
export default defineComponent({
async setup() {
const data = await fetch("xxxx.xxxx.xxxx");
return {};
},
});
</script>
<!-- vue 3.2 -->
<script setup>
const post = await fetch(`/api/post/1`).then((r) => r.json());
</script>
props 使用
vue3.0
<script lang="ts">
import { ExtractPropTypes, PropType, SetupContext } from "vue";
interface IObj {
name: string;
age: number;
}
// 将 props 定义为常量
const Props = {
str: {
type: String,
default: "",
required: true,
},
obj: {
type: Object as PropType<IObj>,
default: () => {},
},
} as const; // as const 让深层结构的 props 依旧只保留只读属性
// ExtractPropTypes: 类型反推, 根据上面定义的 props 常量反推出常量的类型
const IProps = ExtractPropTypes<typeof Props>;
export default defineComponent({
props: Props,
setup(props: IProps, ctx: SetupContext) {
// 。。。。。
},
});
</script>
vue3.2
<script setup lang="ts">
import { withDefault } from "vue";
interface IProps {
str: string;
obj: {
name: string;
age: number;
};
}
// 不需要默认值
const props = defineProps({
foo: String,
});
// 需要默认值
const props = withDefault(defineProps<IProps>(), {
str: "",
obj: {},
});
</script>
vue3.2 响应式语法糖
<script setup lang="ts">
interface Props {
msg: string
count?: number
foo?: string
}
const {
msg,
// 默认值正常可用
count = 1,
// 解构时命别名也可用
// 这里我们就将 `props.foo` 命别名为 `bar`
foo: bar
} = defineProps<Props>()
watchEffect(() => {
// 会在 props 变化时打印
console.log(msg, count, bar)
})
</script>
emit 使用
vue3.0
<template>
<span @click="update"></span>
<span @click="update2('1')"></span>
</template>
<script lang="ts">
export default defineComponent({
props: Props,
emits: ["update"],
setup(props: IProps, ctx: SetupContext) {
const update = () => {
ctx.emit("update");
};
const update2 = () => {
ctx.emit("update", id);
};
return {
update,
};
},
});
</script>
vue3.2
<template>
<span @click="update"></span>
<span @click="update2('1')"></span>
</template>
<script setup lang="ts">
const emit = defineEmit<{
(e: "update"): void;
(e: "update2", value: string): void;
}>();
const update = () => {
emit("update");
};
const update2 = (id: string): void => {
emit("update1", id);
};
</script>
slot 使用
子组件模板:
<!-- Son -->
<template>
<div>
<!-- 默认插槽 -->
<slot></slot>
<!--具名插槽 -->
<slot name="name"></slot>
<slot name="age"></slot>
</div>
</template>
SFC 写法
<!-- Father -->
<template>
<Son>
<template #name>name</template>
<template #age>age</template>
<template>xxxxxxxxxxxx</template>
</Son>
</template>
jsx 写法
// 默认插槽
<Son>{() => 'hello'}</Son>
// 具名插槽
<Son>
{{
default: () => 'default slot',
name: () => <div>name</div>,
age: () => [<span>one</span>, <span>two</span>]
}}
</Son>
vue3 类与样式的绑定
绑定对象
<template>
<!-- 不推荐在元素里直接使用 -->
<div :class="{ active: isActive, 'text-danger': hasError }"></div>
</template>
<script setup>
import { ref } from "vue";
const isActive = ref(true);
const error = ref(null);
</script>
<template>
<!-- 推荐 -->
<div :class="classObject"></div>
</template>
<script setup>
import { computed } from "vue";
// 利用 计算属性
const classObject = computed(() => ({
active: isActive.value && !error.value,
"text-danger": error.value && error.value.type === "fatal",
}));
</script>
绑定数组
<template>
<!-- 直接使用 -->
<div :class="[activeClass, errorClass]"></div>
<!-- 需要条件判断 -->
<!-- 方式一:不推荐 -->
<div :class="[isActive ? activeClass : '', errorClass]"></div>
<!-- 方式二: 推荐 -->
<div :class="[{ active: isActive }, errorClass]"></div>
</template>
<script setup>
import { ref } from "vue";
const activeClass = ref("active");
const errorClass = ref("text-danger");
</script>
vue3 条件渲染
当 v-if 和 v-for 同时存在于一个元素上的时候,v-if 会首先被执行
SFC
<template>
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>C</div>
</template>
jsx
<div>{type === "A" ? "A" : type === "B" ? "B" : "C"}</div>
vue 事件处理
WARNING
使用修饰符时需要注意调用顺序
,因为相关代码是以相同的顺序生成的。
因此使用 @click.prevent.self 会阻止元素内的所有点击事件而 @click.self.prevent 则只会阻止对元素本身的点击事件
。
<template>
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>
<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>
<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>
<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>
</template>
WARNING
.capture,.once,和 .passive 修饰符与原生 addEventListener 事件相同:
<template>
<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>
<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>
<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<!-- passive: 一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能 -->
<div @scroll.passive="onScroll">...</div>
</template>
注意
请勿同时使用 .passive 和 .prevent
,因为 .prevent 会被忽略并且你的浏览器可能会抛出警告。请记住,.passive 是向浏览器表明你不想阻止事件的默认行为。并且如果你这样做,可能在浏览器中收到一个警告。
vue3 生命周期
当调用 onMounted 时,Vue 会自动将注册的回调函数与当前活动组件实例相关联。
这就要求这些钩子在组件设置时同步注册。例如请不要这样做:
setTimeout(() => {
onMounted(() => {
// 这将不会正常工作
});
}, 100);
请注意,这并不意味着对 onMounted 的调用必须放在 setup() 或 <script setup>
内的词法环境下。 onMounted()
也可以在一个外部函数中调用,只要调用栈是同步的,且最终起源自 setup()。
自定义指令
vue3.2
在
<script setup>
中,任何以v
开头的camelCase 格式的变量
都会可以被用作一个自定义指令
<script setup>
const vFocus = {
mounted: (el: HtmlInputElement) => el.focus,
};
</script>
<template>
<input v-focus />
</template>
vue3.0
<script>
export default {
setup() {
/*...*/
},
directives: {
// 在模板中启用 v-focus
focus: {
/* ... */
},
},
};
</script>
<template>
<input v-focus />
</template>
全局自定义指令
const app = createApp({});
// 使 v-focus 在所有组件中都可用
app.directive("focus", {
/* ... */
});
setup 语法下的 directive 不需要显示注册, 但命名规则必须在 v{指令名}Directive, 从其他文件导入的指令如不符合命名规则,则需要重命名
<script setup>
import { myDirective as vMyDirective } from './MyDirective.js'
const vFocusDirective = {
beforeMount: (el) => {
el.focus()
}
}
</script>
<template>
<input v-focus-directive></input>
<div v-my-directive></div>
</template>
插件
官方描述: 插件是一种能为 Vue 添加全局功能的工具代码。它可以是一个拥有
install()
方法的对象,或者就简单地只是一个函数,它自己就是安装函数。
定义一个插件(对象),这个插件必须要有一个 install
方法
// plugin/myPlugin/index.ts
const myPlugin = {
install(app, options) {
// 配置此应用
},
};
export default myPlugin;
使用插件
import { createApp } from "vue";
import App from "./App.vue";
import myPlugin from "./plugin/myPlugin";
const app = createApp(App);
app.use(myPlugin);
// 最常用的插件就是 vue-router 和 Vuex
app.use(router)
app.use(vuex)
getCurrentInstance 获取 vue 实例
vue3 的 setup 中没有 this ,所以无法在 setup 中直接获取到当前组件的实例,所以 vue3 给我们提供了 getCurrentInstance 这个方法用于获取组件实例
// 原文地址: https://juejin.cn/post/7080800488915992589
import { getCurrentInstance } from "vue";
// 获取当前组件实例
const instance = getCurrentInstance();
// 获取当前组件的上下文,下面两种方式都能获取到组件的上下文
// 方式一,这种方式只能在开发环境下使用,生产环境下的ctx将访问不到
const { ctx } = getCurrentInstance();
// 方式二,此方法在开发环境以及生产环境下都能放到组件上下文对象(推荐)
const { proxy } = getCurrentInstance();
【备注】:
- vue 官方不推荐在 vue3 项目开发中使用
getCurrentInstance
, 除非自己非常清楚这个 api 会造成的影响 - api 一般是在 ui 组件库等开源项目时才会使用、
getCurrentInstance.proxy
下面挂载的方法:
// ctx 中包含了组件中由ref和reactive创建的响应式数据对象,以及以下对象及方法;
proxy.$attrs;
proxy.$data;
proxy.$el;
proxy.$emit;
proxy.$forceUpdate;
proxy.$nextTick;
proxy.$options;
proxy.$parent;
proxy.$props;
proxy.$refs;
proxy.$root;
proxy.$slots;
proxy.$watch;
v-model 的新特性
dahkjshd
v-memo
当搭配 v-for 使用 v-memo,确保它两是用在同一个元素中. v-memo 不能用在 v-for 内
<template>
<div v-for="item in list" v-memo="[item.id === selected]" :key="item.id">
<p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
<p>...more child nodes</p>
</div>
</template>
defineExpose
在 vue 中,组件可以通过 ref / $parent / $children 来获取或调用其他相关组件的方法或变量,但在 setup 语法中是不允许的,除非组件主动对外暴露自身的属性,defineExpose 就是干这活的
<script setup>
import { ref } from "vue";
const a = 1;
const b = ref(2);
const c = () => {};
// defineExpose 里面的对象外界可以通过 ref / $parent / $children 等方式访问到
defineExpose({
a,
b,
c,
});
</script>
useSlot 和 useAttrs
官方原话:useSlots 和 useAttrs 是真实的运行时函数,它的返回与 setupContext.slots 和 setupContext.attrs 等价。它们同样也能在普通的组合式 API 中使用
<script setup>
import { useSlots, useAttrs } from "vue";
const slots = useSlots();
const attrs = useAttrs();
</script>
<!-- 等价于 -->
<script>
export default defineComponent({
setup(props, { slots, attrs }) {
return {};
},
});
</script>