放了几篇我觉得还不错的文章,兄弟们可以参考,有遗漏的地方,基本官网都有说明
defineProps() 和 defineEmits()
-
内置函数,无需import导入,直接使用。
-
传入到 defineProps 和 defineEmits 的选项会从 setup 中提升到模块的范围。因此,传入的选项不能引用在 setup 范围中声明的局部变量(比如设置默认值时),但是,它可以引用导入(import)的变量,因为它们也在模块范围内。就是说
props设置默认值的时候不能直接用setup里的变量,可以用import引入的数据<script setup> import type { PropType } from 'vue'; //props是响应式的不能解构 //方法1:不能设置默认值(使用withDefaults解决) const props = defineProps({ foo?: String, id: [Number, String], onEvent: Function, //Function类型 metadata: null }) //方法2 const props = defineProps({ foo: { type: String, required: true, default: '默认值' }, bar: Number }) //方法3-对于运行时声明,我们可以使用 `PropType` 工具类型 interface Book { data?: number[] } const props = defineProps({ book: Object as PropType<Book> }) //方法4-基于类型的声明。推荐:弊端:不能设置默认值(使用withDefaults解决) // 通过泛型参数来定义 props 的类型通常更直接, 这被称之为“基于类型的声明”。编译器会尽可能地尝试根据类型参数推导出等价的运行时选项。 interface Props { data?: number[] } const props = defineProps<Props>(); //设置默认值 const props = withDefaults(defineProps<Props>(), { data: () => [1, 2] }) //`Props` 也可以从从另一个源文件中导入 //import type { Props } from './foo' </script><script setup lang="ts"> // 运行时 const emit = defineEmits(['change', 'update']) // 基于选项 const emit = defineEmits({ change: (id: number) => { // 返回 `true` 或 `false` // 表明验证通过或失败 }, update: (value: string) => { // 返回 `true` 或 `false` // 表明验证通过或失败 } }) // 基于类型 const emit = defineEmits<{ (e: 'change', id: number): void (e: 'update', value: string): void }>() // 3.3+: 可选的、更简洁的语法 const emit = defineEmits<{ change: [id: number] update: [value: string] }>() </script> //将事件传递出去 emit('change', { id: 1}); emit('update', { value: '1111'});
defineExpose()
- 内置函数,无需import导入,直接使用。Vue3中的setup默认是封闭的,如果想要使用ref或者
$parent获取到的组件的的变量或函数,被访问的组件须使用defineExpose将属性和方法暴露出去。使用方式参考获取DOM
获取DOM
- 官网Api
- 注意:Vue3中,移除了 $children 属性
- ref
- $parent
- $root
<!--父组件parent.vue -->
<template>
<child ref="childRef"></child>
<div ref="divEl"></div>
</template>
<script setup lang="ts">
import child from './components/child.vue';
// 我用了自动导入,不需要引getCurrentInstance
import { getCurrentInstance, type ComponentInternalInstance, unref } from 'vue';
//方法一(常用推荐):
//typeof P 是获取到类,InstanceType<类>是拿到类的实例,一个是类一个是实例不一样
//为了获取组件的类型,我们首先需要通过 `typeof` 得到其类型,再使用 TypeScript 内置的 `InstanceType` 工具类型来获取其实例类型:
//获取组件。这个变量名和 DOM 上的 ref 属性必须同名,会自动形成绑定。变量名不能和组件名同名,即chilRef不能命名为child
let childRef = ref<InstanceType<typeof child> | null>(null);
//获取dom
let divEl = ref<HTMLDivElement | null>(null)
//方法二:(不推荐)
//这样写会标红:类型“ComponentInternalInstance | null”上不存在属性“proxy”。使用类型断言或非空断言
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
//或
const { proxy } = getCurrentInstance()!;
let parentNum = ref(1)
onMounted(() => {
console.log(divEl.value); //直接拿到dom本身
//或者
console.log(unref(divEl))
console.log(childRef.value?.msg); //.value的方式调用子组件的数据和方法(defineExpose暴露)
childRef.value?.open();
console.log(proxy?.$refs); //proxy对象{childRef: Proxy(Object), divEl: div}
});
defineExpose({
parentNum
})
</script>
//子组件child.vue
<script setup lang="ts">
import type { ComponentInternalInstance } from 'vue'
let msg: string = '111';
const open = function() {
console.log(222);
}
const { proxy } = getCurrentInstance()!;
onMounted(() => {
//标红:类型“ComponentPublicInstance”上不存在属性“parentNum”
console.log('parent', proxy?.$parent?.parentNum);
})
defineExpose({
msg,
open
})
</script>
注意: 如果ref在v-for里,将会是一个数组,这里和vue2一样。使用
childRef.value[0].msg\
<!-- v-for中使用ref -->
<template>
<a-timeline>
<a-timeline-item ref="itemRefs" v-for="item in dataList" :key="item.value">
</a-timeline-item>
</a-timeline>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
const itemRefs = ref<any[]>([]);
onMounted(() => {
//第一个a-timeline-item的dom
console.log(itemRefs.value[0]?.$el)
//第二个timeline-item的dom
console.log(itemRefs.value[1]?.$el)
})
</script>
useAttrs()
- 包含了父组件传递过来的所有属性(子组件内没有被
defineProps和defineEmits声明的),包括class和style以及事件(相当于vue2中相当于attrs 是接到不到 class,style, 事件
//parent.vue
<template>
<child
foo="222"
foo2="333"
class="child"
:style="{}"
@test="handleTest"
@test2="handleTest"
></child>
</template>
<script setup lang="ts">
function handleTest() {}
</script>
//child.vue
<template>
<div></div>
</template>
<script setup lang="ts">
const props = defineProps(['foo2'])
const emits = defineEmits(['test2'])
console.log(props); //{foo2: '333'}
const attrs = useAttrs()
console.log(attrs); // {foo: '222', class: 'child', style: {…}, onTest: f handleTest()}
</script>
全局注册
-
vue2
//vue2中 Vue.prototype.$axios = xxx //使用 this.$axios -
vue3 扩展全局属性
//main.ts import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) app.config.globalProperties.name = '猪八戒' app.mount('#app')<script setup lang="ts"> import type { ComponentInternalInstance } from 'vue'; //这两都很少用 // proxy 就是当前组件实例,可以理解为组件级别的 this,没有全局的、路由、状态管理之类的 const { proxy, appContext } = getCurrentInstance() as ComponentInternalInstance; //global 就是全局实例 const global = appContext.config.globalProperties console.log(global.name) console.log(proxy?.name) //会标红 </script>
异步组件
设置组件名称
zhuanlan.zhihu.com/p/481640259
-
在 3.2.34 或以上的版本中,使用
<script setup>的单文件组件会自动根据文件名生成对应的name选项,即使是在配合<KeepAlive>使用时也无需再手动声明。不写index.vue,写一个具象的组件名称 -
再加个平级的script标签(注意:两个script标签使用的语言要同步,lang="ts")
<script lang="ts" setup> </script> <script lang="ts"> export default { name: 'draft', inheritAttrs: false, customOptions: {}, }; </script> -
defineOptions 这个宏可以用来直接在
<script setup>中声明组件选项,而不必使用单独的<script>块
官网解释<script lang="ts" setup> defineOptions({ name:'draft' }) </script> -
利用插件
- vite-plugin-vue-setup-extend-plus(推荐)
- vite-plugin-vue-setup-extend(断点调试存在问题,未修复sourcemap is broken
- unplugin-vue-define-options error in production:defineOptions is not defined
//会报错[vueSetupExtend不是一个函数],删掉package.json 中的 type: module即可 //vite.config.ts import { defineConfig, Plugin } from 'vite' import vue from '@vitejs/plugin-vue' import vueSetupExtend from 'vite-plugin-vue-setup-extend-plus' export default defineConfig({ plugins: [vue(), vueSetupExtend()], })//SFC <template> <div>hello world {{ a }}</div> </template> <script lang="ts" setup name="App" inheritAttrs="false"> const a = 1 </script>
watch的使用
当我们需要在数据变化时执行一些“副作用”:如更改 DOM、执行异步操作(发起网络请求等),我们可以使用
watch函数:
-
监听基本数据类型
const name = ref('猪八戒') // 监听 ref 属性 watch(name, (newValue, oldValue) => { }) -
监听对象
const obj = reactive({ a: 1, b: 2, c: { d: 1, e: 2 }, f: [] }) //法一:深层次,当直接侦听一个响应式对象时,侦听器会自动启用深层模式: //可以监听到obj.f = [1]和obj.c.d ++ watch(obj, (newValue, oldValue) => { }) //法二:深层次,必须写deep: true,不然浅层的也监听不到 watch(() => obj, (newValue, oldValue) => { }, { deep: true }) //法三:浅层, 监听不到obj.f = [1]和obj.c.d ++ watch(() => { ...obj }, (newValue, oldValue) => { }) -
监听对象的某个属性
watch(() => obj.a, (newValue, oldValue) => { }) //如果是对象的属性是引用数据类型,必须加deep: true才能监听到 watch(() => obj.f, (newValue, oldValue) => { }, { deep: true }) -
监听多个数据
//法一: watch([() => obj.a, name], ([newA, newName], [oldA, oldName]) => { }); //法二: watch(() => [obj.a, obj.b], (newValue, oldValue) => { })
watchEffect
watch默认是懒执行的:仅当数据源变化时,才会执行回调。可以传入immediate: true选项来强制侦听器的回调立即执行watchEffect的回调会立即执行(依赖收集),不需要指定immediate: true。在执行期间,它会在同步执行过程中,自动追踪所有能访问到的响应式属性。watch:手动指定依赖> >watchEffect:自动收集依赖(注意: 依赖太多各种坑)watchEffect无法访问侦听数据的新值和旧值
//侦听器的回调使用与源完全相同的响应式状态是很常见的。例如下面的代码,在每当 `todoId` 的引用发生变化时使用侦听器来加载一个远程资源
const todoId = ref(1)
const data = ref(null)
watch(todoId, async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
}, { immediate: true })
//使用watchEffect简化代码,我们不再需要明确传递 `todoId` 作为源值.
watchEffect(async () => {
const response = await fetch( `https://jsonplaceholder.typicode.com/todos/${todoId.value}`);
data.value = await response.json()
})
- 使用哪种随你喜欢,如果不需要使用先前值(oldValue)并且希望立即执行就用
watchEffect,可以少写一点代码。watch的自由度更高,watchEffect相当于封装了一层- 推荐在大部分时候用
watch显式的指定依赖以避免不必要的重复触发,也避免在后续代码修改或重构时不小心引入新的依赖。watchEffect适用于一些逻辑相对简单,依赖源和逻辑强相关的场景
停止监听器
- 一个关键点是,侦听器必须用同步语句创建,这时它会在宿主组件卸载时自动停止。如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。
// 它会自动停止 watchEffect(() => {}) // ...这个则不会! setTimeout(() => { watchEffect(() => {}) }, 100) //停止监听器 const unwatch = watchEffect(() => {}) // ...当该侦听器不再需要时 unwatch() //第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。 //该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。 watch(id, async (newId, oldId, onCleanup) => { const { response, cancel } = doAsyncWork(newId) // 当 `id` 变化时,`cancel` 将被调用, // 取消之前的未完成的请求 onCleanup(cancel) data.value = await response })
computed
//创建一个只读的计算属性 ref:
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误
//创建一个可写的计算属性
onst count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => { count.value = val - 1 }
})
plusOne.value = 1 console.log(count.value) // 0
计算属性的 getter 应只做计算而没有任何其他的副作用,这一点非常重要,请务必牢记。举例来说,不要在 getter 中做异步请求或者更改 DOM!
CSS
-
样式穿透:Vue3 中不支持
/deep/或者>>>写法, 支持:deep(.class)<style scoped> .a :deep(.b) { /* ... */ } </style> -
css绑定js变量(v-bind):单文件组件的
<style>标签支持使用v-bindCSS 函数将 CSS 的值链接到动态的组件状态。<script setup> const theme = { color: 'red' } </script> <template> <p>hello</p> </template> <style scoped> p { color: v-bind('theme.color'); } </style>
provie和inject
//父级
const name = ref('猪八戒');
const changeName = (newName: string) => {
name.value = newName;
};
provide('name', name);
provide('changeName', changeName); //更改name的方法
//子级孙级
const name = inject('name') as string; //使用类型断言,不然会有红色波浪线
//或
const name:string = inject('name')
const changeName = inject('changeName') as Fn;
vite中引入图片
说明: vite默认资源文件在assets文件夹打包后会把图片名加上 hash值。例如,imgUrl 在开发时会是 /img.png,在生产构建后会是 /assets/img.2d8efhg.png。行为类似于 Webpack 的 file-loader。区别在于导入既可以使用绝对公共路径(基于开发期间的项目根路径),也可以使用相对路径。
-
引入一张图片(非public文件夹)
import imageA from '@/assets/images/image_a.png' //可行 <img :src="imageA" /> //以下方式均不可,线上找不着路径 //直接引入并不会在打包的时候解析,导致开发环境可以正常引入,打包后却不能显示的问题。 //引入的文件是/assets/img.png, 实际有效文件确是 /assets/img.2d8efhg.png <img :src="@/assets/images/image_a.png" /> <img :src="/src/assets/images/image_a.png" /> //报错require is not defind,因为 require是属于 Webpack 的方法 <img :src="require('@/assets/images/image_a.png')" /> -
动态引入(非public文件夹)
//我们首先想到的是,此法不行,vite无法解析 <img :src="`/src/assets/images/${变量}.png`" /> //下面可行 <img :src="getImageUrl(变量)" /> const getImageUrl = function (name) { return new URL(`../assets/images/${url}.png`, import.meta.url).href } -
直接将文件放到public里