本項目綜合運用了 Vue3.0
的新特性,適合新手學習😁javascript
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 代碼插件食用Ψ( ̄∀ ̄)Ψ。html
如下是項目運用到的依賴,@vue/composition-api
配合 vue
模塊讓咱們 Vue2.0
版本能夠搶先體驗 Vue3.0
的新特性,axios
是輔助咱們發送網絡請求獲得數據的工具庫,weui
是一套與微信原生視覺一致的基礎樣式庫,方便咱們快速搭建項目頁面。vue
"@vue/composition-api": "^0.3.4", "axios": "^0.19.0", "core-js": "^3.4.3", "vue": "^2.6.10", "weui": "^2.1.3"
├── src │ ├── App.vue # 組件入口 │ ├── assets # 資源目錄 │ ├── stores/index.js # 狀態管理 │ ├── components # 組件目錄 │ │ ├── Header.vue # 頭部組件 │ │ ├── Search.vue # 搜索框組件 │ │ ├── Panel.vue # 列表組件 │ ├── main.js # 項目入口 ├── public # 模板文件 ├── vue.config.js # 腳手架配置文件 ├── screenshot # 程序截圖
npm install @vue/composition-api --save
使用 npm
命令下載了 @vue/composition-api
插件之後,引入該模塊後,須要顯式調用 Vue.use(VueCompositionApi)
,按照文檔在 main.js
引用便開啓了 Composition API
的能力。java
// main.js import Vue from 'vue' import App from './App.vue' // 1.引入Composition API模塊 import VueCompositionApi from '@vue/composition-api' Vue.config.productionTip = false // 2.不要漏了顯式調用 VueCompositionApi Vue.use(VueCompositionApi) new Vue({ render: h => h(App), }).$mount('#app')
npm install weui --save
咱們一樣使用 npm
安裝 weui
模塊,而後在 main.js
中引入 weui
的基礎樣式庫,方便咱們能夠在全局使用微信基礎樣式構建項目頁面。node
// main.js import Vue from 'vue' import App from './App.vue' // 全局引入 `weui` 的基礎樣式庫 import 'weui' import VueCompositionApi from '@vue/composition-api' Vue.config.productionTip = false Vue.use(VueCompositionApi) new Vue({ render: h => h(App), }).$mount('#app')
回到 App.vue
,保留 components
屬性值清空 <template>
模板的內容,刪除 <style>
模板,等待從新引入新的組件。react
<template> <div id="app"> Hello World </div> </template> <script> export default { name: "app", components: {} }; </script>
在 src/components
目錄下新建第一個組件,取名爲 Header.vue
寫入如下代碼,點擊查看源代碼:ios
<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
,這是一個組件的入口,讓咱們能夠運用 Vue3.0
暴露的新接口,它運行在組件被實例化時候,props
屬性被定義以後,實際上等價於 Vue2.0
版本的 beforeCreate
和 Created
這兩個生命週期,setup
返回的是一個對象,裏面的全部被返回的屬性值,都會被合併到 Vue2.0
的 render
渲染函數裏面,在單文件組件中,它將配合 <template>
模板的內容,完成 Model
到 View
之間的綁定,在將來版本中應該還會支持返回 JSX
代碼片斷。git
<template> <!-- View --> <div>{{name}}</div> </template> <script> import { reactive } from '@vue/composition-api' export default { setup() { const state = reactive({ name: 'Eno Yao' }); // return 暴露到 template 中 return { // Model ...state } } } </script>
在 setup
函數裏面, 咱們適應了 Vue3.0 的第一個新接口 reactive
它主要是處理你的對象讓它通過 Proxy
的加工變爲一個響應式的對象,相似於 Vue2.0
版本的 data
屬性,須要注意的是加工後的對象跟原對象是不相等的,而且加工後的對象屬於深度克隆的對象。github
const state = reactive({ name: 'Eno Yao' })
在 Vue2.0
中咱們可使用 props
屬性值完成父子通訊,在這裏咱們須要定義 props
屬性去定義接受值的類型,而後咱們能夠利用 setup
的第一個參數獲取 props
使用。ajax
export default { props: { // 標題 title: String, // 顏色 color: String }, setup(props) { // 這裏可使用父組件傳過來的 props 屬性值 } };
咱們在 App.vue
裏面就可使用該頭部組件,有了上面的 props
咱們能夠根據傳進來的值,讓這個頭部組件呈現不一樣的狀態。
<template> <div id="app"> <!-- 複用組件,並傳入 props 值,讓組件呈現對應的狀態 --> <Header title="Eno" color="red" /> <Header title="Yao" color="blue" /> <Header title="Wscats" color="yellow" /> </div> </template> <script> import Header from "./components/Header.vue"; export default { name: "app", components: { Header, } }; </script>
setup
函數的第二個參數是一個上下文對象,這個上下文對象中包含了一些有用的屬性,這些屬性在 Vue2.0
中須要經過 this
才能訪問到,在 vue3.0
中,訪問他們變成如下形式:
setup(props, ctx) { console.log(ctx) // 在 setup() 函數中沒法訪問到 this console.log(this) // undefined }
具體能訪問到如下有用的屬性:
完成上面的 Header.vue
咱們就編寫 Search.vue
搜索框組件,繼續再 src/components
文件夾下面新建 Search.vue
文件,點擊查看源代碼。
<template> <div :class="['weui-search-bar', {'weui-search-bar_focusing' : isFocus}]" id="searchBar"> <form class="weui-search-bar__form"> <div class="weui-search-bar__box"> <i class="weui-icon-search"></i> <input v-model="searchValue" ref="inputElement" type="search" class="weui-search-bar__input" id="searchInput" placeholder="搜索" required /> <a href="javascript:" class="weui-icon-clear" id="searchClear"></a> </div> <label @click="toggle" class="weui-search-bar__label" id="searchText"> <i class="weui-icon-search"></i> <span>搜索</span> </label> </form> <a @click="toggle" href="javascript:" class="weui-search-bar__cancel-btn" id="searchCancel">取消</a> </div> </template> <script> import { reactive, toRefs, watch } from "@vue/composition-api"; import store from "../stores"; export default { // setup至關於2.x版本的beforeCreate生命週期 setup() { // reactive() 函數接收一個普通對象,返回一個響應式的數據對象 const state = reactive({ searchValue: "", // 搜索框兩個狀態,聚焦和非聚焦 isFocus: false, inputElement: null }); // 切換搜索框狀態的方法 const toggle = () => { // 讓點擊搜索後出現的輸入框自動聚焦 state.inputElement.focus(); state.isFocus = !state.isFocus; }; // 監聽搜索框的值 watch( () => { return state.searchValue; }, () => { // 存儲輸入框到狀態 store 中心,用於組件通訊 store.setSearchValue(state.searchValue); // window.console.log(state.searchValue); } ); return { // 將 state 上的每一個屬性,都轉化爲 ref 形式的響應式數據 ...toRefs(state), toggle }; } }; </script>
能夠看到咱們上面用了不少的新屬性,咱們先介紹 toRefs
,函數能夠將 reactive()
建立出來的響應式對象,轉換爲普通的對象,只不過,這個對象上的每一個屬性節點,都是 ref()
類型的響應式數據,配合 v-model
指令能完成數據的雙向綁定,在開發中很是高效。
import { reactive, toRefs } from "@vue/composition-api"; export default { setup() { const state = reactive({ name: 'Eno Yao' }) } return { // 直接返回 state 那麼數據會是非響應式的, MV 單向綁定 // ...state, // toRefs 包裝後返回 state 那麼數據會是響應式的, MVVM 雙向綁定 ...toRefs(state), }; }
這裏的輸入框擁有兩個狀態,一個是有輸入框的狀態和無輸入框的狀態,因此咱們須要一個布爾值 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()
原生方法,在切換搜索框狀態的時候光標自動聚焦到輸入框,加強用戶體驗。
<input v-model="searchValue" ref="inputElement" />
watch()
函數用來監視某些數據項的變化,從而觸發某些特定的操做,使用以前仍是須要按需導入,監聽 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) }; } };
在這裏咱們維護一份數據來實現共享狀態管理,也就是說咱們新建一個 store.js
暴露出一個 store
對象共享 Panel
和 Search
組件的 searchValue
值,當 Search.vue
組件從輸入框接受到 searchValue
檢索值,就放到 store.js
的 store
對象中,而後把該對象注入到 Search
組件中,那麼兩個組件均可以共享 store
對象中的值,爲了方便調試咱們還分別封裝了 setSearchValue
和 getSearchValue
來去操做該 store
對象,這樣咱們就能夠跟蹤狀態的改變。
// store.js export default { state: { searchValue: "" }, // 設置搜索框的值 setSearchValue(value) { this.state.searchValue = value }, // 獲取搜索框的值 getSearchValue() { return this.state.searchValue } }
完成上面的 Search.vue
咱們緊接着編寫 Panel.vue
搜索框組件,繼續再 src/components
文件夾下面新建 Panel.vue
文件,點擊查看源代碼。
<template> <div class="weui-panel weui-panel_access"> <div v-for="(n,index) in newComputed" :key="index" class="weui-panel__bd"> <a href="javascript:void(0);" class="weui-media-box weui-media-box_appmsg"> <div class="weui-media-box__hd"> <img class="weui-media-box__thumb" :src="n.author.avatar_url" alt /> </div> <div class="weui-media-box__bd"> <h4 class="weui-media-box__title" v-text="n.title"></h4> <p class="weui-media-box__desc" v-text="n.author.loginname"></p> </div> </a> </div> <div @click="loadMore" class="weui-panel__ft"> <a href="javascript:void(0);" class="weui-cell weui-cell_access weui-cell_link"> <div class="weui-cell__bd">查看更多</div> <span class="weui-cell__ft"></span> </a> </div> </div> </template> <script> import { reactive, toRefs, onMounted, computed } from "@vue/composition-api"; import axios from "axios"; import store from "../stores"; export default { setup() { const state = reactive({ // 頁數 page: 1, // 列表數據 news: [], // 經過搜索框的值去篩選劣列表數據 newComputed: 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 }); // 發送 ajax 請求獲取列表數據 const loadMore = async () => { // 獲取列表數據 let data = await axios.get("https://cnodejs.org/api/v1/topics", { params: { // 每一頁的主題數量 limit: 10, // 頁數 page: state.page } }); // 疊加頁數 state.page += 1; state.news = [...state.news, ...data.data.data]; }; onMounted(() => { // 首屏加載的時候觸發請求 loadMore(); }); return { // 讓數據保持響應式 ...toRefs(state), // 查看更多事件 loadMore }; } }; </script>
Vue3.0
的生命週期鉤子和以前不同,新版本都是以 onXxx()
函數註冊使用,一樣須要局部引入生命週期的對應模塊:
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 }; } };
如下是新舊版本生命週期的對比:
beforeCreate
</s> -> use setup()
created
</s> -> use setup()
beforeMount
-> onBeforeMount
mounted
-> onMounted
beforeUpdate
-> onBeforeUpdate
updated
-> onUpdated
beforeDestroy
-> onBeforeUnmount
destroyed
-> onUnmounted
errorCaptured
-> onErrorCaptured
同時新版本還提供了兩個全新的生命週期幫助咱們去調試代碼:
在 Panel
列表組件中,咱們註冊 onMounted
生命週期,並在裏面觸發請求方法 loadMore
以便從後端獲取數據到數據層,這裏咱們使用的是 axios
網絡請求庫,因此咱們須要安裝該模塊:
npm install axios --save
封裝了一個請求列表數據方法,接口指向的是 Cnode
官網提供的 API
,因爲 axios
返回的是 Promise
,因此配合 async
和 await
能夠完美的編寫異步邏輯,而後結合onMounted
生命週期觸發,並將方法綁定到視圖層的查看更多按鈕上,就能夠完成列表首次的加載和點擊查看更多的懶加載功能。
// 發送 ajax 請求獲取列表數據 const loadMore = async () => { // 獲取列表數據 let data = await axios.get("https://cnodejs.org/api/v1/topics", { params: { // 每一頁的主題數量 limit: 10, // 頁數 page: state.page } }); // 疊加頁數 state.page += 1; // 合併列表數據 state.news = [...state.news, ...data.data.data]; }; onMounted(() => { // 首屏加載的時候觸發請求 loadMore(); });
接下來咱們就使用另一個屬性 computed
計算屬性,跟 Vue2.0
的使用方式很相近,一樣須要按需導入該模塊:
import { computed } from '@vue/composition-api';
計算屬性分兩種,只讀計算屬性和可讀可寫計算屬性:
// 只讀計算屬性 let newsComputed = computed(() => news.value + 1) // 可讀可寫 let newsComputed = computed({ // 取值函數 get: () => news.value + 2, // 賦值函數 set: val => { news.value = news.value - 3 } })
這裏咱們使用可讀可寫計算屬性去處理列表數據,還記得咱們上一個組件 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 }); } }
若是文章和筆記能帶您一絲幫助或者啓發,請不要吝嗇你的贊和 Star,你的確定是我前進的最大動力😁