模仿element-ui封裝vue元件庫(radio,radio-group)
七、封裝一個element-ui風格的radio元件
前置知識點:
radio的基本使用
引數支援:
引數名稱 | 引數描述 | 引數型別 | 預設值 |
v-model | 雙向繫結 | boolean | false |
label | 單選框和value值 | string,num,boolean | '' |
name | name屬性 | string | '' |
7.1radio元件的基本框架和樣式
框架、基本樣式以及選中樣式:
<template> <label class="ra-radio is-checke"> <span class="ra-radio_input"> <span class="ra-radio_inner"></span> <input type="radio" class="ra-radio_original" > </span> <span class="ra-radio_label">我是label</span> </label> </template> <script> export default { name: 'ra-radio', props: {} }, watch: {}, data () { return {} }, methods: {} }</script> <style lang="scss" scoped> .ra-radio{ color: #606266; font-weight: 500; line-height: 1; position: relative; cursor: pointer; display: inline-block; white-space: nowrap; outline: none; font-size: 14px; margin-right: 30px; -moz-user-select: none; -webkit-user-select: none; -moz-user-select: none; .one-radio_input{ white-space: nowrap; cursor: pointer; outline: none; display: inline-block; line-height: 1; position: relative; vertical-align: middle; .ra-radio_inner{ border: 1px solid #dcdfe6; border-radius: 100%; width: 14px; height: 14px; background-color: #fff; position: relative; cursor: pointer; display: inline-block; box-sizing: border-box; &:after{ width: 4px; height: 4px; border-radius: 100%; background-color: #fff; content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%) scale(0); transition: transform .15s ease-in; } } .ra-radio_original{ opacity: 0; outline: none; position: absolute; z-index: -1; top: 0; left: 0px; right: 0; bottom: 0; margin: 0; } } .ra-radio_label{ font-size: 14px; padding-left: 10px;; } } // 選中的樣式 .ra-radio.is-checked{ .ra-radio_input{ .ra-radio_inner{ border-color: #409eff; background-color: #409eff; &:after{ transform: translate(-50%,-50%) scale(1); } } } .ra-radio_label{ color:#409eff; } } </style>
7.2radio元件的資料雙向繫結
實現radio元件的資料雙向繫結,除了要繫結資料本身外,還要控制radio元件的樣式。
實現radio元件資料的繫結,需要父元件傳遞的label值和value值,其中value值使用v-model語法糖來繫結。
<ra-radio v-model="gender" label="0">男</ra-radio> <ra-radio v-model="gender" label="1">女</ra-radio>
子元件接收資料後,要對資料進行處理。
當radio元件被點選時,繫結的資料應該變為該元件的label值。我們將元件中的input標籤的value繫結為傳入的label值,並且宣告一個計算屬性model雙向繫結到input元件上,model我們需要通過get方法獲取值;並且通過set方法將值回撥給父元件。
同時,當我們在點選radio元件時,我們應該讓被選中的元件新增選中樣式,我們通過label和value的比較來判斷,如果相同則顯示選中樣式。
<template> <label class="ra-radio" :class="{'is-checked': label == value}"> <span class="ra-radio_input"> <span class="ra-radio_inner"></span> <input type="radio" class="ra-radio_original" :value="label" v-model="model" > </span> <span class="ra-radio_label"> <slot></slot> <!-- 如果沒有傳值,就把label作為文字顯示 --> <template v-if="!$slots.default">{{label}}</template> </span> </label> </template> //計算屬性 computed: { model: { get () { return this.value }, set (value) { // 觸發父元件的input事件 this.$emit('input', value) } } },
八、封裝一個element-ui風格的radio-group元件
radio-group元件是再radio元件上進行優化的,它的目的是在我們使用radio元件時,不必給每個元件都新增一個v-model,而是通過繫結一個v-model來實現資料繫結。
使用radio-group元件包裹radio元件時,需要考慮到的一個問題就是radio-group元件於radio元件之間的通訊。我們在使用radio-group元件時將資料通過v-model進行了繫結,那麼raido元件就不能直接拿到這個值,所以我們需要使用provide/inject進行祖孫元件之間得傳值。
使用provide/inject非常簡單,在radio-group中通過宣告provide物件將元件自身進行傳遞,在radio中使用inject進行接收即可。
radio-group元件架構:
<template> <div class="ra-radio-group"> <slot></slot> </div> </template> <script> export default { name: 'ra-radio-group', provide () { return { RadioGroup: this } }, props: { // 元件接收到了value值,我們需要觸發這個元件的input事件 // provide 與 inject 用來做祖孫之間得元件通訊 value: null } } </script>
在radio元件中,通過inject可以直接接收到引數,此時,原本通過v-model傳遞進來的value值,變成了radio-group元件傳進來的RadioGroup.value值,所以在computed計算屬性中,我們先寫一個radio元件是否被radio-group元件進行判斷的方法,並且使用在model中,如果被包裹了,則使用RadioGroup.value值,否則使用value值。
同時在is-checked類的判斷上拋棄label於value的比較,轉而通過label於model(model此時的值為value或RadioGroup.value)比較,來進行樣式的更改。
<template> <label class="ra-radio" :class="{'is-checked': label == model}"> <span class="ra-radio_input"> <span class="ra-radio_inner"></span> <input type="radio" class="ra-radio_original" :value="label" v-model="model" > </span> <span class="ra-radio_label"> <slot></slot> <!-- 如果沒有傳值,就把label作為文字顯示 --> <template v-if="!$slots.default">{{label}}</template> </span> </label> </template> <script> export default { name: 'ra-radio', props: { label: { type: [String, Number, Boolean], defualt: '' }, value: null, name: { type: String, defualt: '' } }, inject: { RadioGroup: { default: '' } }, computed: { model: { get () { return this.isGroup ? this.RadioGroup.value : this.value }, set (value) { // 觸發父元件的input事件 this.isGroup ? this.RadioGroup.$emit('input', value) : this.$emit('input', value) } }, // 用於判斷radio是否被radioGroup包裹 isGroup () { return !!this.RadioGroup } } } </script>
--------------------------------------------至此,radio元件與radio-group元件封裝完畢--------------------------------------------------
附radio元件程式碼:
<template> <label class="ra-radio" :class="{'is-checked': label == model}"> <span class="ra-radio_input"> <span class="ra-radio_inner"></span> <input :value="label" :name="name" v-model="model" type="radio" class="ra-radio_original" > </span> <span class="one-radio_label"> <slot></slot> <template v-if="!$slots.default"> {{label}} </template> </span> </label> </template> <script> export default { name: 'ra-radio', inject: { RadioGroup: { default: '' } }, data () { return { } }, props: { label: { type: [String, Number, Boolean], default: '' }, value: null, name: { type: String, default: '' } }, computed: { model: { get () { return this.isGroup ? this.RadioGroup.value : this.value }, set (value) { // 觸發父元件的input事件 this.isGroup ? this.RadioGroup.$emit('input', value) : this.$emit('input', value) } }, // 用於判斷radio是否被radioGroup包裹 isGroup () { return !!this.RadioGroup } } } </script> <style lang="scss" scoped> .ra-radio{ color: #606266; font-weight: 500; line-height: 1; position: relative; cursor: pointer; display: inline-block; white-space: nowrap; outline: none; font-size: 14px; margin-right: 30px; -moz-user-select: none; -webkit-user-select: none; -moz-user-select: none; .ra-radio_input{ white-space: nowrap; cursor: pointer; outline: none; display: inline-block; line-height: 1; position: relative; vertical-align: middle; .ra-radio_inner{ border: 1px solid #dcdfe6; border-radius: 100%; width: 14px; height: 14px; background-color: #fff; position: relative; cursor: pointer; display: inline-block; box-sizing: border-box; &:after{ width: 4px; height: 4px; border-radius: 100%; background-color: #fff; content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%) scale(0); transition: transform .15s ease-in; } } .ra-radio_original{ opacity: 0; outline: none; position: absolute; z-index: -1; top: 0; left: 0px; right: 0; bottom: 0; margin: 0; } } .ra-radio_label{ font-size: 14px; padding-left: 10px;; } } // 選中的樣式 .ra-radio.is-checked{ .ra-radio_input{ .ra-radio_inner{ border-color: #409eff; background-color: #409eff; &:after{ transform: translate(-50%,-50%) scale(1); } } } .ra-radio_label{ color:#409eff; } } </style>
附radio-group元件程式碼:
<template> <div class="ra-radio-group"> <slot></slot> </div> </template> <script> export default { name: 'ra-radio-group', provide () { return { RadioGroup: this } }, props: { // 元件接收到了value值,我們需要觸發這個元件的input事件 // provide 與 inject 用來做祖孫之間得元件通訊 value: null } } </script> <style lang="scss" scoped> </style>