日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

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

新聞中心

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

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

為王益等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計(jì)制作服務(wù),及王益網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為成都網(wǎng)站設(shè)計(jì)、做網(wǎng)站、王益網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(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)在于對(duì)應(yīng)另外一個(gè)資源而言,我們貌似還需要寫類似的模板代碼,因此可以將這一堆代碼進(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
    }
}

注意這是一個(gè)非常通用的方法,假設(shè)現(xiàn)在需求封裝其他的請(qǐng)求,處理起來也是非常方便的,不需要再一遍遍地處理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) => {
        // 封裝請(qǐng)求返回值
        return fetchUserList(params).then(res => {
            console.log(res)
            if (res.code === 200) {
                return res.data
            }
            return []
        })
    })
    // ...
}

思考

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

useEventBus

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

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

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

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

實(shí)現(xiàn)

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

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

// 事件總線,全局單例
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()
    // 劫持兩個(gè)監(jiān)聽方法,收集當(dāng)前組件對(duì)應(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)
    }

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

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

思考

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

  • DOM事件注冊(cè)addEventListener和removeEventListener
  • 計(jì)時(shí)器setTimeout和clearTimeout
  • 網(wǎng)絡(luò)請(qǐng)求request和abort

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

useModel

參考:

  • hox源碼[2]

背景

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

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

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

在某些場(chǎng)景下我們希望多個(gè)組件可以共享同一個(gè)計(jì)數(shù)器,而不是每個(gè)組件自己獨(dú)立的計(jì)數(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á)到相同的效果,封裝就變得意義不大;此外,如果單單只是為了這個(gè)功能就為項(xiàng)目增加了vuex依賴,顯得十分笨重。

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

實(shí)現(xiàn)

整個(gè)思路也比較簡(jiǎn)單,使用一個(gè)Map來保存某個(gè)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)
}

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

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

思考

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

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

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

useReducer

redux的思想可以簡(jiǎn)單概括為

  • store維護(hù)全局的state數(shù)據(jù)狀態(tài),
  • 各個(gè)組件可以按需使用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)一個(gè)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ù)一個(gè)全局的store,因此可以使用上面的useModel

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

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



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

思考

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

useDebounce與useThrottle

背景

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

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

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

    return {
        keyword
    }
}

然后在視圖中引入



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

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

貌似不需要我們?cè)兕~外編寫任何代碼,直接將debounce?方法重命名為useDebounce即可,為了湊字?jǐn)?shù),我們還是改裝一下,同時(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)題:手把手教你封裝幾個(gè)Vue3中很有用的組合式API
文章出自:http://www.dlmjj.cn/article/dphsejg.html