本文已参与「新人创作礼」活动,一起开启掘金创作之路。详情
组件化
vue系统提供了一种抽象,让我们可以使用独立可复用的组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树。
组件化能提高开发效率,方便重复使用,简化调试步骤,提升项目可维护性,便于多人协同开发
1、Vue组件之间的通信
组件通信常用方式
-
props示例 props (父传子)
// parent组件 <HelloWorld msg="Welcome to Your Vue.js App"/> // child组件 props: { msg: String } -
$emit/$on(vue3废弃$on)示例:自定义事件 (子传父)
// child this.$emit('add', good) // parent <Child @add="cartAdd($event)"></Cart> -
$children/$parent(vue3废弃$children)兄弟组件之间通信可通过共同祖辈搭桥,root。
// brother1 this.$parent.$on('foo', handle) // brother2 this.$parent.$emit('foo')父组件可以通过$children访问子组件实现父子通信。
// parent this.$children[0].xx = 'xxx' // $children可以拿到父组件中自定义组件的实例集合注意:$children不能保证子元素顺序。
-
$root同理$parent可以作为桥梁通信
-
ref获取子节点引用
// parent <HelloWorld ref="hw"/> mounted() { this.$refs.hw.xx = 'xxx' } -
provide/inject能够实现祖先和后代之间传值,隔代传参(一般3代以上使用)
// 祖先组件中 provide() { return { foo: "foo", // 传入foo值 handel: this.someMsg, // 传入handel方法 }; }, methods: { someMsg(e) { console.log(e); // 此处可拿到孙辈组件传来的值 1 }, } // 子孙辈组件: inject: ["foo", "handel"], mounted() { console.log(this.foo); this.handel(1); // 触发传进来的handel函数并传值1 }, -
$attrs/$listeners(vue3废弃$listeners)$attrs识别非属性特性(非props特性)。即识别没有声明在props中的属性
示例:父子组件
// parent父组件 <HelloWorld foo="foo"/> // child子组件:并未在props中声明foo <p>{{$attrs.foo}}</p>主要使用场景:属性的透传,创建高阶组件时这个属性非常有用。
比如有一个通用组件A,但是需要在A的基础上做一些扩展,那么我们就开发一个组件B将A包起来做扩展组件使用。然后用在组件C中时需要给B传一些属性,但是这些属性可能是实际上B中使用的也可能是A中使用了。这时的属性透传就可以用$attrs。
c父组件 -> b子组件 -> a孙组件// A中使用B组件,给C传参 <B msg="lalala" @some-event="onSomeEvent"></B> methods:{ onSomeEvent(e) { console.log(e) // 此处的e是从C组件中传来的值 } } // B组件做C组件的扩展组件 <C v-bind="$attrs" v-on="$listeners"></C> // v-bind可以讲属性展开,类似react中传值时用...展开符。此处就将b组件拿到的$attrs的属性全部挂在了c组件 // $listeners是监听A组件传进来的事件,通过v-on将所有的事件就监听上 // C组件识别参数 <div @click="click"> {{$attrs.msg}} </div> methods: { click() { // 点击时触发透传过来的事件并传值 this.$emit('some-event', 'C中的值') } } -
eventbus事件总线:在任意两个组件之间传值常用事件总线或vuex
// Bus:事件派发、监听和回调管理 class Bus { constructor(){ this.callbacks = {} } $on(name, fn){ this.callbacks[name] = this.callbacks[name] || [] this.callbacks[name].push(fn) } $emit(name, args){ if(this.callbacks[name]){ this.callbacks[name].forEach(cb => cb(args)) } } } // main.js Vue.prototype.$bus = new Bus() // child1 this.$bus.$on('foo', handle) // child2 this.$bus.$emit('foo')实践中通常⽤Vue代替Bus,因为Vue已经实现了emit
-
vuex创建唯⼀的全局数据管理者store,通过它管理数据并通知组件状态变更。
应用场景
父子传值:props / parent / ref / $attrs
兄弟组件:root / eventbus / vuex
跨层级通信:eventbus / vuex / provide + inject
vue3变化
vue3中废弃了$children、$listeners、$on
-
$children$childreninstance 属性已从 Vue 3.0 中删除。如果需要访问子组件实例,建议使用refs。
-
$listeners该
$listeners对象已在 Vue 3 中删除。事件监听器现在是用$attrs。在 Vue 3 的虚拟 DOM 中,事件监听器现在只是属性,前缀
on为 ,因此是对象的一部分$attrs,因此$listeners已被删除。
-
$on$on,$off和$once在vue3中已被删除。注意:通过
const Bus = new Vue()创建的eventBus的$on,$off和$once的方法已经不能再用。但是我们自己封装的事件总线可以自行定义这些属性方法使用的。在vue3中我们可以自行定义事件总线使用这些方法,也可以使用例如mitt或tiny-emitter外部插件来实现。
具体详解:
v3-migration.vuejs.org/breaking-ch…
2、插槽
插槽主要就是内容分发。
插槽语法是Vue 实现的内容分发 API,用于复合组件开发。该技术在通用组件库开发中有大量应用
匿名插槽
// comp1子组件在需要父组件自定义的地方使用slot标签
<div>
<slot></slot>
</div>
// parent父组件中使用子组件,但是子组件的某些内容在父组件中自行定义
<comp>hello</comp>
具名插槽
将多个内容分发到子组件指定位置
// 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>
3、vue3中的组件化
vue3新特性
-
可以提高更好的逻辑复用和代码组织。可以替换mixins、生命周期、计算属性、各种方法等。使用setup可以讲这些逻辑用很好的函数的方式去实现复用。
<template> <h1 @click="msg = 'xx'">{{ msg }}</h1> <p>{{ data.num }}</p> <p>{{ data.doubleNum }}</p> </template> <script> import { reactive, computed, onMounted, onUnmounted, ref } from "vue"; export default { setup() { // num相关逻辑 const data = reactive({ // reactive创建一个响应式对象 num: 1, doubleNum: computed(() => data.num * 2), // 计算属性 }); let timer; onMounted(() => { // 生命周期挂载事件 timer = setInterval(() => { data.num++; }, 1000); }); onUnmounted(() => { // 生命周期卸载事件 clearTimeout(timer); }); // 使用dom元素 const desc = ref(null); // desc在下面return导出,会在html中找到同名的ref元素赋值 watch( () => data.num, (val, oldVal) => { // console.log(desc.value); const p = desc.value; p.textContent = "当前值" + val + ",之前值" + oldVal; } ); // msg相关逻辑 const msg = useMsg(); return { data, msg, desc }; }, }; // 还可以讲相关的数据操作这样封装后在setup中使用 function useMsg() { let msg = ref("msg"); // ref生成一个响应式的单值 // 其他处理... return msg } </script> -
全局API
vue3中创建vue的方法改为
createApp// vue3 - main.js import { createApp } from 'vue' import App from './App.vue' createApp(App).mount('#app') // vue2 - main.js import Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app')
\