前言
在工作中經(jīng)常會(huì)用到Vue,包括也會(huì)用到很多重要的點(diǎn)例如組件化等等仿野,現(xiàn)在也想對(duì)于之前的應(yīng)用和學(xué)習(xí)做一個(gè)小小的總結(jié)~后期也會(huì)不定期的更新
組件化
- 概念:
Vue組件系統(tǒng)提供了一種抽象芒率,讓我們可以使用獨(dú)立可復(fù)用的組件來構(gòu)建大型應(yīng)用隆敢,任何類型的應(yīng)用界面都可以抽象為一棵組件樹。 - 思想:
高內(nèi)聚低耦合(功能性越單一可復(fù)用性就越強(qiáng)) - 優(yōu)點(diǎn):
- 提高開發(fā)效率
- 方便重復(fù)使用
- 簡(jiǎn)化調(diào)試步驟
- 提升項(xiàng)目可維護(hù)性
- 便于多人協(xié)助開發(fā)
....
一粘姜、組件通信
組件化的重中之重就是組件之間的通信鞠苟,怎么進(jìn)行傳值可以高效方便的完成功能開發(fā)
-
常用通信方式:
1. props (parent -> children)
// child props: { msg: String } // parent <HelloWorld msg="Welcome to Vue.js" />
2. event (children -> parent)
// 派發(fā)自定義事件 誰(shuí)派發(fā)誰(shuí)監(jiān)聽 // child this.$event('add',good) // parent <Cart @add="cartAdd($event)" />
3. 事件總線 (任意兩個(gè)組件)
// 發(fā)布訂閱模式 // Bus: 事件派發(fā)乞榨、監(jiān)聽和回調(diào)管理 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代替Bus,因?yàn)閂ue已經(jīng)實(shí)現(xiàn)了相應(yīng)接口 Vue.proptotype.$bus = new Bus() // child1 this.$bus.$on('foo',handle) // child2 this.$bus.$emit('foo')
4. vuex (任意兩個(gè)組件)
創(chuàng)建唯一的全局?jǐn)?shù)據(jù)管理者store,通過它管理數(shù)據(jù)并通知組件狀態(tài)變更偶妖,具體使用大家可以去vux了解學(xué)習(xí)姜凄。
-
自定義事件:
-
邊界情況:
注:parent、root趾访、children由于高耦合态秧、強(qiáng)依賴的原因在項(xiàng)目里可根據(jù)實(shí)際情況使用
1. parent / root
// 兄弟組件之間通信可以通過公共祖輩搭橋,$parent或$root // brother1 this.$parent.$on('foo',handle) // brother2 this.$parent.$emit('foo')
2. $children(自定義組件不包含原始標(biāo)簽)
// 父組件可以通過$children訪問子組件實(shí)現(xiàn)父子通信 // parent this.$children[0].xx = 'xxxxxx' // 注:$children不能保證子元素的順序(異步組件)
3. $refs
// 獲取子節(jié)點(diǎn)引用 // parent <HelloWorld ref="hw" /> mounted() { this.$refs.hw.xx = 'xxxxxx' }
4. provide/inject
// 能夠?qū)崿F(xiàn)祖先和后代之間傳值 // 并不是響應(yīng)式的扼鞋,但是可以傳入響應(yīng)式的數(shù)據(jù) // 后代組件內(nèi)部聲明的變量名稱和inject傳入的名稱沖突申鱼,后代組件會(huì)覆蓋傳入的值 // ancestor provide() { // 隔代傳參愤诱,用法類似于data return { foo: 'foo', app: this // 指的是當(dāng)前組件實(shí)例本身 } } // descendant <p>{{ app.$options.name }}</p> // 當(dāng)命名沖突還想使用傳入的值的時(shí)候,給傳入的數(shù)據(jù)起別名 // inject: ['foo','app'], inject: { foo2: 'foo', app: 'app' }, data() { return { foo: 'my-foo' } }
-
非prop特性
注:包含父作用域中不作為prop被識(shí)別(且獲染栌选)的特性綁定(class和style除外)淫半。當(dāng)一個(gè)組件沒有聲明任何prop時(shí),這里會(huì)包含所有父作用域的綁定(class和style除外)匣砖,并且可以通過v-bind="$attrs"傳入內(nèi)部組件1. $attrs(屬性并未在props中聲明)
// child:并未在props中聲明foo <p>{{ $attrs.foo }}</p> // parent <HelloWorld foo="foo" />
2. $listeners (在子組件中只負(fù)責(zé)觸發(fā)回調(diào)函數(shù)但是在父組件中處理回調(diào)函數(shù)的邏輯)
// parent <HelloWorld @click="onClick"/> // child 使用v-on指令將$listeners展開(如果有多個(gè)事件科吭,是都會(huì)展開的) // $listeners (本身是一個(gè)鍵值對(duì)格式的對(duì)象 ) // key->所有事件監(jiān)聽器的名稱 value->回調(diào)函數(shù) // 展開后 <p @click="onClick"></p> <p v-on="$listeners"></p>
-
二、插槽
插槽語(yǔ)法是Vue實(shí)現(xiàn)的內(nèi)容分發(fā)API猴鲫,用于復(fù)合組件開發(fā)对人。內(nèi)容分發(fā)簡(jiǎn)單來說就是內(nèi)容要在子組件中使用,但是要通過父組件將內(nèi)容傳遞進(jìn)來拂共。
-
匿名插槽
// comp1
<div>
<slot></slot>
</div>
// parent
<Comp1>Hello</Comp1>
-
具名插槽
// 將內(nèi)容分發(fā)到子組件指定位置
// comp2
<div>
<slot></slot>
<slot name="content"></slot>
</div>
// parent
<Comp2>
// 默認(rèn)插槽用default做參數(shù)
<template v-slot:default>匿名插槽</template>
// 具名插槽用插槽名做參數(shù)
<template v-slot:content>內(nèi)容...</template>
</Comp2>
-
作用域插槽
數(shù)據(jù)在子組件中牺弄,但是要在插槽中使用
// comp3
<div>
<slot :foo="foo"></slot>
</div>
// parent
<Comp3>
// 把v-slot的值指定為作用域上下文對(duì)象
<template v-slot:default="slotProps">
來自子組件中的數(shù)據(jù){{ slotProps.foo }}
</template>
// 解構(gòu)賦值寫法
<template v-slot:default="{foo}">
來自子組件中的數(shù)據(jù){{ foo }}
</template>
</Comp3>
上面是對(duì)Vue組件化包括組件通信以及插槽的一些介紹,接下來要通過幾個(gè)在工作中常用的實(shí)例來實(shí)踐一下
一宜狐、表單組件
通用表單組件势告,參考element表單分析我們需要實(shí)現(xiàn)哪些組件:
- KForm (指定數(shù)據(jù)、校驗(yàn)規(guī)則->便于管理抚恒,統(tǒng)一傳參)
- KFormItem (執(zhí)行校驗(yàn)咱台、顯示錯(cuò)誤信息)
- KInput (維護(hù)數(shù)據(jù))
-
KInput
- 創(chuàng)建components/form/KInput.vue
<template> <div> // 實(shí)現(xiàn)雙向數(shù)據(jù)綁定 @input,:value // 通過v-bind展開$attrs柑爸,顯示沒有在props里面?zhèn)魅氲闹党郴ぃ纾╬laceholder、type) <input :value="value" @input="onInput" v-bind="$attrs" /> </div> </template> <script> export default { inheritAttrs: false, // 將屬性繼承關(guān)閉 props: { value: { type: String, defaule: '' } }, methods: { onInput(e) { // 派發(fā)事件表鳍,將最新的值傳出去 this.$emit('input',e.target.value) } } } </script>
- 使用KInput,創(chuàng)建components/form/index.vue
<template> <div> <h3>KForm表單</h3> <hr /> <KInput v-model="model.username"></KInput> <KInput type="password" v-model="model.password"></KInput> </div> </template> <script> import KInput from './KInput' export default { components: { KInput }, data() { return { model: { username: 'Cherry', password: '' } } } } <script>
-
KFormItem
- 創(chuàng)建components/form/KFormItem.vue
<template> <div> // label標(biāo)簽 <label v-if="label">{{ label }}</label> // 插槽 input <slot></slot> // 錯(cuò)誤信息 <p v-if="error">{{ error }}</p> </div> <script> export default { props: { label: { type: String, default: '' }, prop: String // 校驗(yàn)的字段名稱 }, // 這個(gè)值是否涉及當(dāng)前組件的狀態(tài),如果是就放在data里面 data() { return { error: '' } } } </script> </template>
- 使用KFormItem 在components/form/index.vue添加
<template> <div> <h3>KForm表單</h3> <KFormItem label="用戶名" prop="username"> <KInput v-model="model.username"></KInput> </KFormItem> <KFormItem label="密碼" prop="password"> <KInput v-model="model.password" type="password"></KInput> </KFormItem> <KFormItem> <button @click="onLogin">登錄</button> </KFormItem> </div> </template>
-
KForm 設(shè)置數(shù)據(jù)模型和校驗(yàn)規(guī)則
- 創(chuàng)建components/form/KForm.vue
<template> <div> <form> <slot></slot> </form> </div> </template> <script> export default { // 隔層傳遞數(shù)據(jù) provide() { return { // 將表單實(shí)例直接傳遞給后代 form: this } }, props: { model: { type: Object, required: true }, rules: Object } } </script>
- 使用KForm.vue 在components/form/index.vue添加
<template> <div> <KForm :model="model" :rules="rules"> ... </KForm> </div> </template> <script> import KForm from './KForm' export default { components: { KForm }, data() { return { rules: { username: [{ required: true, message: "請(qǐng)輸入用戶名" }], password: [{required: true, message: "請(qǐng)輸入密碼" }] }, model: { username: "Cherry", password: "" }, } } } </script>
- 在KFormItem添加
export default { inject: ['form'] // 通過form.rules[prop]可以訪問當(dāng)前表單的校驗(yàn)規(guī)則 }
-
數(shù)據(jù)校驗(yàn)
- 在KInput里面的onIput事件中觸發(fā)校驗(yàn)
onInput(e) { // $parent指向KFormItem this.$parent.$emit('validate') }
- KFormItem監(jiān)聽校驗(yàn)通知馅而,獲取規(guī)則并執(zhí)行校驗(yàn)
// 引入校驗(yàn)庫(kù):npm i -S async-validator import Schema from 'async-validator' export default { inject: ['form'], //注入 mounted() { this.$on('validate',() => { this.validate() }) }, methods: { validate() { // 獲取校驗(yàn)規(guī)則 const rule = this.form.rules[this.prop] // 獲取校驗(yàn)值 const val = this.form.model[this.prop] // 獲取校驗(yàn)器 Schema參數(shù),key: 校驗(yàn)字段名 value: 校驗(yàn)規(guī)則 const validator = new Schema({ [this.prop] : rule }) // 執(zhí)行校驗(yàn),參數(shù)1校驗(yàn)?zāi)繕?biāo):校驗(yàn)值,參數(shù)2回調(diào)函數(shù) // 返回Promise對(duì)象 return new Promise((resolve,reject) => { validator.validate({ [this.prop] : val },(errors) => { if(errors) { // 校驗(yàn)失敗 this.error = errors[0].message reject() } else { // 校驗(yàn)通過 清空error this.error = '' resolve() } }) }) } } }
- 在index.vue添加
<template> <div> <KForm :model="model" :rules="rules" ref="loginForm"> ... <KFormItem> <button @click="onLogin">登錄</button> </KFormItem> </KForm> </div> <script> export default { methods: { onLogin() { // 全局校驗(yàn) this.$refs.loginForm.validate(isValid => { if(isValid) { console.log('success') } else { alert('校驗(yàn)失敗譬圣!') } }) } } } </script> </template>
- 在KForm.vue添加
// 添加全局校驗(yàn)方法 validate() { // 遍歷所有FormItem瓮恭,執(zhí)行他們的validate方法 // tasks是返回的Promise數(shù)組 const tasks = this.$children .filter(item => item.prop) // 過濾一下沒有prop的FormItem .map(item => {}) Promise.all(tasks) .then(() => cb(true)) // 校驗(yàn)通過 返回true .catch(() => cb(false)) // 校驗(yàn)失敗 返回false }
四、彈窗組件
-
彈窗這一類組件的特點(diǎn):
- 在當(dāng)前vue實(shí)例之外是獨(dú)立存在的厘熟,通常掛載在body上
- 通過js動(dòng)態(tài)創(chuàng)建屯蹦,不需要在任何組件中聲明
-
創(chuàng)建utils文件夾,并創(chuàng)建create.js
import Vue from 'vue' // 創(chuàng)建create函數(shù)绳姨,可以動(dòng)態(tài)生成組件實(shí)例登澜,并且掛載至body上 // Component:組件配置對(duì)象 function create(Component,props) { // 第一種實(shí)現(xiàn)方式:通過Vue實(shí)例實(shí)現(xiàn) // 借助Vue的構(gòu)造函數(shù)來動(dòng)態(tài)生成組件實(shí)例 const vm = new Vue({ render(h) { // h createElement別名,可以返回一個(gè)虛擬dom飘庄,VNode return h(Component,{props}) } }) vm.$mount() // 不指定宿主元素脑蠕,則會(huì)創(chuàng)建真實(shí)dom,但是不會(huì)追加操作 // 通過$el屬性獲取真實(shí)dom,并在body后面做追加操作 document.body.appendChild(vm.$el) // 返回組件實(shí)例 const comp = vm.children[0] // 銷毀方法 comp.remove = () => { document.body.removeChild(vm.$el) vm.destroy() } // 第二種實(shí)現(xiàn)方式:通過Vue.extend()實(shí)現(xiàn) const Ctor = Vue.extend(Component) // 構(gòu)造函數(shù) // 創(chuàng)建組件實(shí)例 const comp = new Ctor({propsData:props}) // 掛載 comp.$mount() document.body.appendChild(comp.$el) comp.remove = () => { document.body.removeChild(comp.$el) comp.$destroy() } return comp } // 暴露調(diào)用接口 export default create
-
創(chuàng)建通知組件:Notice.vue
<template> <div class="box" v-if="isShow"> <h3>{{ title }}</h3> <p class="box-content">{{ message} }</p> </div> </template> <script> export default { props: { title: { // 標(biāo)題 type: String, default: "" }, message: { // 信息 type: String, default: "" }, duration: { // 時(shí)間 type: Number, default: 1000 } }, data() { return { isShow: false } }, methods: { show() { // 顯示 this.isShow = true // 自動(dòng)隱藏 setTimeout(this.hide,this.duration) }, hide() { // 隱藏 this.isShow = false // 銷毀 this.remove() } } } </script> <style> .box { position: fixed; width: 100%; top: 16px; left: 0; text-align: center; pointer-events: none; background-color: #fff; border: grey 3px solid; box-sizing: border-box; } .box-content { width: 200px; margin: 10px auto; font-size: 14px; padding: 8px 16px; background: #fff; border-radius: 3px; margin-bottom: 8px; } </style>
-
-
使用create.js在index.vue中
// 引入 import create from "@/utils/create" import Notice from "@/components/Notice" export default { onLogin() { // 全局校驗(yàn) this.$refs.loginForm.validate(isValid => { if(isValid) { console.log('success') } else { // 傳入值第一個(gè)參數(shù)組件谴仙,第二個(gè)參數(shù)是配置項(xiàng) this.$create(Notice,{ title: '校驗(yàn)失敗'迂求, message: '校驗(yàn)錯(cuò)誤,請(qǐng)重試', duration: 3000 }).show() } }) } }
五晃跺、遞歸組件
遞歸組件是可以在它們自己模板中調(diào)用自身的組件,主要是針對(duì)樹形結(jié)構(gòu)的數(shù)據(jù)進(jìn)行展示揩局,在工作中的應(yīng)用場(chǎng)景也是很多的
-
創(chuàng)建Node.vue
<template>
<div>
<div @click="toggle" :style="{ paddingLeft: (level-1)+'em' }">
<label>
{{ model.name }}
</label>
<span v-if="isFolder">[{{ open ? '-' : '+' }}]</span>
</div>
<div v-show="open" v-if="isFolder">
<Node
class="item"
v-for="model in model.children"
:model="model"
:key="model.name"
:level="level + 1"
></Node>
</div>
</div>
</template>
<script>
export default {
name: 'Node',
props: {
model: Object,
level: {
type: Number,
default: 0
}
},
data() {
return {
open: false
}
},
computed: {
isFolder: function() {
return this.model.children && this.model.children.length
}
},
methods: {
toggle: function() {
if(this.isFolder) {
this.open = !this.open
}
}
}
}
</script>
-
創(chuàng)建Tree.vue
<template>
<div class="tree">
<Node v-for="item in date" :key="item.name" :model="item"></Node>
</div>
</template>
<script>
import Node from './Node'
export default {
name: 'Tree',
props: {
data: {
type: Array,
required: true
}
},
components: {
Node
}
}
</script>
<style>
.tree {
text-align: left;
}
</style>
-
使用Tree.vue在Index.vue
<template>
<div>
<Node :data="treeData"></Node>
</div>
</template>
<script>
import Node from '@/components/Tree'
export default {
components: {
Node
},
data() {
return {
treeData: [
{
name: '水果',
children:[
{
name: '南方水果',
children: [
...
]
},
{
name: '北方水果'
}掀虎,
]
},
{
name: '蔬菜'
}
]
}
}
}
</script>