Deep understanding the knowledge of hooks in Vue.js

Recently studied the latest progress of Vue3.0, found that there is a big change, and VUE has also begun to close to hooks, and Vue author I also The characteristics of Vue3.0 have drawn a lot of hooks inspiration. So take the time to study the Hooks related things before the Vue3.0 is not officially released.

Source Address: Vue-hooks-poc

Why is you used to use hooks?

First from Class- Component / Vue-Options:

Cross-assembly code is difficult to multiplex

large component, maintenance difficult, particle size is not good control, fine granular division, components are bidding Levels too deep – influencing performance
  • Class components, this is uncontrollable, logical dispersion, not easy to understand
  • Mixins has side effects, logic mutual nested, data source is unknown, and can not consume each other
  • When a template relys for many Mixin, it is easy to appear data source unclear or naming conflicts, and when developing Mixins, logic and logical dependence attributes are distracted to each other and Mixin is not mutual. Consumption. These are very painful points in development, so the characteristics introduced in Vue3.0 are very sensible.

Prior to exploring Vue-hooks, roughly reviewing the Vue response system First, the VUE component initializes the attribute response processing (mount dependency manager) on the DATA, and then the template is compiled into a V-DOM, instantiate a Watcher observers observe the entire comparison VNODE, and also access these dependencies, triggering the dependency manager collection dependence (associated with Watcher observer). whenWhen the dependent attribute changes, the corresponding Watcher observer reclaimed (Setter-> NOTIFY-> Watcher-> run), the corresponding template is re-render (Re-Render).

Note: VUE internal defaults in the microsense queue, the current render will value in the last render flush phase.


Export Function withhook (Render) {Return {Data () {Return {_State :! {}}}, created () {this._effectStore = {} this._refsStore = {} this._computedStore = {}}, render (h) {callIndex = 0currentInstance = thisisMounting = this._vnodeconst ret = render (h , $ attrs, this. $ props) currentinstance = nullreturn Ret}}}

withhook provides the Vue component development method, the way of use is as follows:

Export Default withhooks ((h) => {… return

 It is not difficult to see that withhook is still a configuration item Options for Vue Component, follow-upThe hooks related properties are mounted on options locally.   
First, let’s analyze a few global variables vue-hooks need to use:

currentInstance: vue current cache instance

isMounting: whether for the first time render rendering
  isMounting =! this._vnode    
here _vnode and $ vnode a big difference, $ vnode Representative parent components (vm._vnode.parent)
_vnode initialized to null, in the mounted stage is assigned to the current component v-dom

isMounting addition to controlling the internal data of the initialization phase, the but also to prevent repeated re-render.

callIndex: attribute index, when mounted to the options properties, when used as the only index callIndex identification.
  • Statement on several local variables vue options:
_state: placing a responsive data

_refsStore: placing non-responsive data, and returns reference type
_effectStore: storage logic and clean side logic

_computedStore: used to store the property

Finally, withHooks callback function, the incoming and $ attrs the props as the reference, and the current rendering after assembly, reset the global variables, in preparation for rendering the next assembly.

  • 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]}
  • we know that you want to change in response to a type of monitor data, in vue need to go through some processing, and the scene relatively limited. Use useData declare variables, it would also mount a responsive data on the internal data._state. But the drawback is that it does not provide an updated external data returned by the changes, there may be lost responsive listener.


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 is one of the very core of API hooks, which is provided by a closure in the interiorUpdater Updater, use Updater to respond to update data, the data change will trigger Re-render, the next RENDER process, not initialization reuse, but will be updated.

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

Use UseRef initialization to return a reference to a CURRENT, and Current points to the value of initialization. I always understand its application scene when I first use UseRef, but I really have some feelings after I started.

Export Default withhooks (h => {const [count, setcount] = useestate (0 Const Num = Usef (count) const log = () => {let sum = count + 1setcount (sum) Num.current = SUMCONSOLE.LOG (count, num.current);} Return (


Click the button to +1, simultaneously print the corresponding variable, the output result is:
 0 11 22 33 44 5   
It can be seen that Num.Current will always be the latest. Value, and count get the value of the last render.

In fact, the same effect can be achieved here to enhance Num to global scope.

Therefore, the use scenario of UseRef can be foreseen:

Save the latest value during multiple reender
 This value does not require a response process    

useeffect (function () => {// Side Effect Logic Return () => {// Cleanup logic}}}}, [DEPS]) Export function useeffect (Raweffect, DEPS) {EnSureCurrentInstance () const id = ++ callindexif (ismounting) {const clalanup = ) => {const {current} = cleanupif (current) {current () cleanup.current = null}} const effect = function ()}} {const {current} = effectif (CURRENt) {cleanup.current = (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, defs: prevdeps = []} = RecordRecord.deps = depsif (! DEPS || DEPS.SOME ((D, i) => D! == prevdeps [i]) {Cleanup () Effect.current = raweffect}}}
Useeffect is also one of the very important APIs in hooks, which is responsible for side effects and cleaning logic. The side effects here can be understood as an operation that can be based on the performance of selective execution, no need to perform each Re-render, such as a DOM operation, network request, etc. These operations may result in some side effects, such as the DOM listener, emptying reference, and so on.
   When you look at the execution order, initialize, declare the cleaning function and the side effect function, and point the CURRENT of the Effect to the current side effect logic, in MountedThe stage calls a side effect function, and the return value is saved as cleaning logic. At the same time, according to the dependencies, it is determined whether the side effect function is called again in the Updated phase. 
Non-first rendering, will determine if DEPS reliance is required to call the side effect function again, you need to execute again, first clear the side effects generated by the previous render, and point the Current of the side effect function to the latest side effect logic, waiting for the Updated phase. transfer.

useMOUNTED (Function () {}) export function useMOUNTED (FN) {useeffect (FN, [])}

When usingEeffect Dependency [], the side effect function is only called in the moused phase.

  • UsedStroyed (Function () {}) Export Function Usedestroyed (fn) {useeffect (() => Fn, [])}
  • Useeffect-dependent transmission [] and there is a return function, the return function will be used as the cleaning logic to be called at the Destroyed.

UseUpdated (FN, DEPS) Export Function UseUpdated (FN, DEPS) {Const ismount = useeref (true) useeffect (() => {if (ismount.curreNT) {ismount.current = false} else {return fn ()}}, DEPS)}
  If the DEPS is fixed, the incoming useeffect will be in MOUNTED and The Updated phase is executed once, where the useRef declares a persistent variable to skip the MOUNTED phase.  

Export Function UseWatch (Getter, CB, Options) {ENSURECURRENSTANCE () IF () IF {CurrentInstance. $ Watch (getter, cb, options)}}

The same way is the same as $ WATCH. Here is a first rendering judgment to prevent RE-render from producing excess Watcher observer.

Const Data = Usedata ({Count: 1}) const same 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 first Calculate a dependency and cache, call $ Watch to observe the dependence of property changes, and update the corresponding cache value.

In fact, the VUE underlayer is slightly complex to computed. When initialization computed, use lazy: true (asynchronous) mode to listen to dependence, that is, the dependency change does not immediately evaluate the value, Instead, the DIRTY variable changes; and the Key corresponding to the calculation property is bound to the component instance, and simultaneously modify the accesser properties, wait until the calculation property is accessed, and then determine whether the value is determined according to Dirty.

Here, the Watch directly acquires the latest values ​​immediately, rather than waiting until the Render Flush phase.

  Export Function Hooks (Vue) {Vue.Mixin ({BeforeCreate () {const {hooks , DATA} = this. $ Optionsif (hooks) {this._effectStore = {} this._refsstore = {} this._computedstore = {} // Remote Data function, inject _State property this. $ options.Data = function () {const ret = data? (this): {} rett._state = {} return Ret}}}, beforemount () {const {hooks,RENDER} = this. $ Optionsif (Hooks && Render) {// Rewriting the render function this. $ options.render = function (h) {callindex = 0currentInstance = thisismounting =! this._vnode // Default incoming PROPS attribute const HookProps = hooks (this. $ props) // _self Indicates the component instance Object.assign (this._self, hookprops) const ret = (this, h) currentinstance = nullreturn RET}}}}}  
With withhooks, we can play hooks, but sacrifices a lot of Vue features, such as Props, Attrs, Components, etc.

Vue-hooks exposes a hooks function, and the developer can mix internal logic into all subcompons after entry Vue.use (Hooks). This way, we can use hooks in the SFC component.

In order to facilitate understanding, a function is simply implemented, and dynamically calculate element node size is encapsulated into independent hooks:

Import {hooks, useeref, usdata, readystate, useeffect, useMounted, usewatch} from ‘../hook’;function useresize EL){const node = usef (null); const [resize, setResize] = useEstate ({}); 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 rings}: {msg: string}: {msg: string}, // Here and the setup function is very close, it is accepted for PrOPS, and finally returns dependent attribute hooks (props) {const data =resize (); return {resize: json.stringify (data)}}};

HTML, Body {HEIGHT: 100%;}

When the effect is that the element size is changed, the change information is output to the document while the component is destroyed. Log out of the Resize listener.

Hooks returned attributes, incorporates the own instance of the component,This module binding variables can be referenced.
What is the problem with hooks?

In the actual application, it was found that Hooks’s appearances did resolve many of the many problems brought by Mixin, and they can also achieve more abstract development components. But at the same time, it also brings a higher threshold. For example, useeffect must be relied loyal when using it, otherwise it is a minute.
 Compared to React-hooks, Vue can learn from the ability to abstraction and reuse, and can also play the advantages of its own response tracking. We can see the views given in comparison with React-Hooks:   The whole is more in line with JavaScript; 
is not subject to the restriction of the call sequence, can be called; Will not continue to generate a large number of inline functions in subsequent updates, affecting engine optimization, or leading to GC pressure;
Do not need to always use UseCallback to cache the callback to subcaps to prevent excessive updates;

No need Worried that the error-dependent array is sent to Useeffect / UseMemo / UseCallback, which causes the expired value in the callback to use expiration-Vue’s dependencies.

In order to be able to get a new feature after Vue3.0 is released, he has studied the source code related to hooks and found that than imagination. There are many harvested, and the newly released RFC is contrast, suddenly realizes. Unfortunately, the development project has rely on Vue-Property-Decorator to do TS adaptation. It seems that the three versions will be changed.
Finally, hooks really fragrant (escape)

The above is all of this article, I hope to help everyone, and I hope everyone supports Tumi Cloud.
© Copyright Notice
Just support it if you like
comment Grab the couch

Please log in to comment