日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
手把手教你封裝幾個Vue3中很有用的組合式API

為了拼寫方便,下文內(nèi)容均使用Hook代替Composition API。相關(guān)代碼均放在github[1]上面。

為王益等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計制作服務(wù),及王益網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為成都網(wǎng)站設(shè)計、做網(wǎng)站、王益網(wǎng)站設(shè)計,以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!

useRequest

背景

使用hook來封裝一組數(shù)據(jù)的操作是很容易的,例如下面的useBook

import {ref, onMounted} from 'vue'

function fetchBookList() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve([1, 2, 3])
        }, 1000)
    })
}

export function useBook() {
    const list = ref([])
    const loading = ref(false)
    const getList = async () => {
        loading.value = true
        const data = await fetchBookList({page: 1})
        loading.value = false
        list.value = data
    }

    onMounted(() => {
        getList()
    })

    return {
        list,
        loading,
        getList
    }
}

其中封裝了獲取資源、處理加載狀態(tài)等邏輯,看起來貌似能滿足我們的需求了

缺點(diǎn)在于對應(yīng)另外一個資源而言,我們貌似還需要寫類似的模板代碼,因此可以將這一堆代碼進(jìn)行抽象,封裝成useApi方法

實(shí)現(xiàn)

function useApi(api) {
    const loading = ref(false)
    const result = ref(null)
    const error = ref(null)

    const fetchResource = (params) => {
        loading.value = true
        return api(params).then(data => {
            // 按照約定,api返回的結(jié)果直接復(fù)制給result
            result.value = data
        }).catch(e => {
            error.value = e
        }).finally(() => {
            loading.value = false
        })
    }
    return {
        loading,
        error,
        result,
        fetchResource
    }
}

然后修改上面的useBook方法

function useBook2() {
    const {loading, error, result, fetchResource,} = useApi(fetchBookList)

    onMounted(() => {
        fetchResource({page: 1})
    })

    return {
        loading,
        error,
        list: result
    }
}

注意這是一個非常通用的方法,假設(shè)現(xiàn)在需求封裝其他的請求,處理起來也是非常方便的,不需要再一遍遍地處理loading和error等標(biāo)志量

function fetchUserList() {
    return new Promise((resolve) => {
        setTimeout(() => {
            const payload = {
                code: 200,
                data: [11, 22, 33],
                msg: 'success'
            }
            resolve(payload)
        }, 1000)
    })
}

function useUser() {
    const {loading, error, result, fetchResource,} = useApi((params) => {
        // 封裝請求返回值
        return fetchUserList(params).then(res => {
            console.log(res)
            if (res.code === 200) {
                return res.data
            }
            return []
        })
    })
    // ...
}

思考

處理網(wǎng)絡(luò)請求是前端工作中十分常見的問題,處理上面列舉到的加載、錯誤處理等,還可以包含去抖、節(jié)流、輪詢等各種情況,還有離開頁面時取消未完成的請求等,都是可以在useRequest中進(jìn)一步封裝的

useEventBus

EventBus在多個組件之間進(jìn)行事件通知的場景下還是比較有用的,通過監(jiān)聽事件和觸發(fā)事件,可以在訂閱者和發(fā)布者之間解耦,實(shí)現(xiàn)一個常規(guī)的eventBus也比較簡單

class EventBus {
    constructor() {
        this.eventMap = new Map()
    }

    on(key, cb) {
        let handlers = this.eventMap.get(key)
        if (!handlers) {
            handlers = []
        }
        handlers.push(cb)
        this.eventMap.set(key, handlers)
    }

    off(key, cb) {
        const handlers = this.eventMap.get(key)
        if (!handlers) return
        if (cb) {
            const idx = handlers.indexOf(cb)
            idx > -1 && handlers.splice(idx, 1)
            this.eventMap.set(key, handlers)
        } else {
            this.eventMap.delete(key)
        }
    }

    once(key, cb) {
        const handlers = [(payload) => {
            cb(payload)
            this.off(key)
        }]
        this.eventMap.set(key, handlers)
    }

    emit(key, payload) {
        const handlers = this.eventMap.get(key)
        if (!Array.isArray(handlers)) return
        handlers.forEach(handler => {
            handler(payload)
        })
    }
}

我們在組件初始化時監(jiān)聽事件,在交互時觸發(fā)事件,這些是很容易理解的;但很容易被遺忘的是,我們還需要在組件卸載時取消事件注冊,釋放相關(guān)的資源。

因此可以封裝一個useEventBus接口,統(tǒng)一處理這些邏輯

實(shí)現(xiàn)

既然要在組件卸載時取消注冊的相關(guān)事件,簡單的實(shí)現(xiàn)思路是:只要在注冊時(on和once?)收集相關(guān)的事件和處理函數(shù),然后在onUnmounted?的時候取消(off)收集到的這些事件即可

因此我們可以劫持事件注冊的方法,同時額外創(chuàng)建一個eventMap用于收集使用當(dāng)前接口注冊的事件

// 事件總線,全局單例
const bus = new EventBus()

export default function useEventBus() {
    let instance = {
        eventMap: new Map(),
        // 復(fù)用eventBus事件收集相關(guān)邏輯
        on: bus.on,
        once: bus.once,
        // 清空eventMap
        clear() {
            this.eventMap.forEach((list, key) => {
                list.forEach(cb => {
                    bus.off(key, cb)
                })
            })
            eventMap.clear()
        }
    }
    let eventMap = new Map()
    // 劫持兩個監(jiān)聽方法,收集當(dāng)前組件對應(yīng)的事件
    const on = (key, cb) => {
        instance.on(key, cb)
        bus.on(key, cb)
    }
    const once = (key, cb) => {
        instance.once(key, cb)
        bus.once(key, cb)
    }

    // 組件卸載時取消相關(guān)的事件
    onUnmounted(() => {
        instance.clear()
    })
    return {
        on,
        once,
        off: bus.off.bind(bus),
        emit: bus.emit.bind(bus)
    }
}

這樣,當(dāng)組價卸載時也會通過instance.clear?移除該組件注冊的相關(guān)事件,比起手動在每個組件onUnmounted時手動取消要方便很多。

思考

這個思路可以運(yùn)用在很多需要在組件卸載時執(zhí)行清理操作的邏輯,比如:

  • DOM事件注冊addEventListener和removeEventListener
  • 計時器setTimeout和clearTimeout
  • 網(wǎng)絡(luò)請求request和abort

從這個封裝也可以看見組合API一個非常明顯的優(yōu)勢:盡可能地抽象公共邏輯,而無需關(guān)注每個組件具體的細(xì)節(jié)

useModel

參考:

  • hox源碼[2]

背景

當(dāng)掌握了Hook(或者Composition API)之后,感覺萬物皆可hook,總是想把數(shù)據(jù)和操作這堆數(shù)據(jù)的方法封裝在一起,比如下面的計數(shù)器

function useCounter() {
    const count = ref(0)
    const decrement = () => {
        count.value--
    }
    const increment = () => {
        count.value++
    }
    return {
        count,
        decrement,
        increment
    }
}

這個useCounter暴露了獲取當(dāng)前數(shù)值count、增加數(shù)值decrement和減少數(shù)值increment等數(shù)據(jù)和方法,然后就可以在各個組件中愉快地實(shí)現(xiàn)計數(shù)器了

在某些場景下我們希望多個組件可以共享同一個計數(shù)器,而不是每個組件自己獨(dú)立的計數(shù)器。

一種情況是使用諸如vuex等全局狀態(tài)管理工具,然后修改useCounter的實(shí)現(xiàn)

import {createStore} from 'vuex'

const store = createStore({
    state: {
        count: 0
    },
    mutations: {
        setCount(state, payload) {
            state.count = payload
        }
    }
})

然后重新實(shí)現(xiàn)useCounter

export function useCounter2() {
    const count = computed(() => {
        return store.state.count
    })
    const decrement = () => {
        store.commit('setCount', count.value + 1)
    }
    const increment = () => {
        store.commit('setCount', count.value + 1)
    }
    return {
        count,
        decrement,
        increment
    }
}

很顯然,現(xiàn)在的useCounter2?僅僅只是store的state與mutations的封裝,直接在組件中使用store也可以達(dá)到相同的效果,封裝就變得意義不大;此外,如果單單只是為了這個功能就為項(xiàng)目增加了vuex依賴,顯得十分笨重。

基于這些問題,我們可以使用一個useModel來實(shí)現(xiàn)復(fù)用某個鉤子狀態(tài)的需求

實(shí)現(xiàn)

整個思路也比較簡單,使用一個Map來保存某個hook的狀態(tài)

const map = new WeakMap()
export default function useModel(hook) {
    if (!map.get(hook)) {
        let ans = hook()
        map.set(hook, ans)
    }
    return map.get(hook)
}

然后包裝一下useCounter

export function useCounter3() {
    return useModel(useCounter)
}

// 在多個組件調(diào)用
const {count, decrement, increment} = useCounter3()
// ...
const {count, decrement, increment} = useCounter3()

這樣,在每次調(diào)用useCounter3時,都返回的是同一個狀態(tài),也就實(shí)現(xiàn)了多個組件之間的hook狀態(tài)共享。

思考

userModel?提供了一種除vuex和provide()/inject()之外共享數(shù)據(jù)狀態(tài)的思路,并且可以很靈活的管理數(shù)據(jù)與操作數(shù)據(jù)的方案,而無需將所有state放在一起或者模塊下面。

缺點(diǎn)在于,當(dāng)不使用useModel?包裝時,useCounter就是一個普通的hook,后期維護(hù)而言,我們很難判斷某個狀態(tài)到底是全局共享的數(shù)據(jù)還是局部的數(shù)據(jù)。

因此在使用useModel處理hook的共享狀態(tài)時,還要要慎重考慮一下到底合不合適。

useReducer

redux的思想可以簡單概括為

  • store維護(hù)全局的state數(shù)據(jù)狀態(tài),
  • 各個組件可以按需使用state中的數(shù)據(jù),并監(jiān)聽state的變化
  • reducer?接收action并返回新的state,組件可以通過dispatch傳遞action觸發(fā)reducer
  • state更新后,通知相關(guān)依賴更新數(shù)據(jù)

我們甚至可以將redux的使用hook化,類似于

function reducer(state, action){
    // 根據(jù)action進(jìn)行處理
    // 返回新的state
}
const initialState = {}
const {state, dispatch} = useReducer(reducer, initialState);

實(shí)現(xiàn)

借助于Vue的數(shù)據(jù)響應(yīng)系統(tǒng),我們甚至不需要實(shí)現(xiàn)任何發(fā)布和訂閱邏輯

import {ref} from 'vue'

export default function useReducer(reducer, initialState = {}) {
    const state = ref(initialState)
     // 約定action格式為 {type:string, payload: any}
    const dispatch = (action) => {
        state.value = reducer(state.value, action)
    }
    return {
        state,
        dispatch
    }
}

然后實(shí)現(xiàn)一個useRedux?負(fù)責(zé)傳遞reducer和action

import useReducer from './index'

function reducer(state, action) {
    switch (action.type) {
        case "reset":
            return initialState;
        case "increment":
            return {count: state.count + 1};
        case "decrement":
            return {count: state.count - 1};
    }
}

function useStore() {
    return useReducer(reducer, initialState);
}

我們希望是維護(hù)一個全局的store,因此可以使用上面的useModel

export function useRedux() {
    return useModel(useStore);
}

然后就可以在組件中使用了



看起來跟我們上面useModel?的例子并沒有什么區(qū)別,主要是暴露了通用的dispatch方法,在reducer處維護(hù)狀態(tài)變化的邏輯,而不是在每個useCounter中自己維護(hù)修改數(shù)據(jù)的邏輯

思考

當(dāng)然這個redux是非常簡陋的,包括中間件、combineReducers、connect等方法均為實(shí)現(xiàn),但也為我們展示了一個最基本的redux數(shù)據(jù)流轉(zhuǎn)過程。

useDebounce與useThrottle

背景

前端很多業(yè)務(wù)場景下都需要處理節(jié)流或去抖的場景,節(jié)流函數(shù)和去抖函數(shù)本身沒有減少事件的觸發(fā)次數(shù),而是控制事件處理函數(shù)的執(zhí)行來減少實(shí)際邏輯處理過程,從而提高瀏覽器性能。

一個去抖的場景是:在搜索框中根據(jù)用戶輸入的文本搜索關(guān)聯(lián)的內(nèi)容并下拉展示,由于input是一個觸發(fā)頻率很高的事件,一般需要等到用戶停止輸出文本一段時間后才開始請求接口查詢數(shù)據(jù)。

先來實(shí)現(xiàn)最原始的業(yè)務(wù)邏輯

import {ref, watch} from 'vue'

function debounce(cb, delay = 100) {
    let timer
    return function () {
        clearTimeout(timer)
        let args = arguments,
            context = this
        timer = setTimeout(() => {
            cb.apply(context, args)
        }, delay)
    }
}
export function useAssociateSearch() {
    const keyword = ref('')

    const search = () => {
        console.log('search...', keyword.value)
        // mock 請求接口獲取數(shù)據(jù)
    }

    // watch(keyword, search) // 原始邏輯,每次變化都請求
    watch(keyword, debounce(search, 1000)) // 去抖,停止操作1秒后再請求

    return {
        keyword
    }
}

然后在視圖中引入



與useApi?同理,我們可以將這個debounce的邏輯抽象出來,,封裝成一個通用的useDebounce

實(shí)現(xiàn)useDebounce

貌似不需要我們再額外編寫任何代碼,直接將debounce?方法重命名為useDebounce即可,為了湊字?jǐn)?shù),我們還是改裝一下,同時增加cancel方法

export function useDebounce(cb, delay = 100) {
    const timer = ref(null)

    let handler = function () {
        clearTimeout(timer.value)
        let args = arguments,
            context = this
        timer.value = setTimeout(() => {
            cb.apply(context, args)
        }, delay)
    }

    const cancel = () => {
        clearTimeout(timer)
        timer.value = null
    }

    return {
        handler,
        cancel
    }
}

實(shí)現(xiàn)useThrottle

節(jié)流與去抖的封裝方式基本相同,只要知道throttle的實(shí)現(xiàn)就可以了。

export function useThrottle(cb, duration = 100) {
    let
分享標(biāo)題:手把手教你封裝幾個Vue3中很有用的組合式API
網(wǎng)頁地址:http://m.5511xx.com/article/dphsejg.html