10個Vue開發技巧助力成為更好的工程師(二)

来源:https://www.cnblogs.com/chanwahfung/archive/2020/07/22/13358667.html
-Advertisement-
Play Games

優雅更新props 更新 prop 在業務中是很常見的需求,但在子組件中不允許直接修改 prop,因為這種做法不符合單向數據流的原則,在開發模式下還會報出警告。因此大多數人會通過 $emit 觸發自定義事件,在父組件中接收該事件的傳值來更新 prop。 child.vue: export defal ...


優雅更新props

更新 prop 在業務中是很常見的需求,但在子組件中不允許直接修改 prop,因為這種做法不符合單向數據流的原則,在開發模式下還會報出警告。因此大多數人會通過 $emit 觸發自定義事件,在父組件中接收該事件的傳值來更新 prop

child.vue:

export defalut {
    props: {
        title: String  
    },
    methods: {
        changeTitle(){
            this.$emit('change-title', 'hello')
        }
    }
}

parent.vue:

<child :title="title" @change-title="changeTitle"></child>
export default {
    data(){
        return {
            title: 'title'
        }  
    },
    methods: {
        changeTitle(title){
            this.title = title
        }
    }
}

這種做法沒有問題,我也常用這種手段來更新 prop。但如果你只是想單純的更新 prop,沒有其他的操作。那麼 sync 修飾符能夠讓這一切都變得特別簡單。

parent.vue:

<child :title.sync="title"></child>

child.vue:

export defalut {
    props: {
        title: String  
    },
    methods: {
        changeTitle(){
            this.$emit('update:title', 'hello')
        }
    }
}

只需要在綁定屬性上添加 .sync,在子組件內部就可以觸發 update:屬性名 來更新 prop。可以看到這種手段確實簡潔且優雅,這讓父組件的代碼中減少一個“沒必要的函數”。

參考文檔

provide/inject

這對選項需要一起使用,以允許一個祖先組件向其所有子孫後代註入一個依賴,不論組件層次有多深,併在其上下游關係成立的時間里始終生效。

簡單來說,一個組件將自己的屬性通過 provide 暴露出去,其下麵的子孫組件 inject 即可接收到暴露的屬性。

App.vue:

export default {
    provide() {
        return {
            app: this
        }
    } 
}

child.vue:

export default {
    inject: ['app'],
    created() {
        console.log(this.app) // App.vue實例
    }
}

在 2.5.0+ 版本可以通過設置預設值使其變成可選項:

export default {
    inject: {
        app: {
            default: () => ({})
        }
    },
    created() {
        console.log(this.app) 
    }
}

如果你想為 inject 的屬性變更名稱,可以使用 from 來表示其來源:

export default {
    inject: {
        myApp: {
            // from的值和provide的屬性名保持一致
            from: 'app',
            default: () => ({})
        }
    },
    created() {
        console.log(this.myApp) 
    }
}

需要註意的是 provideinject 主要在開發高階插件/組件庫時使用。並不推薦用於普通應用程式代碼中。但是某些時候,或許它能幫助到我們。

參考文檔

小型狀態管理器

大型項目中的數據狀態會比較複雜,一般都會使用 vuex 來管理。但在一些小型項目或狀態簡單的項目中,為了管理幾個狀態而引入一個庫,顯得有些笨重。

在 2.6.0+ 版本中,新增的 Vue.observable 可以幫助我們解決這個尷尬的問題,它能讓一個對象變成響應式數據:

// store.js
import Vue from 'vue'

export const state = Vue.observable({ 
  count: 0 
})

使用:

<div @click="setCount">{{ count }}</div>
import {state} from '../store.js'

export default {
    computed: {
        count() {
            return state.count
        }
    },
    methods: {
        setCount() {
            state.count++
        }
    }
}

當然你也可以自定義 mutation 來複用更改狀態的方法:

import Vue from 'vue'

export const state = Vue.observable({ 
  count: 0 
})

export const mutations = {
  SET_COUNT(payload) {
    if (payload > 0) {
        state.count = payload
    } 
  }
}

使用:

import {state, mutations} from '../store.js'

export default {
    computed: {
        count() {
            return state.count
        }
    },
    methods: {
        setCount() {
            mutations.SET_COUNT(100)
        }
    }
}

參考文檔

卸載watch觀察

通常定義數據觀察,會使用選項的方式在 watch 中配置:

export default {
    data() {
        return {
            count: 1      
        }
    },
    watch: {
        count(newVal) {
            console.log('count 新值:'+newVal)
        }
    }
}

除此之外,數據觀察還有另一種函數式定義的方式:

export default {
    data() {
        return {
            count: 1      
        }
    },
    created() {
        this.$watch('count', function(){
            console.log('count 新值:'+newVal)
        })
    }
}

它和前者的作用一樣,但這種方式使定義數據觀察更靈活,而且 $watch 會返回一個取消觀察函數,用來停止觸發回調:

let unwatchFn = this.$watch('count', function(){
    console.log('count 新值:'+newVal)
})
this.count = 2 // log: count 新值:2
unwatchFn()
this.count = 3 // 什麼都沒有發生...

$watch 第三個參數接收一個配置選項:

this.$watch('count', function(){
    console.log('count 新值:'+newVal)
}, {
    immediate: true // 立即執行watch
})

參考文檔

巧用template

相信 v-if 在開發中是用得最多的指令,那麼你一定遇到過這樣的場景,多個元素需要切換,而且切換條件都一樣,一般都會使用一個元素包裹起來,在這個元素上做切換。

<div v-if="status==='ok'">
    <h1>Title</h1>
    <p>Paragraph 1</p>
    <p>Paragraph 2</p>
</div>

如果像上面的 div 只是為了切換條件而存在,還導致元素層級嵌套多一層,那麼它沒有“存在的意義”。

我們都知道在聲明頁面模板時,所有元素需要放在 <template> 元素內。除此之外,它還能在模板內使用,<template> 元素作為不可見的包裹元素,只是在運行時做處理,最終的渲染結果並不包含它。

<template>
    <div>
        <template v-if="status==='ok'">
          <h1>Title</h1>
          <p>Paragraph 1</p>
          <p>Paragraph 2</p>
        </template>
    </div>
</template>

同樣的,我們也可以在 <template> 上使用 v-for 指令,這種方式還能解決 v-forv-if 同時使用報出的警告問題。

<template v-for="item in 10">
    <div v-if="item % 2 == 0" :key="item">{{item}}</div>
</template>

template使用v-if
template使用v-for

過濾器復用

過濾器被用於一些常見的文本格式化,被添加在表達式的尾部,由“管道”符號指示。

<div>{{ text | capitalize }}</div>
export default {
    data() {
        return {
            text: 'hello'
        }  
    },
    filters: {
        capitalize: function (value) {
            if (!value) return ''
            value = value.toString()
            return value.charAt(0).toUpperCase() + value.slice(1)
         }
    }
}

試想一個場景,不僅模板內用到這個函數,在 method 里也需要同樣功能的函數。但過濾器無法通過 this 直接引用,難道要在 methods 再定義一個同樣的函數嗎?

要知道,選項配置都會被存儲在實例的 $options 中,所以只需要獲取 this.$options.filters 就可以拿到實例中的過濾器。

export default {
    methods: {
        getDetail() {
            this.$api.getDetail({
                id: this.id
            }).then(res => {
                let capitalize = this.$options.filters.capitalize
                this.title = capitalize(res.data.title)
            })
        }
    }
}

除了能獲取到實例的過濾器外,還能獲取到全局的過濾器,因為 this.$options.filters 會順著 __proto__ 向上查找,全局過濾器就存在原型中。

自定義指令獲取實例

有的情況下,當需要對普通 DOM 元素進行底層操作,這時候就會用到自定義指令。像是項目中常用的許可權指令,它能精確到某個模塊節點。大概思路為獲取許可權列表,如果當前綁定許可權不在列表中,則刪除該節點元素。

Vue.directive('role', {
    inserted: function (el, binding, vnode) {
      let role = binding.value
      if(role){
        const applist = sessionStorage.getItem("applist")
        const hasPermission = role.some(item => applist.includes(item)) 
        // 是否擁有許可權
        if(!hasPermission){
          el.remove() //沒有許可權則刪除模塊節點
        }
      }
    }
})

自定義指令鉤子函數共接收3個參數,包括 el (綁定指令的真實dom)、binding (指令相關信息)、vnode (節點的虛擬dom)。

假設現在業務發生變化,applist 存儲在 vuex 里, 但指令內想要使用實例上的屬性,或者是原型上的 $store。我們是沒有辦法獲取到的,因為鉤子函數內並沒有直接提供實例訪問。vnode 作為當前的虛擬dom,它裡面可是綁定到實例上下文的,這時候訪問 vnode.context 就可以輕鬆解決問題。

Vue.directive('role', {
    inserted: function (el, binding, vnode) {
      let role = binding.value
      if(role){
        // vnode.context 為當前實例
        const applist = vnode.context.$store.state.applist
        const hasPermission = role.some(item => applist.includes(item)) 
        if(!hasPermission){
          el.remove()
        }
      }
    }
})

優雅註冊插件

插件通常用來為 Vue 添加全局功能。像常用的 vue-routervuex 在使用時都是通過 Vue.use 來註冊的。Vue.use 內部會自動尋找 install 方法進行調用,接受的第一個參數是 Vue 構造函數。

一般在使用組件庫時,為了減小包體積,都是採用按需載入的方式。如果在入口文件內逐個引入組件會讓 main.js 越來越龐大,基於模塊化開發的思想,最好是單獨封裝到一個配置文件中。配合上 Vue.use,在入口文件使用能讓人一目瞭然。

vant.config.js:

import {
  Toast,
  Button
} from 'vant'

const components = {
  Toast,
  Button
}

const componentsHandler = {
  install(Vue){
    Object.keys(components).forEach(key => Vue.use(components[key]))
  }
}

export default componentsHandler

main.js:

import Vue from 'vue'
import vantCompoents from '@/config/vant.config'

Vue.config.productionTip = false

Vue.use(vantCompoents)

new Vue({
  render: h => h(App)
}).$mount('#app')

參考文檔

自動化引入模塊

在開發中大型項目時,會將一個大功能拆分成一個個小功能,除了能便於模塊的復用,也讓模塊條理清晰,後期項目更好維護。

像 api 文件一般按功能劃分模塊,在組合時可以使用 require.context 一次引入文件夾所有的模塊文件,而不需要逐個模塊文件去引入。每當新增模塊文件時,就只需要關註邏輯的編寫和模塊暴露,require.context 會幫助我們自動引入。

需要註意 require.context 並不是天生的,而是由 webpack 提供。在構建時,webpack 在代碼中解析它。

let importAll = require.context('./modules', false, /\.js$/)

class Api extends Request{
    constructor(){
        super()
        //importAll.keys()為模塊路徑數組
        importAll.keys().map(path =>{
            //相容處理:.default獲取ES6規範暴露的內容; 後者獲取commonJS規範暴露的內容
            let api = importAll(path).default || importAll(path)
            Object.keys(api).forEach(key => this[key] = api[key])
        })
    }
}

export default new Api()

require.context 參數:

  1. 文件夾路徑
  2. 是否遞歸查找子文件夾下的模塊
  3. 模塊匹配規則,一般匹配文件尾碼名

只要是需要批量引入的場景,都可以使用這種方法。包括一些公用的全局組件,只需往文件夾內新增組件即可使用,不需要再去註冊。如果還沒用上的小伙伴,一定要瞭解下,簡單實用又能提高效率。

參考文檔

路由懶載入(動態chunkName)

路由懶載入作為性能優化的一種手段,它能讓路由組件延遲載入。通常我們還會為延遲載入的路由添加“魔法註釋”(webpackChunkName)來自定義包名,在打包時,該路由組件會被單獨打包出來。

let router = new Router({
  routes: [
    {
      path:'/login',
      name:'login',
      component: import(/* webpackChunkName: "login" */ `@/views/login.vue`)
    },
    {
      path:'/index',
      name:'index',
      component: import(/* webpackChunkName: "index" */ `@/views/index.vue`)
    },
    {
      path:'/detail',
      name:'detail',
      component: import(/* webpackChunkName: "detail" */ `@/views/detail.vue`)
    }
  ]
})

上面這種寫法沒問題,但仔細一看它們結構都是相似的,作為一名出色的開發者,我們可以使用 map 迴圈來解決這種重覆性的工作。

const routeOptions = [
  {
    path:'/login',
    name:'login',
  },
  {
    path:'/index',
    name:'index',
  },
  {
    path:'/detail',
    name:'detail',
  },
]

const routes = routeOptions.map(route => {
  if (!route.component) {
    route = {
      ...route,
      component: () => import(`@/views/${route.name}.vue`)
    }
  }
  return route
})

let router = new Router({
  routes
})

在書寫更少代碼的同時,我們也把“魔法註釋”給犧牲掉了。總所周知,代碼中沒辦法編寫動態註釋。這個問題很尷尬,難道就沒有兩全其美的辦法了嗎?

強大的 webpack 來救場了,從 webpack 2.6.0 開始,占位符 [index] 和 [request] 被支持為遞增的數字或實際解析的文件名。我們可以這樣使用“魔法註釋”:

const routes = routeOptions.map(route => {
  if (!route.component) {
    route = {
      ...route,
      component: () => import(/* webpackChunkName: "[request]" */ `@/views/${route.name}.vue`)
    }
  }
  return route
})

參考文檔
參考文章

最後

往期相關文章:

10個Vue開發技巧助力成為更好的工程師


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 經過一天的的工作或者學習是否感到枯燥乏味?現在的你是否想找些樂子?下麵我將教大家製作簡單易做的3D煙花動態網頁,告別無聊與乏味。。。。。 第一步:首先新建一個文本文檔。 第二步:將以下代碼複製到該文本文檔中,保存。 <!doctype html><html><head><meta charset=" ...
  • 在移動端開發時,經常會遇到在視網膜屏幕中元素邊框變粗的問題。本文將帶你探討邊框變粗問題的產生原因及介紹目前市面上最好的解決方法。 ...
  • body { font-family: 'Lora', serif; font-size: 16px; color: #6f6767;}a { color: #6f6767;}a:hover { color: #17223f; text-decoration: none;}a:focus { out ...
  • 滾動條 常見於移動端 App 在滾動點擊進入的時候 問題描述 當我們在開發 web app 的時候,經常會遇到一個問題,就是當從一個可滾動的列表頁進入到下一個詳情頁面,然後返回列表頁面的時候,很難去還原滾動條的狀態,無法記住進來時候的位置。 以前我嘗試過很多方法: 有 vue-router 自帶的 ...
  • Typescript Class import _ from "lodash"; /**類 ${NAME}*/ export default class ${NAME} { /**v-for :key*/ forKey?: string = _.uniqueId() constructor(obj? ...
  • 想要下載快手的視頻,又不想要水印,要如何操作?小編在這裡教一個批量下載,又能自動去水印,完美解析的方法。 有粉絲發消息給我,想找一個批量去水印網站。前天晚上,有個粉絲給小編留言,告訴小編有個網站可以一鍵批量解析視頻,只要複製任意作品鏈接即可獲取作者所有的作品並解析出來。批量解析功能目前支持快手、抖音 ...
  • 蒲公英 · JELLY技術周刊 Vol.15 聽到“雲游戲”,或許我們的第一反應會是“雲玩家”而不是那些上雲的“游戲”,在這個 5G 已來的時代,雲游戲也迎來了全新的機遇。這是 5G 時代的過客?又或者這會是 5G 場景下第一個重量級應用?看完雲鷺科技的這次大會分享,你會有自己的答案。 登高遠眺 天 ...
  • //假設我有以下數據 const data = [ { 'tm': '2020-05-16', 'nm': 'haha', 'tmp': 'piouyjhjkn' }, { 'tm': '2020-06-16', 'nm': 'haha', 'tmp': 'piouyjhjkn' }, { 'tm' ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...