defineProps() 和 defineEmits()
- 内置函数,无需import导入,直接使用。
- 传入到 defineProps 和 defineEmits 的选项会从 setup 中提升到模块的范围。因此,传入的选项不能引用在 setup 范围中声明的局部变量(比如设置默认值时),但是,它可以引用导入(import)的变量,因为它们也在模块范围内。就是说props设置默认值的时候不能直接用setup里的变量,可以用import引入的数据
<script setup>
//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-推荐:弊端:不能设置默认值(使用withDefaults解决)
interface Props {data?: number[]
}
//const props = defineProps<Props>();
//或const props = defineProps<{ data?: number[] }>();
const props = withDefaults(defineProps<Props>(), { data: () => [1, 2] })const emit = defineEmits(['change', 'delete']) //声明从父组件来的事件
//将事件传递出去
emit('change', 参数);
emit('delete', 参数);
</script>
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';
import type { ComponentInternalInstance } from 'vue';
//import { getCurrentInstance, ComponentInternalInstance } from 'vue'; 我用了自动导入,不需要引getCurrentInstance//方法一(常用推荐):
//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(childRef.value?.msg); //.value的方式调用子组件的数据和方法(defineExpose暴露)childRef.value?.open();console.log(proxy?.$refs); //proxy对象{childRef: Proxy(Object), divEl: div}
});
defineExpose({parentNum
})</script>
注意: 如果ref在v-for里,将会是一个数组,这里和vue2一样。使用childRef.value[0].msg
//子组件child.vue
<script setup lang="ts">
import type { ComponentInternalInstance } from 'vue'
let msg: string = '111';
const open = function() {console.log(222);}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
onMounted(() => {//标红:类型“ComponentPublicInstance”上不存在属性“parentNum”console.log('parent', proxy?.$parent?.parentNum);
})
defineExpose({msg,open
})
</script>
useAttrs()
包含了父组件传递过来的所有属性(子组件内没有被defineProps和defineEmits声明的),包括 class 和 style 以及事件(相当于vue2中相当于listeners)。在vue2中,listeners)。在vue2中,listeners)。在vue2中,attrs 是接到不到 class 和 style 的
//parent.vue
<template><childfoo="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中
Vue.prototype.$axios = xxx//使用
this.$axios
# 扩展全局属性
//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.globalPropertiesconsole.log(global.name)
console.log(proxy?.name) //会标红
</script>
异步组件
## 异步组件,路由懒加载
设置组件名称
- 在 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>
3.defineOptions
这个宏可以用来直接在 <script setup> 中声明组件选项,而不必使用单独的 <script> 块
官网解释
<script lang="ts" setup>defineOptions({name:'draft'})
</script>
4.利用插件
- 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-bind CSS 函数将 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 changeName = inject('changeName') as Fn;