组件命名

官方建议我们使用大驼峰命名法注册组件,因为:

  1. 驼峰命名是合法的JS标识符,导入、注册组件会更简单,IDE会自动完成。
  2. 使Vue组件比原生HTML元素更醒目。

例如,使用组件Pascalcase.vue

  1. <script setup>
  2. import Pascalcase from './Pascalcase.vue'
  3. </script>
  4. <template>
  5. <h1>使用驼峰命名组件!</h1>
  6. <Pascalcase />
  7. </template>

属性定义

使用更细节的方式定义属性:

  1. <script setup>
  2. // 不推荐
  3. // const props = defineProps(['foo'])
  4. // 推荐
  5. const props = defineProps({
  6. foo: {
  7. type: String,
  8. default: ''
  9. }
  10. })
  11. console.log(props.foo)
  12. </script>

事件定义

自定义事件的名称会被自动做转换,我们通常使用驼峰做事件名,但监听时需要转换为肉串方式。
例如:

  1. <!-- MyComponent -->
  2. <button @click="$emit('someEvent')">click me</button>
  3. <MyComponent @some-event="callback" />

script setup中定义

  1. <script setup lang="ts">
  2. const emit = defineEmits(['someEvent'])
  3. emit('someEvent')
  4. </script>

透传定义

在vue3中,那些没有明确在组件props和emits中声明的特性或事件监听器称为透传特性,以前叫非属性特性。比如class,style和id特性。当组件只有单根时,透传特性自动被添加到根元素上作为其特性。例如:

  1. <MyButton class="large" />
  1. <button class="large">click me</button>

如果不想自动继承特性,可以使用inheritAttrs: false禁止

  1. <script>
  2. // use normal <script> to declare options
  3. export default {
  4. inheritAttrs: false
  5. }
  6. </script>
  7. <script setup>
  8. // ...setup logic
  9. </script>

访问透传特性

  1. <script setup>
  2. import { useAttrs } from 'vue'
  3. const attrs = useAttrs()
  4. </script>

插槽

如果要传递模板内容给子组件,我们使用插槽。
Vue3中插槽的变化是移除scopeSlots,只需要访问slots对象,且包括default插槽都是函数形式。

  1. <script setup>
  2. import { useSlots } from 'vue'
  3. const slots = useSlots()
  4. const defaultContent = slots.default()
  5. </script>

提供/注入 provide/inject

隔代传参时,使用provide/inject这组API。

  1. <script setup>
  2. import { provide } from 'vue'
  3. // 祖代提供数据
  4. provide(/* key */ 'message', /* value */ 'hello!')
  5. </script>
  1. import { inject } from 'vue'
  2. export default {
  3. setup() {
  4. // 后代注入数据
  5. inject(/* key */ 'message', /* value */ 'hello!')
  6. }
  7. }

Composbles

利用Composition API封装的可重用状态逻辑称为composables。
约定composables函数命名时加上use前缀,例如:

  1. // mouse.js
  2. import { ref, onMounted, onUnmounted } from 'vue'
  3. // by convention, composable function names start with "use"
  4. export function useMouse() {
  5. // state encapsulated and managed by the composable
  6. const x = ref(0)
  7. const y = ref(0)
  8. // a composable can update its managed state over time.
  9. function update(event) {
  10. x.value = event.pageX
  11. y.value = event.pageY
  12. }
  13. // a composable can also hook into its owner component's
  14. // lifecycle to setup and teardown side effects.
  15. onMounted(() => window.addEventListener('mousemove', update))
  16. onUnmounted(() => window.removeEventListener('mousemove', update))
  17. // expose managed state as return value
  18. return { x, y }
  19. }

传入refs参数代替原始值

  1. import { unref } from 'vue'
  2. function useFeature(maybeRef) {
  3. // if maybeRef is indeed a ref, its .value will be returned
  4. // otherwise, maybeRef is returned as-is
  5. const value = unref(maybeRef)
  6. }

总是返回包含refs的对象

  1. // x and y are refs
  2. const { x, y } = useMouse()

组件通信

组件通信常⽤⽅式,vue3中有很多变化:

props:父子通信,传入一个属性

  1. {
  2. props: { msg: String }
  3. }

除了props选项,vue3.2还出了script setup的新写法

  1. const props = defineProps({
  2. model: { type: Object, required: true }
  3. });
  4. props.model

$emit

现在只剩下派发事件的emit方法

  1. this.$emit('add', good)

vue3.2还出了script setup的新写法

  1. const emit = defineEmits(['update:model-value', 'validate'])
  2. const emit = defineEmits<{
  3. (e: "update:model-value", value: string): void;
  4. (e: "validate"): void;
  5. }>();
  6. emit("update:model-value", inp.value);

on,once, $off被移除了!
上述3个⽅法被认为不应该由vue提供,因此被移除了,可以使⽤其他库实现等效功能。

  1. import mitt from 'mitt'
  2. const emitter = mitt()
  3. // 发送事件
  4. emitter.emit('foo', 'foooooooo')
  5. // 监听事件
  6. emitter.on('foo', msg => console.log(msg))

event bus

vue2时代我们常常使用一个vue实例来做事件总线,现在不行了:
所以我们就使用上面的mitt方案来代替就好了!

  1. // vue2时代,现在因为没有$on不行了
  2. Vue.prototype.$bus = new Vue()
  3. // 组件里面
  4. this.$bus.$emit('xxx')
  5. // 其他组件
  6. this.$bus.$on('xxx', () => {})

vuex/pinia

vuex 4.x中的composition api写法:

  1. const store = useStore()
  2. store.commit('add')
  3. store.dispatch('add')

parent/root

兄弟组件之间通信可通过共同祖辈搭桥。

  1. // brother1
  2. this.$parent.$on('foo', handle)
  3. // brother2
  4. this.$parent.$emit('foo')

$children

vue3中移除了该选项,官方建议我们访问子组件时使用$refs
$refs
获取指定元素或组件

  1. // parent
  2. <HelloWorld ref="hw"/>
  3. mounted() {
  4. this.$refs.hw.xx = 'xxx'
  5. }

provide/inject

能够实现祖先和后代之间传值
在composition中有对应的api

  1. // ancestor
  2. provide() {
  3. return {foo: 'foo'}
  4. }
  5. // descendant
  6. inject: ['foo']
  1. import {provide, inject} from vue
  2. // 提供数据
  3. provide(key, value)
  4. // 注入数据
  5. inject(key)

$attrs

attrs 会包含那些没有声明的组件特性,vue3中会移除了listeners,只剩下$attrs

  1. // child:并未在props中声明foo
  2. <p>{{$attrs.foo}}</p>
  3. // parent
  4. <HelloWorld foo="foo"/>

通过 v-bind=”$attrs” 透传到内部组件——在创建⾼级别的组件时⾮常有⽤:

  1. // 给Grandson隔代传值,parent.vue
  2. <Child2 msg="lalala" @some-event="onSomeEvent"></Child2>
  3. // Child做展开
  4. <Grandson v-bind="$attrs"></Grandson>
  5. // Grandson使⽤
  6. <div @click="$emit('some-event', 'msg from grandson')">
  7. {{msg}}
  8. </div>

插槽

插槽语法是Vue 实现的内容分发 API,⽤于复合组件开发。该技术在通⽤组件库开发中有⼤量应⽤。

匿名插槽

slot称为匿名插槽,作为占位符存在,将来被替换为传入内容。

  1. // comp1
  2. <div>
  3. <slot></slot>
  4. </div>
  5. // parent
  6. <comp>hello</comp>

具名插槽

slot加上name就称为具名插槽,可以将内容分发到⼦组件指定位置

  1. // comp2
  2. <div> <slot></slot> <slot name="content"></slot>
  3. </div>
  4. // parent
  5. <Comp2>
  6. <!-- 默认插槽⽤default做参数 -->
  7. <template v-slot:default>具名插槽</template>
  8. <!-- 具名插槽⽤插槽名做参数 -->
  9. <template v-slot:content>内容...</template>
  10. </Comp2>

作⽤域插槽

分发内容要⽤到⼦组件中的数据

  1. // comp3
  2. <div> <slot :foo="foo"></slot>
  3. </div>
  4. // parent
  5. <Comp3>
  6. <!-- v-slot的值指定为作⽤域上下⽂对象 -->
  7. <template v-slot:default="slotProps">
  8. 来⾃⼦组件数据:{{slotProps.foo}}
  9. </template>
  10. </Comp3>

Vue3中组件相关api变化总结

global-api改为实例⽅法

全局静态⽅法引发⼀些问题,vue3将global-api改为app实例⽅法

  1. // vue2中
  2. // Vue.component()
  3. // vue3中
  4. const app = createApp({})
  5. .component('comp', {template: '<div>i am comp</div>'})
  6. .mount('#app')

移除.sync,统⼀为v-model

以前.sync和v-model功能有重叠,容易混淆,vue3做了统⼀。

  1. <div id="app">
  2. <comp v-model="data"></comp>
  3. <comp :model-value="data" @update:model-value="onxxx"></comp>
  4. <comp :data="data" @update:data="onxxx"></comp>
  5. </div>
  1. app.component('comp', {
  2. props: {
  3. modelValue
  4. },
  5. template: `
  6. <div @click="$emit('update:model-value', 'new value')">
  7. i am comp, {{modelValue}}
  8. </div>
  9. `,
  10. props: ['modelValue'],
  11. })

渲染函数api修改

不再传⼊h函数,需要我们⼿动导⼊;拍平的props结构。scopedSlots删掉了,统⼀到slots

  1. import {h} from 'vue'
  2. export default {
  3. render() {
  4. const emit = this.$emit
  5. const onclick = this.onclick
  6. return h('div', [
  7. h('div', { onClick(){ emit('update:modelValue', 'new value')} },
  8. `i am comp, ${this.modelValue}`),
  9. h('button', { onClick(){ onclick() }}, 'buy it!')
  10. ])
  11. },
  12. }

组件emits选项

该选项⽤于标注⾃定义事件及其校验等。

  1. createApp({}).component("comp", {
  2. template: `...`,
  3. // emits标明组件对外事件
  4. emits: ['buy', '...']
  5. // 还能对事件进行校验
  6. emits: {
  7. 'update:modelValue': null, // 不做校验
  8. buy(p) { // 校验buy事件
  9. if (p === 'nothing') {
  10. console.warn('参数⾮法');
  11. return false
  12. } else {
  13. return true
  14. }
  15. }
  16. },
  17. })