<span id="mktg5"></span>

<i id="mktg5"><meter id="mktg5"></meter></i>

        <label id="mktg5"><meter id="mktg5"></meter></label>
        最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關鍵字專題關鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
        問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
        當前位置: 首頁 - 科技 - 知識百科 - 正文

        深度了解vue.js中hooks的相關知識

        來源:懂視網 責編:小采 時間:2020-11-27 21:55:20
        文檔

        深度了解vue.js中hooks的相關知識

        深度了解vue.js中hooks的相關知識:背景 最近研究了vue3.0的最新進展,發現變動很大,總體上看,vue也開始向hooks靠攏,而且vue作者本人也稱vue3.0的特性吸取了很多hooks的靈感。所以趁著vue3.0未正式發布前,抓緊時間研究一下hooks相關的東西。 源碼地址:vue-hooks-poc 為
        推薦度:
        導讀深度了解vue.js中hooks的相關知識:背景 最近研究了vue3.0的最新進展,發現變動很大,總體上看,vue也開始向hooks靠攏,而且vue作者本人也稱vue3.0的特性吸取了很多hooks的靈感。所以趁著vue3.0未正式發布前,抓緊時間研究一下hooks相關的東西。 源碼地址:vue-hooks-poc 為

        背景

        最近研究了vue3.0的最新進展,發現變動很大,總體上看,vue也開始向hooks靠攏,而且vue作者本人也稱vue3.0的特性吸取了很多hooks的靈感。所以趁著vue3.0未正式發布前,抓緊時間研究一下hooks相關的東西。

        源碼地址:vue-hooks-poc

        為什么要用hooks?

        首先從class-component/vue-options說起:

      1. 跨組件代碼難以復用
      2. 大組件,維護困難,顆粒度不好控制,細粒度劃分時,組件嵌套存層次太深-影響性能
      3. 類組件,this不可控,邏輯分散,不容易理解
      4. mixins具有副作用,邏輯互相嵌套,數據來源不明,且不能互相消費
      5. 當一個模版依賴了很多mixin的時候,很容易出現數據來源不清或者命名沖突的問題,而且開發mixins的時候,邏輯及邏輯依賴的屬性互相分散且mixin之間不可互相消費。這些都是開發中令人非常痛苦的點,因此,vue3.0中引入hooks相關的特性非常明智。

        vue-hooks

        在探究vue-hooks之前,先粗略的回顧一下vue的響應式系統:首先,vue組件初始化時會將掛載在data上的屬性響應式處理(掛載依賴管理器),然后模版編譯成v-dom的過程中,實例化一個Watcher觀察者觀察整個比對后的vnode,同時也會訪問這些依賴的屬性,觸發依賴管理器收集依賴(與Watcher觀察者建立關聯)。當依賴的屬性發生變化時,會通知對應的Watcher觀察者重新求值(setter->notify->watcher->run),對應到模版中就是重新render(re-render)。

        注意:vue內部默認將re-render過程放入微任務隊列中,當前的render會在上一次render flush階段求值。

        withHooks

        export function withHooks(render) {
        return {
        data() {
        return {
        _state: {}
        }
        },
        created() {
        this._effectStore = {}
        this._refsStore = {}
        this._computedStore = {}
        },
        render(h) {
        callIndex = 0
        currentInstance = this
        isMounting = !this._vnode
        const ret = render(h, this.$attrs, this.$props)
        currentInstance = null
        return ret
        }
        }
        }

        withHooks為vue組件提供了hooks+jsx的開發方式,使用方式如下:

        export default withHooks((h)=>{
        ...
        return <span></span>
        })

        不難看出,withHooks依舊是返回一個vue component的配置項options,后續的hooks相關的屬性都掛載在本地提供的options上。

        首先,先分析一下vue-hooks需要用到的幾個全局變量:

      6. currentInstance:緩存當前的vue實例
      7. isMounting:render是否為首次渲染
      8. isMounting = !this._vnode

        這里的_vnode與$vnode有很大的區別,$vnode代表父組件(vm._vnode.parent)

        _vnode初始化為null,在mounted階段會被賦值為當前組件的v-dom

        isMounting除了控制內部數據初始化的階段外,還能防止重復re-render。

      9. callIndex:屬性索引,當往options上掛載屬性時,使用callIndex作為唯一當索引標識。
      10. vue options上聲明的幾個本地變量:

      11. _state:放置響應式數據
      12. _refsStore:放置非響應式數據,且返回引用類型
      13. _effectStore:存放副作用邏輯和清理邏輯
      14. _computedStore:存放計算屬性
      15. 最后,withHooks的回調函數,傳入了attrs和$props作為入參,且在渲染完當前組件后,重置全局變量,以備渲染下個組件。

        useData

        const data = useData(initial)
        export function useData(initial) {
        const id = ++callIndex
        const state = currentInstance.$data._state
        if (isMounting) {
        currentInstance.$set(state, id, initial)
        }
        return state[id]
        }

        我們知道,想要響應式的監聽一個數據的變化,在vue中需要經過一些處理,且場景比較受限。使用useData聲明變量的同時,也會在內部data._state上掛載一個響應式數據。但缺陷是,它沒有提供更新器,對外返回的數據發生變化時,有可能會丟失響應式監聽。

        useState

        const [data, setData] = useState(initial)
        export function useState(initial) {
        ensureCurrentInstance()
        const id = ++callIndex
        const state = currentInstance.$data._state
        const updater = newValue => {
        state[id] = newValue
        }
        if (isMounting) {
        currentInstance.$set(state, id, initial)
        }
        return [state[id], updater]
        }
        

        useState是hooks非常核心的API之一,它在內部通過閉包提供了一個更新器updater,使用updater可以響應式更新數據,數據變更后會觸發re-render,下一次的render過程,不會在重新使用$set初始化,而是會取上一次更新后的緩存值。

        useRef

        const data = useRef(initial) // data = {current: initial}
        export function useRef(initial) {
        ensureCurrentInstance()
        const id = ++callIndex
        const { _refsStore: refs } = currentInstance
        return isMounting ? (refs[id] = { current: initial }) : refs[id]
        }

        使用useRef初始化會返回一個攜帶current的引用,current指向初始化的值。我在初次使用useRef的時候總是理解不了它的應用場景,但真正上手后還是多少有了一些感受。

        比如有以下代碼:

        export default withHooks(h => {
        const [count, setCount] = useState(0)
        const num = useRef(count)
        const log = () => {
        let sum = count + 1
        setCount(sum)
        num.current = sum
        console.log(count, num.current);
        }
        return (
        <Button onClick={log}>{count}{num.current}</Button>
        )
        })

        點擊按鈕會將數值+1,同時打印對應的變量,輸出結果為:

        0 1
        1 2
        2 3
        3 4
        4 5

        可以看到,num.current永遠都是最新的值,而count獲取到的是上一次render的值。

        其實,這里將num提升至全局作用域也可以實現相同的效果。

        所以可以預見useRef的使用場景:

      16. 多次re-render過程中保存最新的值
      17. 該值不需要響應式處理
      18. 不污染其他作用域
      19. useEffect

        useEffect(function ()=>{
        // 副作用邏輯
        return ()=> {
        // 清理邏輯
        }
        }, [deps])
        export function useEffect(rawEffect, deps) {
        ensureCurrentInstance()
        const id = ++callIndex
        if (isMounting) {
        const cleanup = () => {
        const { current } = cleanup
        if (current) {
        current()
        cleanup.current = null
        }
        }
        const effect = function() {
        const { current } = effect
        if (current) {
        cleanup.current = current.call(this)
        effect.current = null
        }
        }
        effect.current = rawEffect
        currentInstance._effectStore[id] = {
        effect,
        cleanup,
        deps
        }
        currentInstance.$on('hook:mounted', effect)
        currentInstance.$on('hook:destroyed', cleanup)
        if (!deps || deps.length > 0) {
        currentInstance.$on('hook:updated', effect)
        }
        } else {
        const record = currentInstance._effectStore[id]
        const { effect, cleanup, deps: prevDeps = [] } = record
        record.deps = deps
        if (!deps || deps.some((d, i) => d !== prevDeps[i])) {
        cleanup()
        effect.current = rawEffect
        }
        }
        }

        useEffect同樣是hooks中非常重要的API之一,它負責副作用處理和清理邏輯。這里的副作用可以理解為可以根據依賴選擇性的執行的操作,沒必要每次re-render都執行,比如dom操作,網絡請求等。而這些操作可能會導致一些副作用,比如需要清除dom監聽器,清空引用等等。

        先從執行順序上看,初始化時,聲明了清理函數和副作用函數,并將effect的current指向當前的副作用邏輯,在mounted階段調用一次副作用函數,將返回值當成清理邏輯保存。同時根據依賴來判斷是否在updated階段再次調用副作用函數。
        非首次渲染時,會根據deps依賴來判斷是否需要再次調用副作用函數,需要再次執行時,先清除上一次render產生的副作用,并將副作用函數的current指向最新的副作用邏輯,等待updated階段調用。

        useMounted

        useMounted(function(){})
        export function useMounted(fn) {
        useEffect(fn, [])
        }

        useEffect依賴傳[]時,副作用函數只在mounted階段調用。

        useDestroyed

        useDestroyed(function(){})
        export function useDestroyed(fn) {
        useEffect(() => fn, [])
        }

        useEffect依賴傳[]且存在返回函數,返回函數會被當作清理邏輯在destroyed調用。

        useUpdated

        useUpdated(fn, deps)
        export function useUpdated(fn, deps) {
        const isMount = useRef(true)
        useEffect(() => {
        if (isMount.current) {
        isMount.current = false
        } else {
        return fn()
        }
        }, deps)
        }

        如果deps固定不變,傳入的useEffect會在mounted和updated階段各執行一次,這里借助useRef聲明一個持久化的變量,來跳過mounted階段。

        useWatch

        export function useWatch(getter, cb, options) {
        ensureCurrentInstance()
        if (isMounting) {
        currentInstance.$watch(getter, cb, options)
        }
        }

        使用方式同$watch。這里加了一個是否初次渲染判斷,防止re-render產生多余Watcher觀察者。

        useComputed

        const data = useData({count:1})
        const getCount = useComputed(()=>data.count)
        export function useComputed(getter) {
        ensureCurrentInstance()
        const id = ++callIndex
        const store = currentInstance._computedStore
        if (isMounting) {
        store[id] = getter()
        currentInstance.$watch(getter, val => {
        store[id] = val
        }, { sync: true })
        }
        return store[id]
        }

        useComputed首先會計算一次依賴值并緩存,調用$watch來觀察依賴屬性變化,并更新對應的緩存值。

        實際上,vue底層對computed對處理要稍微復雜一些,在初始化computed時,采用lazy:true(異步)的方式來監聽依賴變化,即依賴屬性變化時不會立刻求值,而是控制dirty變量變化;并將計算屬性對應的key綁定到組件實例上,同時修改為訪問器屬性,等到訪問該計算屬性的時候,再依據dirty來判斷是否求值。

        這里直接調用watch會在屬性變化時,立即獲取最新值,而不是等到render flush階段去求值。

        hooks

        export function hooks (Vue) {
        Vue.mixin({
        beforeCreate() {
        const { hooks, data } = this.$options
        if (hooks) {
        this._effectStore = {}
        this._refsStore = {}
        this._computedStore = {}
        // 改寫data函數,注入_state屬性
        this.$options.data = function () {
        const ret = data ? data.call(this) : {}
        ret._state = {}
        return ret
        }
        }
        },
        beforeMount() {
        const { hooks, render } = this.$options
        if (hooks && render) {
        // 改寫組件的render函數
        this.$options.render = function(h) {
        callIndex = 0
        currentInstance = this
        isMounting = !this._vnode
        // 默認傳入props屬性
        const hookProps = hooks(this.$props)
        // _self指示本身組件實例
        Object.assign(this._self, hookProps)
        const ret = render.call(this, h)
        currentInstance = null
        return ret
        }
        }
        }
        })
        }

        借助withHooks,我們可以發揮hooks的作用,但犧牲來很多vue的特性,比如props,attrs,components等。

        vue-hooks暴露了一個hooks函數,開發者在入口Vue.use(hooks)之后,可以將內部邏輯混入所有的子組件。這樣,我們就可以在SFC組件中使用hooks啦。

        為了便于理解,這里簡單實現了一個功能,將動態計算元素節點尺寸封裝成獨立的hooks:

        <template>
        <section class="demo">
        <p>{{resize}}</p>
        </section>
        </template>
        <script>
        import { hooks, useRef, useData, useState, useEffect, useMounted, useWatch } from '../hooks';
        function useResize(el) {
        const node = useRef(null);
        const [resize, setResize] = useState({});
        useEffect(
        function() {
        if (el) {
        node.currnet = el instanceof Element ? el : document.querySelector(el);
        } else {
        node.currnet = document.body;
        }
        const Observer = new ResizeObserver(entries => {
        entries.forEach(({ contentRect }) => {
        setResize(contentRect);
        });
        });
        Observer.observe(node.currnet);
        return () => {
        Observer.unobserve(node.currnet);
        Observer.disconnect();
        };
        },
        []
        );
        return resize;
        }
        export default {
        props: {
        msg: String
        },
        // 這里和setup函數很接近了,都是接受props,最后返回依賴的屬性
        hooks(props) {
        const data = useResize();
        return {
        resize: JSON.stringify(data)
        };
        }
        };
        </script>
        <style>
        html,
        body {
        height: 100%;
        }
        </style>

        使用效果是,元素尺寸變更時,將變更信息輸出至文檔中,同時在組件銷毀時,注銷resize監聽器。

        hooks返回的屬性,會合并進組件的自身實例中,這樣模版綁定的變量就可以引用了。

        hooks存在什么問題?

        在實際應用過程中發現,hooks的出現確實能解決mixin帶來的諸多問題,同時也能更加抽象化的開發組件。但與此同時也帶來了更高的門檻,比如useEffect在使用時一定要對依賴忠誠,否則引起render的死循環也是分分鐘的事情。
        與react-hooks相比,vue可以借鑒函數抽象及復用的能力,同時也可以發揮自身響應式追蹤的優勢。我們可以看尤在與react-hooks對比中給出的看法:

        整體上更符合 JavaScript 的直覺;
        不受調用順序的限制,可以有條件地被調用;
        不會在后續更新時不斷產生大量的內聯函數而影響引擎優化或是導致 GC 壓力;
        不需要總是使用 useCallback 來緩存傳給子組件的回調以防止過度更新;
        不需要擔心傳了錯誤的依賴數組給 useEffect/useMemo/useCallback 從而導致回調中使用了過期的值 —— Vue 的依賴追蹤是全自動的。

        感受

        為了能夠在vue3.0發布后更快的上手新特性,便研讀了一下hooks相關的源碼,發現比想象中收獲的要多,而且與新發布的RFC對比來看,恍然大悟。可惜工作原因,開發項目中很多依賴了vue-property-decorator來做ts適配,看來三版本出來后要大改了。

        最后,hooks真香(逃)

        聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

        文檔

        深度了解vue.js中hooks的相關知識

        深度了解vue.js中hooks的相關知識:背景 最近研究了vue3.0的最新進展,發現變動很大,總體上看,vue也開始向hooks靠攏,而且vue作者本人也稱vue3.0的特性吸取了很多hooks的靈感。所以趁著vue3.0未正式發布前,抓緊時間研究一下hooks相關的東西。 源碼地址:vue-hooks-poc 為
        推薦度:
        標簽: VUE vue.js hooks
        • 熱門焦點

        最新推薦

        猜你喜歡

        熱門推薦

        專題
        Top
        主站蜘蛛池模板: 亚洲aⅴ天堂av天堂无码麻豆| 一本岛高清v不卡免费一三区| 精品在线免费视频| 亚洲中文字幕久久精品无码2021| 亚洲国产精品专区| 四虎精品成人免费视频| 国产乱子伦精品免费视频| 久久久久国产精品免费看| 97人伦色伦成人免费视频| 免费一级一片一毛片| 亚洲AV无码成人精品区天堂 | 在线a级毛片免费视频| JLZZJLZZ亚洲乱熟无码| 亚洲电影一区二区三区| 精品国产亚洲一区二区三区在线观看| 久久99久久成人免费播放| 亚洲乱亚洲乱妇无码麻豆| 亚洲一区二区三区乱码在线欧洲| 精品国产免费人成网站| 国产V亚洲V天堂A无码| 杨幂最新免费特级毛片| 在线a级毛片免费视频| 国产成人精品亚洲一区| 亚洲精品国产精品乱码在线观看| 无码色偷偷亚洲国内自拍| 亚洲精品成人在线| 亚洲av日韩aⅴ无码色老头| 亚洲国产精品一区二区九九 | 亚洲熟妇AV日韩熟妇在线| 免费污视频在线观看| 国产乱人免费视频| 国产成人精品日本亚洲网址 | 亚洲午夜福利在线观看| 日本免费人成在线网站| 国产亚洲综合一区柠檬导航| 中文字幕免费视频| 亚洲av福利无码无一区二区| 亚洲免费网站观看视频| 两个人看的www高清免费观看| 国产91在线免费| 久久免费区一区二区三波多野|