本專案綜合運用了 Vue3.0 的新特性。
基於 Composition API 即 Function-based API 進行改造,配合 Vue Cli,優先體驗 Vue3 特性使用單例物件模式進行元件通訊使用 axios 庫進行網路請求,weui 庫實現 UI 介面# 安裝依賴npm install# 在瀏覽器開啟localhost:8080檢視頁面,並實時熱更新npm run serve# 釋出專案npm run build
建議配合 Visual Studio Code 和 Vue 3 Snippets 程式碼外掛
Dependencies以下是專案運用到的依賴,@vue/composition-api 配合 vue 模組讓我們 Vue2.0 版本可以搶先體驗 Vue3.0 的新特性,axios 是輔助我們傳送網路請求得到資料的工具庫,weui是一套與微信原生視覺一致的基礎樣式庫,方便我們快速搭建專案頁面。
"@vue/composition-api": "^0.3.4","axios": "^0.19.0","core-js": "^3.4.3","vue": "^2.6.10","weui": "^2.1.3"
Directory Structure
├── src│ ├── App.vue # 元件入口│ ├── assets # 資源目錄│ ├── stores/index.js # 狀態管理│ ├── components # 元件目錄│ │ ├── Header.vue # 頭部元件│ │ ├── Search.vue # 搜尋框元件│ │ ├── Panel.vue # 列表元件│ ├── main.js # 專案入口├── public # 模板檔案├── vue.config.js # 腳手架配置檔案├── screenshot # 程式截圖
Composition APInpm install @vue/composition-api --save
使用 npm 命令下載了 @vue/composition-api 外掛以後,引入該模組後,需要顯式呼叫 Vue.use(VueCompositionApi) ,按照文件在 main.js 引用便開啟了 Composition API 的能力。
// main.jsimport Vue from 'vue'import App from './App.vue'// 1.引入Composition API模組import VueCompositionApi from '@vue/composition-api'Vue.config.productionTip = false// 2.不要漏了顯式呼叫 VueCompositionApiVue.use(VueCompositionApi)new Vue({ render: h => h(App),}).$mount('#app')
npm install weui --save
我們同樣使用 npm 安裝 weui 模組,然後在 main.js 中引入 weui的基礎樣式庫,方便我們可以在全域性使用微信基礎樣式構建專案頁面。
// main.jsimport Vue from 'vue'import App from './App.vue'// 全域性引入 `weui` 的基礎樣式庫import 'weui'import VueCompositionApi from '@vue/composition-api'Vue.config.productionTip = falseVue.use(VueCompositionApi)new Vue({ render: h => h(App),}).$mount('#app')
回到 App.vue,保留 components 屬性值清空 <template> 模板的內容,刪除 <style> 模板,等待重新引入新的元件。
<template> <div id="app"> Hello World </div></template><script>export default { name: "app", components: {}};</script>
在 src/components 目錄下新建第一個元件,取名為 Header.vue 寫入以下程式碼:
<template> <header :style="{ backgroundColor: color?color:defaultColor }">{{title}}</header></template><script>import { reactive } from "@vue/composition-api";export default { // 父元件傳遞進來更改該頭部元件的屬性值 props: { // 標題 title: String, // 顏色 color: String }, setup() { const state = reactive({ defaultColor: "red" }); return { ...state }; }};</script><style scoped>header { height: 50px; width: 100%; line-height: 50px; text-align: center; color: white;}</style>
setup
這裡運用了一個全新的屬性 setup ,這是一個元件的入口,讓我們可以運用 Vue3.0 暴露的新介面,它執行在元件被例項化時候,props 屬性被定義之後,實際上等價於 Vue2.0 版本的 beforeCreate 和 Created 這兩個生命週期,setup 返回的是一個物件,裡面的所有被返回的屬性值,都會被合併到 Vue2.0 的 render 渲染函式裡面,在單檔案元件中,它將配合 <template> 模板的內容,完成 Model 到 View 之間的繫結,在未來版本中應該還會支援返回 JSX 程式碼片段。
conststate = reactive({name:'Eno Yao'})
props
在 Vue2.0 中我們可以使用 props 屬性值完成父子通訊,在這裡我們需要定義 props 屬性去定義接受值的型別,然後我們可以利用 setup 的第一個引數獲取 props 使用。
export default { props: { // 標題 title: String, // 顏色 color: String }, setup(props) { // 這裡可以使用父元件傳過來的 props 屬性值 }};
我們在 App.vue 裡面就可以使用該頭部元件,有了上面的 props 我們可以根據傳進來的值,讓這個頭部元件呈現不同的狀態。
contextsetup 函式的第二個引數是一個上下文物件,這個上下文物件中包含了一些有用的屬性,這些屬性在 Vue2.0 中需要通過 this 才能訪問到,在 vue3.0 中,訪問他們變成以下形式:
setup(props, ctx) { console.log(ctx) // 在 setup() 函式中無法訪問到 this console.log(this) // undefined}
具體能訪問到以下有用的屬性:
rootparentrefsattrslistenersisServerssrContextemit完成上面的 Header.vue 我們就編寫 Search.vue 搜尋框元件,繼續再 src/components 資料夾下面新建 Search.vue 檔案,點選檢視原始碼。
template refs這裡的輸入框擁有兩個狀態,一個是有輸入框的狀態和無輸入框的狀態,所以我們需要一個布林值 isFocus 來控制狀態,封裝了一個 toggle 方法,讓 isFocus 值切換真和假兩個狀態。
const toggle = () => { // isFocus 值取反 state.isFocus = !state.isFocus;};
然後配合 v-bind:class 指令,讓 weui-search-bar_focusing 類名根據 isFocus 值決定是否出現,從而更改搜尋框的狀態。
<div:class="['weui-search-bar', {'weui-search-bar_focusing' : isFocus}]"id="searchBar">
這裡的搜尋輸入框放入了 v-model 指令,用於接收使用者的輸入資訊,方便後面配合列表元件執行檢索邏輯,還放入了 ref 屬性,用於獲取該 <input/> 標籤的元素節點,配合state.inputElement.focus() 原生方法,在切換搜尋框狀態的時候游標自動聚焦到輸入框,增強使用者體驗。
watchwatch() 函式用來監視某些資料項的變化,從而觸發某些特定的操作,使用之前還是需要按需匯入,監聽 searchValue 的變化,然後觸發回撥函式裡面的邏輯,也就是監聽使用者輸入的檢索值,然後觸發回撥函式的邏輯把 searchValue 值存進我們建立 store 物件裡面,方面後面和 Panel.vue 列表元件進行資料通訊:
import { reactive, watch } from "@vue/composition-api";import store from "../stores";export default { setup() { const state = reactive({ searchValue: "", }); // 監聽搜尋框的值 watch( () => { return state.searchValue; }, () => { // 儲存輸入框到狀態 store 中心,用於元件通訊 store.setSearchValue(state.searchValue); } ); return { ...toRefs(state) }; }};
state management在這裡我們維護一份資料來實現共享狀態管理,也就是說我們新建一個 store.js 暴露出一個 store 物件共享 Panel 和 Search 元件的 searchValue 值,當 Search.vue 元件從輸入框接受到 searchValue 檢索值,就放到 store.js 的 store 物件中,然後把該物件注入到 Search 元件中,那麼兩個元件都可以共享 store 物件中的值,為了方便除錯我們還分別封裝了 setSearchValue 和 getSearchValue 來去操作該 store 物件,這樣我們就可以跟蹤狀態的改變。
// store.jsexport default { state: { searchValue: "" }, // 設定搜尋框的值 setSearchValue(value) { this.state.searchValue = value }, // 獲取搜尋框的值 getSearchValue() { return this.state.searchValue }}
完成上面的 Search.vue 我們緊接著編寫 Panel.vue 搜尋框元件,繼續再 src/components 資料夾下面新建 Panel.vue 檔案。
import { onMounted, onUpdated, onUnmounted } from "@vue/composition-api";export default { setup() { const loadMore = () => {}; onMounted(() => { loadMore(); }); onUpdated(() => { console.log('updated!') }) onUnmounted(() => { console.log('unmounted!') }) return { loadMore }; }};
以下是新舊版本生命週期的對比:
<s>beforeCreate</s> -> use setup()<s>created</s> -> use setup()beforeMount -> onBeforeMountmounted -> onMountedbeforeUpdate -> onBeforeUpdateupdated -> onUpdatedbeforeDestroy -> onBeforeUnmountdestroyed -> onUnmountederrorCaptured -> onErrorCaptured同時新版本還提供了兩個全新的生命週期幫助我們去除錯程式碼:
onRenderTrackedonRenderTriggered在 Panel 列表元件中,我們註冊 onMounted 生命週期,並在裡面觸發請求方法 loadMore 以便從後端獲取資料到資料層,這裡我們使用的是 axios 網路請求庫,所以我們需要安裝該模組:
npm install axios --save
封裝了一個請求列表資料方法,介面指向的是 Cnode 官網提供的 API ,由於 axios 返回的是 Promise ,所以配合 async 和 await 可以完美的編寫非同步邏輯,然後結合onMounted 生命週期觸發,並將方法繫結到檢視層的檢視更多按鈕上,就可以完成列表首次的載入和點選檢視更多的懶載入功能。
computed接下來我們就使用另外一個屬性 computed 計算屬性,跟 Vue2.0 的使用方式很相近,同樣需要按需匯入該模組:
import{ computed }from'@vue/composition-api';
計算屬性分兩種,只讀計算屬性和可讀可寫計算屬性:
這裡我們使用可讀可寫計算屬性去處理列表資料,還記得我們上一個元件 Search.vue 嗎,我們可以結合使用者在搜尋框輸入的檢索值,配合 computed 計算屬性來篩選對我們使用者有用列表資料,所以我們首先從 store 的共享例項裡面拿到 Search.vue 搜尋框共享的 searchValue ,然後利用原生字串方法 indexOf 和 陣列方法 filter 來過濾列表的資料,然後重新返回新的列表資料 newsComputed,並在檢視層上配合 v-for 指令去渲染新的列表資料,這樣做既可以在沒搜尋框檢索值的時候返回原列表資料 news ,而在有搜尋框檢索值的時候返回新列表資料 newsComputed。
import store from "../stores";export default { setup() { const state = reactive({ // 原列表資料 news: [], // 通過搜尋框的值去篩選後的新列表資料 newsComputed: computed(() => { // 判斷是否輸入框是否輸入了篩選條件,如果沒有返回原始的 news 陣列 if (store.state.searchValue) { return state.news.filter(item => { if (item.title.indexOf(store.state.searchValue) >= 0) { return item; } }); } else { return state.news; } }), searchValue: store.state }); }}