基于子組件構建 Vue 列表組件并實現(xiàn)視圖模式切換功能
上篇教程學院君給大家演示了如何將文章發(fā)布表單組件拆分成多個子組件,然后基于這些子組件構建登錄表單,今天我們來看看如何基于子組件構建文章列表組件。
我們將基于 Bootstrap 提供的列表組件樣式代碼進行演示,所以只需要關注 Vue 和 JavaScript 代碼即可。
一、編寫列表骨架組件
在 resources/js/components 目錄下新建 list 子目錄來存放列表骨架組件和列表項子組件,然后在該目錄下新建一個從 Bootstrap 的列表組件抽象出來的、一個可以被所有 Vue 列表組件共用的骨架組件 ListSection.vue:
<style scoped>
.card-header h5 {
margin-top: 0.5em;
display: inline-block;
}
.card-header .view-mode {
float: right;
}
</style>
<template>
<div class="card">
<div class="card-header">
<h5><slot name="title"></slot></h5>
<button class="btn btn-success view-mode" @click.prevent="switch_view_mode">
{{ view.switch_to }}
</button>
</div>
<div class="card-body">
<ul class="list-group" v-if="view.mode === 'list'">
<slot></slot>
</ul>
<div class="row row-cols-1 row-cols-md-3" v-else>
<slot></slot>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['view_mode'],
data() {
return {
view: {
'mode': this.view_mode,
'switch_to': this.view_mode === 'list' ? '卡片視圖' : '列表視圖'
}
}
},
methods: {
switch_view_mode() {
if (this.view.mode === 'list') {
this.view.mode = 'card';
this.view.switch_to = '列表視圖';
} else {
this.view.mode = 'list';
this.view.switch_to = '卡片視圖';
}
this.$emit('view-mode-changed', this.view.mode)
}
}
}
</script>
除了基本的 HTML 模板代碼之外,我們還在這個骨架組件中實現(xiàn)了列表視圖模式切換功能,比如列表視圖(view.mode 值為 list)、卡片視圖(view.mode 值為 card),默認展示什么視圖模式,由父級作用域中編寫的具體列表組件決定,然后通過 props 屬性將對應的視圖模式傳遞到骨架組件即可。
你可以通過點擊列表組件標題右側的切換按鈕來切換列表視圖模式,這個點擊事件會調用 switch_view_mode 方法,該方法通過對相應的模型屬性進行設置以便結合 Vue 組件的數(shù)據綁定機制來完成視圖模式的切換,此外,該方法還會通過 $emit 觸發(fā)父級作用域中定義的 view-mode-changed 事件函數(shù),從而將切換后的視圖模式傳遞給父級作用域。關于該事件函數(shù)的定義我們馬上會在引入 ListSection 的文章列表組件中看到。
對于列表標題、不同視圖模式對應的列表項渲染這些與具體的列表組件相關的東西,都通過插槽交由調用該骨架組件的父級作用域對應的列表組件去決定和定制。
二、列表視圖對應列表項子組件
我們先在 list 子目錄下創(chuàng)建列表視圖對應的列表項子組件 ListItem.vue:
<template>
<li class="list-group-item">
<a :href="url">
<slot></slot>
</a>
</li>
</template>
<script>
export default {
props: ['url']
}
</script>
整體代碼非常簡單,只是渲染一個 li 元素而言,相關動態(tài)屬性和文本從父級作用域通過 props 和插槽傳遞過來。
三、卡片視圖對應列表項子組件
然后在同級目錄下新建卡片視圖對應的列表項子組件 CardItem.vue:
<template>
<div class="col mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title"><slot></slot></h5>
<a :href="url" class="btn btn-primary">點擊查看</a>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['url']
}
</script>
和上面的列表項子組件一樣,非常簡單,不多做介紹了。
四、基于子組件構建文章列表組件
接下來,我們就可以組合上面的列表骨架組件和列表項子組件構建文章列表了,在 resources/js/components 目錄下新建 PostList.vue,編寫組件代碼如下:
<style scoped>
.post-list {
margin-top: 50px;
}
</style>
<template>
<div class="post-list">
<ListSection :view_mode="view_mode" @view-mode-changed="change_view_mode">
<template #title>文章列表</template>
<template v-if="view_mode === 'list'">
<ListItem v-for="post in posts" :key="post.id" :url="post.url">
{{ post.title }}
</ListItem>
</template>
<template v-else>
<CardItem v-for="post in posts" :key="post.id" :url="post.url">
{{ post.title }}
</CardItem>
</template>
</ListSection>
</div>
</template>
<script>
import ListSection from "./list/ListSection";
import ListItem from "./list/ListItem";
import CardItem from "./list/CardItem";
export default {
components: {CardItem, ListItem, ListSection},
data() {
return {
posts: [],
view_mode: 'list'
}
},
mounted() {
axios.get('/get_posts').then(resp => {
this.posts = resp.data;
}).catch(error => {
console.log('從服務端加載文章數(shù)據失敗');
});
},
methods: {
change_view_mode(mode) {
this.view_mode = mode;
}
}
}
</script>
在 PostList 組件中,我們通過引入 ListSection、ListItem、CardItem 來使用它們,并填充相應的 props 屬性和插槽內容,默認的視圖模式是 list,即列表視圖。
在這個父級作用域中,我們基于列表視圖模式 view_mode 的值來判斷使用列表視圖列表項子視圖循環(huán)渲染文章數(shù)據,還是使用卡片視圖列表項子視圖來循環(huán)渲染文章數(shù)據。如果 ListSection 子組件中的視圖模式發(fā)生變更,會觸發(fā)這里定義的 view-mode-changed 事件,并執(zhí)行 change_view_mode 方法修改本組件中的 view_mode 值,進而基于數(shù)據綁定切換不同樣式子組件的渲染。
這里我們還定義了 mounted 鉤子函數(shù)來初始化文章列表數(shù)據。關于 /get_posts 接口的后端路由實現(xiàn),我們馬上會介紹。
五、創(chuàng)建引入文章列表組件的視圖
至此,我們就完成了 Vue 文章列表組件的開發(fā),在 resources/js/app.js 中引入它:
Vue.component('post-list', require('./components/PostList.vue').default);
接著在 resources/views 下新建 posts.blade.php 來渲染文章列表組件:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>文章列表</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body class="antialiased">
<div id="app" class="container">
<post-list></post-list>
</div>
<script src="{{ asset('js/app.js') }}" type="text/javascript"></script>
</body>
</html>
注:任意引入
js/app.js和css/app.css文件的 HTML 模板均可正常渲染post-list這個 Vue 組件,當然需要將其放置到div[id=app]容器中,因為 Vue 實例是掛載在它之上的。
六、注冊后端路由
為了正常渲染這個 posts 視圖,需要在 Laravel 后端注冊如下兩個路由:
Route::get('/posts', function () {
return view('posts');
});
Route::get('/get_posts', function () {
return [
[
'id' => 1,
'url' => url('/post/1'),
'title' => '測試文章1'
],
[
'id' => 2,
'url' => url('/post/2'),
'title' => '測試文章2'
],
[
'id' => 3,
'url' => url('/post/3'),
'title' => '測試文章3'
],
[
'id' => 4,
'url' => url('/post/4'),
'title' => '測試文章4'
],
[
'id' => 5,
'url' => url('/post/5'),
'title' => '測試文章5'
]
];
});
在 /get_posts 路由中,我們通過一個數(shù)組模擬文章數(shù)據并返回。
七、在瀏覽器中查看文章列表頁面
如果你后臺啟動了 npm run watch 和 php artisan serve 此時就可以通過 http://localhost:3002/posts(配置了 BrowserSync,所以端口是 3002)訪問文章列表頁了:

你可以通過點擊右上角的綠色切換按鈕在列表和卡片兩個視圖模式之間任意切換:

好了,關于基于子組件構建文章列表組件功能我們就介紹到這里,下篇教程,學院君來給大家演示如何在 Vue 組件中使用過濾器對模型數(shù)據進行格式化。
本系列教程首發(fā)在Laravel學院(laravelacademy.org),你可以點擊頁面左下角閱讀原文鏈接查看最新更新的教程。
