[學習vue]組件化實戰(zhàn)

組件化

組件化是vue的核心思想,它能提高開發(fā)效率,方便重復使用,簡化調(diào)試步驟,提升整個項目的可維護 性,便于多人協(xié)同開發(fā)

組件通信

父組件 => 子組件:
  • 屬性props
//child
props: {
    msg: String
}
//parent
<HelloWorld msg="Welcome to Your Vue.js App" /> 
  • 特性$attrs
//child
<p>attrs: {{$attrs.foo}}</p>
//parent
<HelloWorld  foo="我是自組件attrs傳值" />
  • 引用refs
//parent
<HelloWorld  ref="comeon" />
mounted() {
    this.$refs.hw.comeon = '我是parent'
  },
//child
 methods: {
    sayHello() {
      console.log(this.comeon)
    }
  },
  • 子元素$children
//parent
this.$children[0].xx = 'xx'
 
子組件 => 父組件:自定義事件
// child
<div @click='testClick()'>
    this is test
</div>
testClick() {
      this.$emit('myclick','牛仔很忙');
}
// parent
<test @myclick="onyourclick($event)"/>
onyourclick($event){
      console.log('onyourClick',$event)
},
兄弟組件:通過共同祖輩組件

通過共同的祖輩組件搭橋,parent 或 $root

// brother1
this.$parent.$on('foo', handle)
// brother2
this.$parent.$emit('foo')
祖先和后代之間

由于嵌套層數(shù)過多,傳遞props不切實際,vue提供了 provide/inject API完成該任務,實現(xiàn)祖先給后代傳值,缺點是非響應式

// ancestor
provide() {
    return {foo: '肖邦的夜曲'}
}
// descendant
inject: ['foo']
任意兩個組件之間:事件總線 或 vuex

事件總線:創(chuàng)建一個Bus類負責事件派發(fā)、監(jiān)聽和回調(diào)管理,EventBus來作為一個橋梁的概念,就像是所有組件共用相同的事件中心,可以向該中心注冊發(fā)送事件,或接收事件,所以組件都可以上下平行地通知其他組件,但也就是太方便所以若使用不慎,就會造成難以維護的災難,因為才需要更完善的VUEX作為狀態(tài)管理中心,將通知的概念上升到共享狀態(tài)層次.

//increment.vue (child)
<button @click="increment()">+</button>
increment() {
     this.$bus.$emit("incremented", {
           num:this.num
     })
}

//decrement.vue(child)
<button @click="decrease()">-</button>
decrease(){
     this.$bus.$emit('decreased',{
           num: this.num
     })
}

//app.vue
<div class="increment">
       <increment />
</div>
<div class="show-front"> {{fontCount}} </div>
<div class="decrement">
       <decrement />
</div>

this.$bus.$on("incremented", ({num}) => {
       this.fontCount += num
});
this.$bus.$on("decreased", ({num}) => {
      this.fontCount -= num
})
slot 插槽

插槽可以分為具名插槽,作用域插槽,是在子組件中提供給父組件使用的一個占位符,用<slot></slot>表示,父組件可以在這個占位符中填充任何模版代碼,例如html ,組件等,填充的內(nèi)容會替換子組件的<slot></slot>標簽。

//子組件
<!-- 插槽 -->
<p><slot></slot></p>
<p><slot name="content" :foo="桃花諾"></slot></p>

//父組件 app.vue
<HelloWorld>      
      <template>abc</template>
      <!-- 作用域插槽:顯示數(shù)據(jù)來自子組件 -->
       <template v-slot:content="{foo}">content...{{foo}}</template>
</HelloWorld>

組件化實戰(zhàn)

模仿elementui 實現(xiàn)Form、FormItem、Input
首先實現(xiàn)input,功能實現(xiàn)數(shù)據(jù)收集,表單元素的輸入和校驗反饋,實現(xiàn)雙向綁定,監(jiān)聽@input,給:value賦值

// input.vue
<!-- input事件處理,值賦值 -->
<input :type="type" @input="onInput" :value="value" v-bind="$attrs">
export default {
        inheritAttrs: false, //不繼承attrs
        props: {
            value: {
                type: String,
                default: ''
            },
            type: {
                type: String,
                default: 'text'
            },
        },
        methods: {
            onInput(e) {
                // 轉(zhuǎn)發(fā)input事件即可
                this.$emit('input', e.target.value)

                // 通知校驗
                this.$parent.$emit('validate')
            }
        },
    }

formItem,表單項,用來校驗,顯示錯誤提示,顯示input,顯示label
<slot></slot> 用來顯示input 插槽
引入async-validator校驗包文件

 <div>
        <!-- label -->
        <label v-if="label">{{label}}</label>
        <slot></slot>
        <!-- 錯誤信息 -->
        <p class="error" v-if="error">{{error}}</p>
    </div>
import Schema from 'async-validator';
    export default {
        inject: ['form'],
        props: {
            label: {
                type: String,
                default: ''
            },
            prop: {
                type: String
            }
        },
        data() {
            return {
                error: ''
            }
        },
        mounted() {
            this.$on('validate', () => {
                this.validate();
            })
        },
        methods: {
            validate() {
                // 0.獲取校驗規(guī)則和當前值
                const rules = this.form.rules[this.prop];
                const value = this.form.model[this.prop];
                // 1.創(chuàng)建Schema實例
                // this指向當前組件實例,this.prop是當前項的key
                const schema = new Schema({
                    // [xxx] 計算屬性
                    [this.prop]: rules 
                })
                // 2.使用該實例執(zhí)行校驗
                return schema.validate({
                    [this.prop]: value
                }, errors => {
                    if (errors) {
                        this.error = errors[0].message;
                    } else {
                        this.error = ''
                    }
                })
            }
        },
    }

form 表單 作為數(shù)據(jù)的容器傳遞給后代,實現(xiàn)父組件validate方法,收集所有item組件校驗成功與否,回調(diào)給子組件成功或者失敗參數(shù)。

<slot></slot>
export default {
  provide() {
    return {
      // this指的是表單組件實例
      form: this
    };
  },
  props: {
    model: {
      type: Object,
      required: true
    },
    rules: {
      type: Object
    }
  },
  methods: {
    validate(cb) {
      // 調(diào)用所有formitem的validate,只要一個失敗就失敗
      // 結(jié)果是Promise數(shù)組
      const tasks = this.$children
        .filter(item => !!item.prop) // 留下帶prop屬性的formitem
        .map(item => item.validate());
      // 判斷所有結(jié)果
      Promise.all(tasks)
        .then(() => cb(true)) // 校驗通過
        .catch(() => cb(false));// 校驗失敗
    }
  }
}

實現(xiàn)彈窗組件

彈窗之類的組件的特點是它們存在與vue實例之外的獨立存在,通常掛載與body,它們通過js獨立創(chuàng)建,不需要在任何組件中聲明,常用的姿勢:

this.$create(Notice, {
          title: "社會你楊哥喊你來搬磚",
          message: isValid?"提請求登錄!":"校驗失敗!",
          duration: 2000
}).show();

接下來需要聲明一個Notice,還需要實現(xiàn)一個create方法,能夠創(chuàng)建我們的這個notice實例
先創(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: {
      type: String,
      default: ""
    },
    message: {
      type: String,
      default: ""
    },
    duration: {
      type: Number,
      default: 1000
    }
  },
  data() {
    return {
      isShow: false
    };
  },
  methods: {
    show() {
      this.isShow = true;
      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>

接下來需要實現(xiàn)一個能夠創(chuàng)建這個實例的方法 create.js

// 1.創(chuàng)建傳入組件實例
// 2.掛載它從而生成dom結(jié)構(gòu)
// 3.生成dom結(jié)構(gòu)追加到body
// 4.淘汰機制:不需要時組件實例應當被銷毀
import Vue from 'vue'
export default function create(Component, props) {
    // 1.創(chuàng)建傳入組件實例
    // Vue.extend({})
    const vm = new Vue({
        render(h) { // h即是createElement(tag, data, children)
            // 返回虛擬dom
            return h(Component, {props})
        }
    }).$mount(); // 只掛載,不設置宿主,意思是執(zhí)行初始化過程,但是沒有dom操作

    document.body.appendChild(vm.$el)

    // 獲取組件實例
    const comp = vm.$children[0];
    // 附加一個刪除方法
    comp.remove = () => {
        // 移除dom
        document.body.removeChild(vm.$el)
        // 銷毀組件
        vm.$destroy();
    }
    return comp;
}

最后在全局掛載一下這個方法,就可以使用了
Vue.prototype.$create = create;

<
遺留問題
1, 在實現(xiàn)表單組件,查找父級元素的時候,使用this.$parent 查找,如果頁面嵌套結(jié)構(gòu)發(fā)生改變,這種查找方法就會失效,怎么解決?
2,彈窗組件中,創(chuàng)建組件實例,怎么用Vue.extend({}) 實現(xiàn)?

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容