组件命名
官方建议我们使用大驼峰命名法注册组件,因为:
- 驼峰命名是合法的JS标识符,导入、注册组件会更简单,IDE会自动完成。
使Vue组件比原生HTML元素更醒目。
例如,使用组件Pascalcase.vue
<script setup>import Pascalcase from './Pascalcase.vue'</script><template><h1>使用驼峰命名组件!</h1><Pascalcase /></template>
属性定义
使用更细节的方式定义属性:
<script setup>// 不推荐// const props = defineProps(['foo'])// 推荐const props = defineProps({foo: {type: String,default: ''}})console.log(props.foo)</script>
事件定义
自定义事件的名称会被自动做转换,我们通常使用驼峰做事件名,但监听时需要转换为肉串方式。
例如:
<!-- MyComponent --><button @click="$emit('someEvent')">click me</button><MyComponent @some-event="callback" />
script setup中定义
<script setup lang="ts">const emit = defineEmits(['someEvent'])emit('someEvent')</script>
透传定义
在vue3中,那些没有明确在组件props和emits中声明的特性或事件监听器称为透传特性,以前叫非属性特性。比如class,style和id特性。当组件只有单根时,透传特性自动被添加到根元素上作为其特性。例如:
<MyButton class="large" />
<button class="large">click me</button>
如果不想自动继承特性,可以使用inheritAttrs: false禁止
<script>// use normal <script> to declare optionsexport default {inheritAttrs: false}</script><script setup>// ...setup logic</script>
访问透传特性
<script setup>import { useAttrs } from 'vue'const attrs = useAttrs()</script>
插槽
如果要传递模板内容给子组件,我们使用插槽。
Vue3中插槽的变化是移除scopeSlots,只需要访问slots对象,且包括default插槽都是函数形式。
<script setup>import { useSlots } from 'vue'const slots = useSlots()const defaultContent = slots.default()</script>
提供/注入 provide/inject
隔代传参时,使用provide/inject这组API。
<script setup>import { provide } from 'vue'// 祖代提供数据provide(/* key */ 'message', /* value */ 'hello!')</script>
import { inject } from 'vue'export default {setup() {// 后代注入数据inject(/* key */ 'message', /* value */ 'hello!')}}
Composbles
利用Composition API封装的可重用状态逻辑称为composables。
约定composables函数命名时加上use前缀,例如:
// mouse.jsimport { ref, onMounted, onUnmounted } from 'vue'// by convention, composable function names start with "use"export function useMouse() {// state encapsulated and managed by the composableconst x = ref(0)const y = ref(0)// a composable can update its managed state over time.function update(event) {x.value = event.pageXy.value = event.pageY}// a composable can also hook into its owner component's// lifecycle to setup and teardown side effects.onMounted(() => window.addEventListener('mousemove', update))onUnmounted(() => window.removeEventListener('mousemove', update))// expose managed state as return valuereturn { x, y }}
传入refs参数代替原始值
import { unref } from 'vue'function useFeature(maybeRef) {// if maybeRef is indeed a ref, its .value will be returned// otherwise, maybeRef is returned as-isconst value = unref(maybeRef)}
总是返回包含refs的对象
// x and y are refsconst { x, y } = useMouse()
组件通信
props:父子通信,传入一个属性
{props: { msg: String }}
除了props选项,vue3.2还出了script setup的新写法
const props = defineProps({model: { type: Object, required: true }});props.model
$emit
现在只剩下派发事件的emit方法
this.$emit('add', good)
vue3.2还出了script setup的新写法
const emit = defineEmits(['update:model-value', 'validate'])const emit = defineEmits<{(e: "update:model-value", value: string): void;(e: "validate"): void;}>();emit("update:model-value", inp.value);
on,once, $off被移除了!
上述3个⽅法被认为不应该由vue提供,因此被移除了,可以使⽤其他库实现等效功能。
import mitt from 'mitt'const emitter = mitt()// 发送事件emitter.emit('foo', 'foooooooo')// 监听事件emitter.on('foo', msg => console.log(msg))
event bus
vue2时代我们常常使用一个vue实例来做事件总线,现在不行了:
所以我们就使用上面的mitt方案来代替就好了!
// vue2时代,现在因为没有$on不行了Vue.prototype.$bus = new Vue()// 组件里面this.$bus.$emit('xxx')// 其他组件this.$bus.$on('xxx', () => {})
vuex/pinia
vuex 4.x中的composition api写法:
const store = useStore()store.commit('add')store.dispatch('add')
parent/root
兄弟组件之间通信可通过共同祖辈搭桥。
// brother1this.$parent.$on('foo', handle)// brother2this.$parent.$emit('foo')
$children
vue3中移除了该选项,官方建议我们访问子组件时使用$refs
$refs
获取指定元素或组件
// parent<HelloWorld ref="hw"/>mounted() {this.$refs.hw.xx = 'xxx'}
provide/inject
能够实现祖先和后代之间传值
在composition中有对应的api
// ancestorprovide() {return {foo: 'foo'}}// descendantinject: ['foo']
import {provide, inject} from vue// 提供数据provide(key, value)// 注入数据inject(key)
$attrs
attrs 会包含那些没有声明的组件特性,vue3中会移除了listeners,只剩下$attrs
// child:并未在props中声明foo<p>{{$attrs.foo}}</p>// parent<HelloWorld foo="foo"/>
通过 v-bind=”$attrs” 透传到内部组件——在创建⾼级别的组件时⾮常有⽤:
// 给Grandson隔代传值,parent.vue<Child2 msg="lalala" @some-event="onSomeEvent"></Child2>// Child做展开<Grandson v-bind="$attrs"></Grandson>// Grandson使⽤<div @click="$emit('some-event', 'msg from grandson')">{{msg}}</div>
插槽
插槽语法是Vue 实现的内容分发 API,⽤于复合组件开发。该技术在通⽤组件库开发中有⼤量应⽤。
匿名插槽
slot称为匿名插槽,作为占位符存在,将来被替换为传入内容。
// comp1<div><slot></slot></div>// parent<comp>hello</comp>
具名插槽
slot加上name就称为具名插槽,可以将内容分发到⼦组件指定位置
// comp2<div> <slot></slot> <slot name="content"></slot></div>// parent<Comp2><!-- 默认插槽⽤default做参数 --><template v-slot:default>具名插槽</template><!-- 具名插槽⽤插槽名做参数 --><template v-slot:content>内容...</template></Comp2>
作⽤域插槽
分发内容要⽤到⼦组件中的数据
// comp3<div> <slot :foo="foo"></slot></div>// parent<Comp3><!-- 把v-slot的值指定为作⽤域上下⽂对象 --><template v-slot:default="slotProps">来⾃⼦组件数据:{{slotProps.foo}}</template></Comp3>
Vue3中组件相关api变化总结
global-api改为实例⽅法
全局静态⽅法引发⼀些问题,vue3将global-api改为app实例⽅法
// vue2中// Vue.component()// vue3中const app = createApp({}).component('comp', {template: '<div>i am comp</div>'}).mount('#app')
移除.sync,统⼀为v-model
以前.sync和v-model功能有重叠,容易混淆,vue3做了统⼀。
<div id="app"><comp v-model="data"></comp><comp :model-value="data" @update:model-value="onxxx"></comp><comp :data="data" @update:data="onxxx"></comp></div>
app.component('comp', {props: {modelValue},template: `<div @click="$emit('update:model-value', 'new value')">i am comp, {{modelValue}}</div>`,props: ['modelValue'],})
渲染函数api修改
不再传⼊h函数,需要我们⼿动导⼊;拍平的props结构。scopedSlots删掉了,统⼀到slots
import {h} from 'vue'export default {render() {const emit = this.$emitconst onclick = this.onclickreturn h('div', [h('div', { onClick(){ emit('update:modelValue', 'new value')} },`i am comp, ${this.modelValue}`),h('button', { onClick(){ onclick() }}, 'buy it!')])},}
组件emits选项
该选项⽤于标注⾃定义事件及其校验等。
createApp({}).component("comp", {template: `...`,// emits标明组件对外事件emits: ['buy', '...']// 还能对事件进行校验emits: {'update:modelValue': null, // 不做校验buy(p) { // 校验buy事件if (p === 'nothing') {console.warn('参数⾮法');return false} else {return true}}},})
