亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

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

2019-11-19 11:20:55
字體:
來源:轉載
供稿:網友

背景

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

源碼地址:vue-hooks-poc

為什么要用hooks?

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

  • 跨組件代碼難以復用
  • 大組件,維護困難,顆粒度不好控制,細粒度劃分時,組件嵌套存層次太深-影響性能
  • 類組件,this不可控,邏輯分散,不容易理解
  • mixins具有副作用,邏輯互相嵌套,數據來源不明,且不能互相消費

當一個模版依賴了很多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 = 0currentInstance = thisisMounting = !this._vnodeconst ret = render(h, this.$attrs, this.$props)currentInstance = nullreturn ret}}}

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

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

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

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

  • currentInstance:緩存當前的vue實例
  • isMounting:render是否為首次渲染

isMounting = !this._vnode

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

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

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

  • callIndex:屬性索引,當往options上掛載屬性時,使用callIndex作為唯一當索引標識。

vue options上聲明的幾個本地變量:

  • _state:放置響應式數據
  • _refsStore:放置非響應式數據,且返回引用類型
  • _effectStore:存放副作用邏輯和清理邏輯
  • _computedStore:存放計算屬性

最后,withHooks的回調函數,傳入了attrs和$props作為入參,且在渲染完當前組件后,重置全局變量,以備渲染下個組件。

useData

const data = useData(initial)export function useData(initial) {const id = ++callIndexconst state = currentInstance.$data._stateif (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 = ++callIndexconst state = currentInstance.$data._stateconst 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 = ++callIndexconst { _refsStore: refs } = currentInstancereturn 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 + 1setCount(sum)num.current = sumconsole.log(count, num.current);}return (<Button onClick={log}>{count}{num.current}</Button>)})

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

0 11 22 33 44 5

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

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

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

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

useEffect

useEffect(function ()=>{// 副作用邏輯return ()=> {// 清理邏輯}}, [deps])export function useEffect(rawEffect, deps) {ensureCurrentInstance()const id = ++callIndexif (isMounting) {const cleanup = () => {const { current } = cleanupif (current) {current()cleanup.current = null}}const effect = function() {const { current } = effectif (current) {cleanup.current = current.call(this)effect.current = null}}effect.current = rawEffectcurrentInstance._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 = [] } = recordrecord.deps = depsif (!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 = ++callIndexconst store = currentInstance._computedStoreif (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.$optionsif (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.$optionsif (hooks && render) {// 改寫組件的render函數this.$options.render = function(h) {callIndex = 0currentInstance = thisisMounting = !this._vnode// 默認傳入props屬性const hookProps = hooks(this.$props)// _self指示本身組件實例Object.assign(this._self, hookProps)const ret = render.call(this, h)currentInstance = nullreturn 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真香(逃)

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲欧美国产另类| 久久久久久久久久久免费精品| 91久久综合亚洲鲁鲁五月天| 日韩av观看网址| 成人网在线免费观看| 欧美性色视频在线| 国产女人18毛片水18精品| 理论片在线不卡免费观看| 法国裸体一区二区| 精品国产一区二区三区四区在线观看| 日韩精品在线观| 亚洲精品国产精品国产自| 欧美在线xxx| 国产精品久久久久av免费| 国产成人一区二区三区| 欧美日韩视频免费播放| 神马久久桃色视频| 91久久精品国产91久久| 国产精品爽爽爽爽爽爽在线观看| 日韩欧美在线播放| 欧美亚洲日本黄色| 国产精品福利小视频| 久久久久中文字幕2018| 国产91av在线| 国产精品久久久久免费a∨大胸| 欧美综合国产精品久久丁香| 91在线免费视频| 成人在线国产精品| 欧美精品在线视频观看| 亚洲精品视频久久| 亚洲性夜色噜噜噜7777| 亚洲精选在线观看| 日韩电影大全免费观看2023年上| 九九综合九九综合| 一区国产精品视频| 午夜精品一区二区三区视频免费看| 欧美极品美女电影一区| 日韩欧美福利视频| 欧美成人一区二区三区电影| 姬川优奈aav一区二区| 欧美日韩中文在线观看| 国产第一区电影| 久久亚洲电影天堂| 欧美成人精品一区二区三区| 欧美日韩亚洲视频| 国产剧情日韩欧美| 欧美一区二区大胆人体摄影专业网站| 欧美日韩中文字幕| 美女av一区二区| 国产精品一二区| 亚洲xxxx在线| 岛国av一区二区三区| 日韩人体视频一二区| 欧美午夜宅男影院在线观看| 日韩精品视频在线播放| 久久久久99精品久久久久| 国产精品第一页在线| 一区二区三区视频免费在线观看| 色综合天天狠天天透天天伊人| 美女福利精品视频| 中文字幕av一区二区三区谷原希美| 中文字幕日韩欧美精品在线观看| 久久亚洲精品网站| 亚洲国产欧美日韩精品| 国产丝袜一区二区三区免费视频| 国产成人精品久久二区二区| 国产精品久久久久久中文字| 欧美激情久久久久久| 日韩精品视频在线观看网址| 美女av一区二区| 亚洲综合自拍一区| 亚洲欧美日韩一区二区在线| 久久五月天色综合| 亚洲一区二区在线播放| 中文字幕亚洲二区| 欧美激情一区二区三区高清视频| 久久综合色影院| 国产精品久久久久久久久男| 国产91色在线免费| 91精品视频免费| 国产精品黄色影片导航在线观看| 国产精品亚洲综合天堂夜夜| 国产中文欧美精品| 欧美激情久久久久久| 黄色精品一区二区| 国产精品偷伦视频免费观看国产| 九九精品视频在线观看| 欧美激情乱人伦一区| 国产精品视频久| 欧美色视频日本高清在线观看| 欧美性少妇18aaaa视频| 91成人在线播放| 国产精品久久av| 国产精品女主播| 久久夜精品香蕉| 国产一区二区三区高清在线观看| 国产精品久久久久久中文字| 欧美成年人视频| 日本一本a高清免费不卡| 国产精欧美一区二区三区| 亚洲国产古装精品网站| 欧美成aaa人片免费看| 欧美激情小视频| 亚洲www在线| 97超视频免费观看| 91精品国产色综合| 国产成人综合久久| 久久99精品久久久久久青青91| 久久在精品线影院精品国产| 在线不卡国产精品| 97视频网站入口| 亚洲大胆人体av| 国产精品视频永久免费播放| 亚洲伊人成综合成人网| 国产精品自拍视频| 亚洲香蕉成人av网站在线观看| 亚洲精美色品网站| 国产综合视频在线观看| 中文字幕亚洲一区二区三区| 国产成人精品久久久| 欧美日韩综合视频网址| 亚洲成人av片在线观看| 国产精品免费福利| 久久深夜福利免费观看| 在线视频亚洲欧美| 国产精国产精品| 国产精品视频免费观看www| 精品视频在线播放免| 欧美成aaa人片免费看| 黄色成人av网| 亚洲美女精品成人在线视频| 欧美性猛交xxxx黑人猛交| 中日韩午夜理伦电影免费| 国产成人久久久精品一区| 欧美精品日韩三级| 国产一区二区视频在线观看| 国产精品99蜜臀久久不卡二区| 九九精品在线视频| 国产精品入口福利| 成人亚洲欧美一区二区三区| 亚洲精品suv精品一区二区| 欧洲中文字幕国产精品| 91精品综合视频| 在线观看免费高清视频97| 国产欧美精品一区二区三区介绍| 一本色道久久综合狠狠躁篇的优点| 日韩美女视频免费看| 国产午夜精品一区理论片飘花| 国产精品偷伦免费视频观看的| 欧美精品videossex88| 欧美专区第一页| 欧美老妇交乱视频| 久久精品国产精品亚洲| 久久久久亚洲精品国产| 久久久亚洲国产| 97在线视频精品| 91久久国产婷婷一区二区| 国产精品亚洲视频在线观看| 高清亚洲成在人网站天堂| 久久久午夜视频| 中文字幕日韩精品有码视频| 亚洲精品suv精品一区二区| 日韩经典一区二区三区| 亚洲精品电影网在线观看|