編寫可複用的組件,咱們能夠收穫不少

編寫可複用的組件在實際開發中是很是常見的需求,根據需求的不一樣會有不少不一樣的組件出現,有簡單的好比Alert這樣的彈窗組件,也有複雜些的日曆組件等等css

今天咱們一塊兒來寫寫組件(Vue組件),看看從中能不能掌握什麼知識點吧!!!html

編寫一個輪播圖組件

輪播圖組件想必是使用最多的組件之一了,尤爲是每一個首頁當中一定會出現它的身影。vue

那麼,咱們就閒言少敘了,實現一個適用於移動端上的輪播圖組件吧node

先來爆照

效果圖和項目的目錄已經呈如今觀衆眼前了,接下來咱們先從App.vue文件開始搞起

先劃個重點:要實現組件的開發,先簡單看一下下面的3條邏輯webpack

核心邏輯ios

  1. 初始化輪播圖
  2. 自動播放
  3. 左右切換輪播圖

工欲善其事必先利其器,首先無論怎樣,先把組件引入一番再說git

引入組件

組件的使用向來是先引入再說,絕不例外的,看下面的組件引用github

// App.vue文件  ->  js部分

<script>
// 引入Swiper和SwiperItem組件
import Swiper from './components/Swiper/Swiper';
import SwiperItem from './components/Swiper/SwiperItem';

export default {
    // 組件內註冊組件
    components: {
        Swiper,
        SwiperItem
    }
}
</script>
複製代碼

js部分已經將引入的組件註冊到了當前App組件中,下面就開始在模板中使用它們web

// App.vue文件  ->  html部分

<template>
    <div id="app">
        <Swiper v-model="currentId">
            <SwiperItem :id="currentId">
                <div>第一張圖</div>
            </SwiperItem>
            <SwiperItem :id="currentId">
                <div>第二張圖</div>
            </SwiperItem>
            <SwiperItem :id="currentId">
                <div>第三張圖</div>
            </SwiperItem>
        </Swiper>
    </div>
</template>
複製代碼

小朋友,你是否有不少問號?彆着急,且聽風吟vuex

Swiper組件裏放了3個SwiperItem組件,並且SwiperItem組件裏實現的內容是徹底相同的

因此,不必寫3遍,直接用一個數組經過v-for渲染出來就OK了

數據

這裏我簡單用node寫了個接口,返回輪播圖的數據,若是不想寫個接口的話,我直接把數據貼出來,讓你們直觀的看到

mock數據

// 輪播圖數據
const albums = [
    { "id": 1, "title": "葉惠美", "public": 2003, "song": '晴天', "img": "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=281827478,1045705647&fm=26&gp=0.jpg"},
    { "id": 2, "title": "七里香", "public": 2004, "song": '七里香', "img": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1584873891083&di=7892d2142e6aba7e203d270e20599235&imgtype=0&src=http%3A%2F%2Fpic.rmb.bdstatic.com%2Fc1505303db7c257f248adc87b6e22fd5.jpeg"},
    { "id": 3, "title": "十一月的蕭邦", "public": 2005, "song": '夜曲', "img": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1584873911041&di=51fc38d5805edc63fdd7301dbcef316f&imgtype=0&src=http%3A%2F%2Fzximg.ingping.com%2Fueditor%2Fjsp%2Fupload%2F201705%2F201705031153520358724.jpg"},
    { "id": 4, "title": "依然范特西", "public": 2006, "song": '聽媽媽的話', "img": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1584873937432&di=ebf0092e78d5499f54728eeb43449414&imgtype=0&src=http%3A%2F%2Fimg1.dongqiudi.com%2Ffastdfs2%2FM00%2F66%2FBA%2FChOqM1rO_0mADp5rAACEhAjn7aY043.jpg"}
];
複製代碼

我寫的接口是/getalbums,返回的是json格式,{code: 0, data: albumes}

下面我仍是按照正常的請求來操做,在以前的目錄結構中,你們應該看到了api/shop.js文件,這裏是我封裝好的用來請求輪播圖數據的代碼

// api/shop.js文件

import axios from 'axios';
// 攔截返回的響應數據
axios.interceptors.response.use(res => res.data);

export default {
    async getAlbums() {
        let { data } = await axios.get('/api/getalbums');
        return data;
    }
}
複製代碼

請求數據

如今讓咱們回到App.vue這裏,開始進行請求操做,而後v-for遍歷數組並渲染SwiperItem組件

// App.vue文件  ->  js部分

<script>
import Swiper from './components/Swiper/Swiper';
import SwiperItem from './components/Swiper/SwiperItem';
// 引入封裝好的請求方法
// @是webpack配的alias別名,指代src目錄
import shop from '@/api/shop';

export default {
    data() {
        // 輪播圖數據
        sliders: [],
        // 當前id,用來顯示對應的圖片
        currentId: 1
    },
    async mounted() {
        // 請求數據並更新slider數組
        this.sliders = await shop.getAlbums();   
    },
    components: {
        Swiper,
        SwiperItem
    }
}
</script>
複製代碼

渲染數據

渲染數據的部分要交給咱們的模板來處理了

// App.vue文件  ->  html部分

<template>
    <div id="app">
        <Swiper v-model="currentId" v-if="sliders.length">
            <template v-for="item in sliders">
                <SwiperItem :key="item.id" :id="item.id">
                    <img :src="item.img" />
                </SwiperItem>
            </template>
        </Swiper>
    </div>
</template>
複製代碼

上面模板爲何寫成這樣?

  1. v-model也能夠在組件上進行綁定
    • 一個組件上的v-model默認會利用名爲value的prop和名爲input的事件
  2. v-if控制組件是在拿到數據後才進行渲染
  3. SwiperItem上的id動態屬性是用來區分當前應該展現哪張圖片的重點

好了,寫到這裏,基本的展現效果已經搞定了,img元素的寬高,在App.vue的css部分能夠定義一下如:#app img { width: 100%; height: 220px; },這樣限制了寬高看起來就比較合適了

如今,開始盡請的開發吧

Swiper組件

Swiper內部寫了多個SwiperItem組件,因此須要一個slot插槽來進行內容的分發,而且接收了v-model綁定數據後傳遞過來的value

下面,來看一眼實現的邏輯吧

// Swiper.vue文件

<template>
    <div class="swiper">
        <div class="view">
            <slot></slot>
        </div>
    </div>
</template>

<script>
export default {
    props: {
        // v-model默認傳遞過來的value屬性
        value: {
            type: Number,
            default: 1
        }
    }
}
</script>

<style scoped>
.swiper {
    position: relative;
    width: 100%;
    height: 220px;
    overflow: hidden;
}
</style>
複製代碼

先寫了一個基本的邏輯,剩下的邏輯稍後再寫,咱們再把SwiperItem組件實現一下

SwiperItem組件

輪播圖和選項卡很相似,都是當前只顯示一張圖,其他的都隱藏起來,SwiperItem組件就是用來作這些事情的

// SwiperItem.vue文件

<template>
    <transition name="items">
        <div class="swiper-item" v-if="isShow">
            <slot></slot>
        </div>
    </transition>
</template>

<script>
export default {
    props: {
        id: {
            type: Number,
            // 必填屬性
            required: true
        }
    },
    data() {
        return {
            selectedId: 1 
        }
    },
    computed: {
        isShow() {
            // 根據props傳過來的值判斷v-if顯示
            return this.id === this.selectedId;
        }
    }
}
</script>

<style scoped>
.items-enter-active,
.items-leave-active {
    transition: .5s linear;
}
.items-leave-to {
    transform: translateX(-100%);
}
.items-enter {
    transform: translateX(100%);
}
.items-enter-active {
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
}
</style>
複製代碼

初始化輪播圖

回到Swiper組件裏,咱們須要寫一個方法用來展現輪播圖,那麼就簡單的起名爲show方法吧,

它的做用就是:遍歷$children子組件實例,修改子組件裏的selectedId的值,而後顯示初始輪播圖

// Swiper.vue文件

<script>
export default {
    ...省略,
    data() {
        return {
            index: 1
        }
    },
    methods: {
        show() {
            // 給index賦值爲傳遞過來的value
            // 若是沒有傳遞value值,就默認取第一個子組件裏的id值
            this.index = this.value || this.$children[0].id;
            // 遍歷子組件,並修改實例上對應的selectedId值
            this.$children.forEach(vm => {
                vm.selectedId = this.index;
            });
        }
    },
    mounted() {
        // 初始化
        this.show();
    }
}
</script>
複製代碼

實現輪播圖小點

輪播圖下方常見會有對應的小點,讓用戶直觀的看到一共有幾張輪播圖片,其實實現起來很easy,由於只要咱們知道有幾條數據就能夠利用v-for對應循環出來了

// Swiper.vue文件

<template>
    <div class="swiper">
        <div class="view">
            <slot></slot>
        </div>
        <div class="dots">
            <span class="dot"
                  v-for="dot in len"
                  :key="dot" 
                  :class="{active: index === dot}">
            </span>
        </div>
    </div>
</template>

<script>
export default {
    ...省略,
    data() {
        return {
            index: 1,
            // 記錄輪播圖的圖片數量
            // 定義在data是爲了在模板使用
            len: 0
        }
    },
    methods: {
        ...省略
    },
    mounted() {
        // 初始化
        this.show();
        // 更新len的長度,其實就是有幾個子組件而已
        this.len = this.$children.length;
    }
}
</script>

<style scoped>
.swiper {
    position: relative;
    width: 100%;
    height: 220px;
    overflow: hidden;
}
/* 輪播圖小點樣式 */
.swiper .dots {
    position: absolute;
    bottom: 10px;
    left: 50%;
    transform: translateX(-50%);
}
.swiper .dots .dot {
    width: 10px;
    height: 4px;
    border-radius: 6px;
    background-color: rgba(255, 255, 255, 0.7);
    display: inline-block;
    margin: 0 2px;
}
.swiper .dots .active {
    width: 14px;
    background-color: #fff;
}
</style>
複製代碼

通過上面的一番折騰,總該有點該有的樣子了吧,讓咱們看看效果圖

自動播放

不少輪播圖效果都是自帶自動播放的,因此咱們當前也不會置之不理。咱們經過給Swiper組件傳遞autoplay的prop來進行設置

// App.vue文件  ->  html部分

<template>
    <div id="app">
        <Swiper v-model="currentId" v-if="sliders.length" :autoplay="true">
            ...省略
        </Swiper>
    </div>
</template>
複製代碼

父組件App傳遞了autoplay了,那麼接下來就繼續回到咱們的Swiper組件

咱們再實現一個play方法,主要是寫一個定時器,而後再經過change方法來修改value的值,作到自動播放

// Swiper.vue文件  ->  js部分

<script>
export default {
    props: {
        ...省略,
        autoplay: {
            type: Boolean,
            default: false
        }
    },
    data() {
        return {
            index: 1,
            len: 0
        }
    },
    methods: {
        show() {...省略},
        // 定時器播放
        play() {
            if (this.autoplay) {
                this.timer = setInterval(() => {
                    this.change(this.index + 1);
                }, 3000);
            }
        },
        // 切換輪播圖
        change(index) {
            // 處理右邊界若是超過了len
            if (index === this.len + 1) {
                // 將index改回到1
                index = 1;
            } else if (index === 0) {
                index = this.len;
            }
            // 經過$emit,觸發input事件,修改value爲index值
            this.$emit('input', index);
        },
        // 清除定時器
        stop() {
            clearInterval(this.timer);
            this.timer = null;
        }
    },
    watch: {
        // 這裏是關鍵
        // 監聽value值的變化,value賦給了index,隨着每次的index變化
        // 就從新調用一下show方法,刷新視圖
        value() {
            this.show();
        }
    },
    mounted() {
        this.show();
        this.len = this.$children.length;
        // 調用自動播放
        this.play();
    }
}
</script>
複製代碼

一塊兒來看看效果吧

左右切換

默認自動播放的時候,圖片都是向左移動到-100%的位置。若是考慮到用戶切換的話,須要先在mounted時記錄一下前一個索引值prevIndex

前一個索引值prevIndex和當前的索引值index去比較,若是大於了當前的index,那麼就說明是反向輪播,圖片須要向右移動

讓咱們再回到SwiperItem組件,給它添加一個動態class,用來顯示正反方向的過渡效果

不一樣方向過渡

// SwiperItem.vue文件

<template>
    <transition name="items">
        <div class="swiper-item" v-if="isShow" :class="{direction}">
            <slot></slot>
        </div>
    </transition>
</template>

<script>
export default {
    ...省略
    data() {
        return {
            index: 1,
            // 判斷方向
            direction: false
        }
    }
}
</script>

<style scoped>
...省略
/* 反向過渡 */
.items-leave-to.direction {
    transform: translateX(100%);
}
.items-enter.direction {
    transform: translateX(-100%);
}
</style>
複製代碼

判斷方向

上面的代碼爲咱們修改了不一樣方向的過渡效果,廢話再也不多說,直接看代碼吧

// Swiper.vue文件  ->  js部分

<script>
export default {
    ...省略,
    methods: {
        show() {
            this.index = this.value || this.$children[0].id;
            
            this.$children.forEach(vm => {
                this.$nextTick(() => {
                    vm.selectedId = this.index;
                });
                // 若是prevIndex大於了index就是反向輪播
                vm.direction = this.prevIndex > this.index;
                
                // 處理自動播放時的邊界狀況
                if (this.timer) {
                    if (this.prevIndex === 1 && this.index === this.len) {
                        // 處理從第1張反向跳到最後一張的條件
                        vm.direction = true;
                    } else if (this.prevIndex === this.len && this.index === 1) {
                        // 處理最後一張正向跳到第一張的條件
                        vm.direction = false;
                    }
                }
            });
        },
        change(index) {
            this.prevIndex = this.index;
            ...省略
        }
    },
    mounted() {
        this.show();
        this.len = this.$children.length;
        this.play();
        // 先記錄當前的值
        this.prevIndex = this.index;
    }
}
</script>
複製代碼

簡單梳理

  1. 修改show方法
  • prevIndex最開始是undefined,由於在mounted按照順序調用show方法的時候,prevIndex尚未賦值爲index
  • 至關於prevIndex > index第一次比較的時候,是undefined和1在比較,以後在每次切換圖片,調用change方法的時候又給prevIndex賦了index的值
  • $nextTick是爲了在vm.direction數據被修改了,等待DOM更新後再修改對應的selectedId以對應展現對應圖片
  1. 修改change方法
  • change方法修改的地方不多,this.prevIndex = this.index就是在每次切換的時候把上一次的index賦給了prevIndex
  • 好比: 最開始prevIndex爲undefined,index爲1;當prevIndex爲1的時候,index爲2,以此類推的賦值,這樣你們就可以理解了吧

至此,咱們把左右切換的過渡效果代碼邏輯搞定了,還差最後一步了,讓咱們給輪播圖添加touch事件,讓用戶能夠左右切換圖片

添加touch事件

上面已經完成了判斷方向的邏輯,用戶左右切換簡直是小菜一碟了,首先要給元素上添加touch事件

// Swiper.vue文件

<template>
    <div class="swiper">
        <div class="view" @touchstart="touchstart" @touchend="touchend">
            <slot></slot>
        </div>
        ...省略
    </div>
</template>

<script>
export default {
    ...省略,
    methods: {
        touchstart(e) {
            // 剛觸摸屏幕時記錄一個x座標
            this.startX = e.touches[0].pageX;
            // 而且中止自動播放
            this.stop();
        },
        touchend(e) {
            // 計算開始點和手指擡起時離開點的座標差值
            let disX = e.changedTouches[0].pageX - this.startX;
            
            // 若是小於0就表示是正向切換(向左滑動)
            // 反之,就是反向切換(向右滑動)
            if (disX < 0) {
                this.change(this.index + 1);
            } else {
                this.change(this.index - 1);
            }
            // 切換完畢後,繼續進行自動播放
            this.play();
        },
        ...省略
    }
}
</script>
複製代碼

寫到這裏就算完成了,讓咱們再來一塊兒看看效果吧

最後的最後

因爲組件裏用到了定時器,那麼別忘記了在beforeDestroy的時候,調用stop方法清除一下定時器

學無止境

原本還想寫兩個別的可複用組件,不過怕字數太多,你們看的疲勞了,就點到爲止吧,我也會把相應的地址發出來,讓你們方便參考

拾人牙慧,學無止境,感謝你們觀看了

相關文章
相關標籤/搜索