背景
大きな変動を見つけvue3.0最新の開発の最近の研究では、全体的に、VUEは、フックに近づくようになった、とVUEは、また、多くのフックのインスピレーションを学んだvue3.0特性の著者として知られます。だから、時間を無駄に関連する事柄フックを見て、正式にリリースされていないvue3.0前に利用しています。
送信元アドレス:VUE-フックA-POC
なぜフックを使うのか?
まず、クラスコンポーネント/ VUE-オプションからして起動します。
- クロス・コンポーネントコードの再利用が難しいです
- ときに大型部品、メンテナンスの難しさ、粒子サイズの制御が不十分、きめ細かなパーティショニング、アセンブリは、階層が深すぎ預金を入れ子になった - パフォーマンスに影響を与えます
- クラスコンポーネントは、この制御不能なロジックが散乱し、簡単に理解されていません
- ミックスインは、副作用、相互に入れ子にされたロジック、未知のソースからのデータを有しており、相互に消費することができません
テンプレートのミックスインは、多くの時間を依存している場合は、データソースへの傾向がある不明瞭な名前の競合や問題、および開発のミックスイン互いにロジックとの論理であるとミックスイン間の分散の特性に依存するが、互いを消費することはできません。これらは非常に非常に苦痛ポイントを開発しているので、vue3.0は非常に賢明なフックに関連する機能を導入しました。
VUE-フック
問い合わせにvue-hooks
最初粗いルック前vue
応答システム:まず、vue
コンポーネントの初期化がマウントされdata
た応答処理(ローディング依存マネージャ)の性質上、次にV-DOMステンシルプロセスにコンパイル、例Watcher
全体配置を観察するために、視聴者vnode
これらのプロパティはトリガーに依存するだけでなく、Access Managerが(と依存収集頼るWatcher
オブザーバー仲間を)。プロパティが依存変更すると、それは、対応する通知するWatcher
観察者が再評価(setter->のnotify-> watcher->実行される )、 テンプレートに対応する(再描画)再描画です。
注意:内部VUEのデフォルトは現在のレンダリングは、レンダリングフラッシュステージ上で評価され、キュー内のマイクロジョブへのプロセスを再レンダリングします。
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のコンポーネント構成アイテムのオプションを返す関連プロパティは、ローカルオプションに搭載されているフックに従ってください。
まずは、分析してみましょうvue-hooks
、いくつかのグローバル変数を使用する必要があります。
- currentInstance:現在のインスタンスのVUEをキャッシュ
- isMounting:初めてかどうかをレンダリングレンダリング
isMounting = !this._vnode
复制代码
ここでは
_vnode
と$vnode
の大きな違いは、そこにある$vnode
親コンポーネントに代わって(vm._vnode.parent)
それはV-DOMの現在のコンポーネントに割り当てられます搭載段階で、NULLに初期化_vnode
内部データの初期化を制御することに加えをisMountingだけでなく、繰り返し再レンダリング防止することができます。
- callIndex:のみインデックスcallIndex識別として使用される場合、オプションのプロパティに取り付けられた属性インデックス、。
VUEオプションに宣言したいくつかのローカル変数:
- _state:応答データを配置します
- _refsStore:非応答データを配置し、参照型を返します。
- _effectStore:ストレージおよびクリーンアップ・ロジックロジック副作用
- _computedStore:プロパティを格納するために使用されます
最後に、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的使用场景:
- 多次re-render过程中保存最新的值
- 该值不需要响应式处理
- 不污染其他作用域
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依存パスは[]
、関数の副作用は、ステージを搭載のみ呼び出します。
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が装着され、更新された各段階で一度実行され、これはuseRefが永続変数を宣言する手段、スキップステージマウント。
useWatch
export function useWatch(getter, cb, options) {
ensureCurrentInstance()
if (isMounting) {
currentInstance.$watch(getter, cb, options)
}
}
复制代码
同じ$ウォッチを使用してください。ここではプラス決意初期レンダリングは、ないようにするかどうかを過剰ウォッチャーオブザーバを生み出す再レンダリングします。
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は、最初のキャッシュに依存する値を計算すると、$コール依存属性の変化が観察された時計は、対応するキャッシュ値を更新します。
実際には、VUEの底部は処理が初期化中に計算され、わずかに複雑である計算され、遅延使用:依存性の変化を監視するために、真(非同期)方法を、すなわち、属性の変化に依存し、直ちに評価が、制御変数の変化が汚れていません;ダーティかどうかを決定するために評価に基づいて、次に、アクセス属性まで、コンポーネントインスタンスに対応するキー属性に結合し、そしてアクセスの属性を変更された計算時間を算出します。
ここでは、ダイレクトコールプロパティの変更が即座に評価段階へのフラッシュレンダリングまで待つのではなく、最新の値を取得するときに、時計になります。
フック
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
、我々は、フックの役割を果たすことができるが、そのようなので、上の小道具、attrsに、部品やなど、多くのVUE機能を、犠牲にします。
vue-hooks
これは、公開さhooks
関数現像入口はVue.use(hooks)
その後、内部ロジックは、全てのサブコンポーネントを混合することができます。このように、我々は可能SFC
でコンポーネントを使用する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真香(逃)
参考文章
文章内容如有错误,欢迎指出!
转载请注明出处!
转载于:https://juejin.im/post/5d0243a3f265da1b8f1abc49