星期

2020年03月26日

Vue3.0新特性探索

2020-03-26 07:26:43 來源:互聯網 閱讀:1636

本項目綜合運用了 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 API

npm 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 代碼片段。

<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>

reactive

在 setup 函數里面, 我們適應了 Vue3.0 的第一個新接口 reactive 它主要是處理你的對象讓它經過 Proxy 的加工變為一個響應式的對象,類似于 Vue2.0 版本的 data 屬性,需要注意的是加工后的對象跟原對象是不相等的,并且加工后的對象屬于深度克隆的對象。

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 我們可以根據傳進來的值,讓這個頭部組件呈現不同的狀態。

<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>

context

setup 函數的第二個參數是一個上下文對象,這個上下文對象中包含了一些有用的屬性,這些屬性在 Vue2.0 中需要通過 this 才能訪問到,在 vue3.0 中,訪問他們變成以下形式:

setup(props, ctx) {  console.log(ctx) // 在 setup() 函數中無法訪問到 this  console.log(this) // undefined}

具體能訪問到以下有用的屬性:

  • root
  • parent
  • refs
  • attrs
  • listeners
  • isServer
  • ssrContext
  • emit

完成上面的 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

可以看到我們上面用了很多的新屬性,我們先介紹 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),  };}

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() 原生方法,在切換搜索框狀態的時候光標自動聚焦到輸入框,增強用戶體驗。

<input  v-model="searchValue"  ref="inputElement"/>

watch

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)    };  }};

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 文件。

<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>

lifecycle hooks

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    };  }};

以下是新舊版本生命周期的對比:

  • <s>beforeCreate</s> -> use setup()
  • <s>created</s> -> use setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured

同時新版本還提供了兩個全新的生命周期幫助我們去調試代碼:

  • onRenderTracked
  • onRenderTriggered

在 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

接下來我們就使用另外一個屬性 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    });  }}


推薦閱讀:adobe是什么

巴西vs瑞士比分预测 手机pk10计划软件下载 北京快三昨天开奖结果 超级3d过滤缩水工具 福建快3投注 腾讯分分彩赚钱计划 甘肃快三遗漏号查询 老时时彩怎么出豹子 河北快三通知 甘肃11选5开奖查询结果 广东高频彩票停售的影响 河南11选5奖金多少钱 两码中特116期 体彩网四川金7乐遗漏 金道娱乐平台 mg线上娱乐游戏 绝地求生微博战队老板