Intensive vue-hooks

background

Recent studies of the latest developments vue3.0 found great variation, overall, vue began to move closer to the hooks, and vue, also known as the author of the characteristics vue3.0 learned many hooks inspiration. So take advantage before vue3.0 not officially released, look at things hooks related to waste time.

Source Address: VUE-Hooks-the PoC

Why use hooks?

First, from the class-component / vue-options start with:

  • Cross-component code reuse difficult
  • When large components, maintenance difficulties, poor control of particle size, fine-grained partitioning, assemblies nested hierarchy too deep deposit - affect performance
  • Class components, the this uncontrollable logic scattered and not easily understood
  • mixins have side effects, nested logic with each other, the data from unknown sources, and can not be mutually consumption

When a template mixin rely a lot of times, it is prone to data sources is unclear naming conflicts or problems, and the development mixins when logic and logic to each other and depend on the properties of the dispersion between mixin can not consume each other. These are developing very very painful point, therefore, vue3.0 introduced hooks related features very wise.

vue-hooks

In the inquiry vue-hooksbefore the first rough look vueresponse systems: First, vuethe component initialization will mount dataon the nature of the response processing (loading dependency manager), then compiled into a v-dom stencil process, examples of a Watcherviewer to observe the entire alignment vnode, but also access these properties depend on the trigger rely Manager collects dependence (with Watcherobserver associate). When a property changes dependent, it will inform the corresponding Watcherobserver is re-evaluated (setter-> notify-> watcher-> run ), corresponding to the template is re-render (re-render).

Note: The internal vue default will re-render the process into micro-jobs in the queue, the current render will be evaluated on a render flush stage.

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
    }
  }
}
复制代码

withHooksProviding for the assembly vue hooks+ jsxdevelopment mode, used as follows:

export default withHooks((h)=>{
    ...
    return <span></span>
})
复制代码

Not difficult to see, withHooks still return a vue component configuration item options, follow the hooks related properties are mounted on options locally.

First, let's analyze vue-hooksa few global variables need to use:

  • currentInstance: cache the current instance vue
  • isMounting: render rendering whether for the first time
isMounting = !this._vnode
复制代码

Here _vnodeand $vnodethere is a big difference, $vnodeon behalf of the parent component (vm._vnode.parent)

_vnode initialized to null, in the mounted stage it will be assigned to the current component of the v-dom

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

  • callIndex: attribute index, when mounted to the options properties, when used as the only index callIndex identification.

Several local variables declared on vue options:

  • _state: placing data responsive
  • _refsStore: placing non-responsive data, and returns a reference type
  • _effectStore: storage and cleanup logic Logic side effects
  • _computedStore: used to store the property

Finally, withHooks callback function, the incoming attrsand $propsas 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 = ++callIndex
  const state = currentInstance.$data._state
  if (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. Used useDatato 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.

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]
}
复制代码

useStateIs a hooksvery central APIone, it internally provided by the closure of a updater updater, use updatermay be responsive to update data, the data changes will trigger re-render, the render process next time, will not be re-initialized using $ set, but it will take on the value of the cache update.

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]
}
复制代码

Use useRef initialize returns a reference to carry the current, current point values ​​initialized. I first use useRef when it can not always understand the application scenario, but really to get started, or how many have some feelings.

For example, we have the following code:

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>
  )
})
复制代码

Click value + button will simultaneously printing variable corresponding to the output is:

0 1
1 2
2 3
3 4
4 5
复制代码

You can see, num.current always new value, and count acquired is the last value of the render. In fact, this will increase to num global scope can also achieve the same effect. It can be expected useRef usage scenarios:

  • Repeatedly re-render the process save the latest value
  • This value need not responsive process
  • Do not pollute the other scopes

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
    }
  }
}
复制代码

useEffectIt is also hooksvery important in APIone, which is responsible for side effects of treatment and clean-up logic. The side effects of this can be understood as an operation may be performed selectively dependent, is not necessary to perform re-render each such dom operation, the network requests the like. These operations may cause some side effects, such as the need to clear dom listener, empty references and so on.

Start with the execution order point of view, initialization, declares cleanup function and side function and effect of the current point to the current logic of side effects, side effects of function is called once mounted the stage, the return value as a clean-up logic is held. At the same time according to rely on to determine whether to call the function again in the updated side-effects stage.

When not the first time rendering, according to deps will depend on whether it is necessary to call again the function of side effects, we need to perform again, to clear the last render side effects produced and directed current function of the latest side effects side effects of logic, wait for updated phase calls.

useMounted

useMounted(function(){})
复制代码
export function useMounted(fn) {
  useEffect(fn, [])
}
复制代码

useEffect dependent pass [], the side effects of function calls only mounted the stage.

useDestroyed

useDestroyed(function(){})
复制代码
export function useDestroyed(fn) {
  useEffect(() => fn, [])
}
复制代码

useEffect dependent pass []and there is a return function, a function that returns will be treated as cleanup logic destroyedcalls.

useUpdated

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

If deps fixed, incoming useEffect will be executed once at each stage mounted and updated, this means useRef declare a persistent variable, skip mounted the stage.

useWatch

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

Use the same $ watch. Here plus a determination whether the initial rendering, to prevent re-render produce excess Watcher observer.

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 will first calculate a value dependent on the cache and, $ call Watch dependency attribute change was observed, and updates the corresponding cache values.

In fact, the bottom of the VUE computed processing is slightly more complex, computed during initialization, using lazy: true (asynchronous) manner to monitor changes dependent, i.e., dependent upon the attribute change is not evaluated immediately, but changes in the control variables dirty ; and calculating the corresponding key attribute on a component instance is bound to, and modify the attributes for the access, the calculation time until the access attribute, then based on the evaluation to determine whether dirty.

Here direct call watch and to listen for synchronization dependency properties change, although it will increase the computational overhead, but it can guaranteewatch will be when a property changes immediately get the latest value, rather than wait until render flush to the evaluation stage.

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
        }
      }
    }
  })
}
复制代码

With withHooksthat we can play the role of hooks, but to sacrifice a lot vue features, such as props, attrs, components and so on.

vue-hooksIt exposes a hooksfunction developer inlet Vue.use(hooks)Thereafter, the internal logic may be mixed all the sub-components. In this way, we can SFCuse the components in hooksit.

For ease of understanding, a simple implementation this functionality, encapsulating the element nodes calculated dynamically sized to separate 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>
复制代码

Use effect is that when the element size is changed, the change information output to the document, while the component destroyed, canceled resize listener.

hooks return of property, and thus meet the component itself instance, the variable so that the template can be bound references.

What's wrong with hooks?

In practice, the process of discovery, hooksappears indeed to solve the many problems brought mixin, but also more abstract development components. But at the same time it also brings higher threshold, such as when using useEffect must rely on loyalty, otherwise cause an infinite loop also render every minute thing.

And react-hookscompared, vue can learn the function abstraction and reuse, but can also play their own advantages responsive tracking. We can see, especially in the react-hooksview given in comparison:

  • JavaScript on the whole more in line with intuition;
  • Is not limited by the order of the calls, it can be invoked conditionally;
  • It does not continue to produce large amounts of inline function when subsequent updates affecting engine optimization results in GC or pressure;
  • Always use useCallback not need to cache callback passed to sub-assemblies to prevent excessive update;
  • You do not need to worry about transfer wrong to rely on an array of useEffect / useMemo / useCallback resulting in a callback using an outdated value - dependent Vue track is fully automatic.

Feeling

To be able to use the new features faster after vue3.0 released, I read a little hooks the relevant source code and found to be more than expected harvest, and with the new release of RFC comparison, understood why. Unfortunately, work reasons, many development projects rely on the vue-property-decoratordo ts adaptation, it appears that three editions came out to be a big change.

Finally, hooks really fragrant (escape)

Reference article

The article is subject to errors, please point out!

Please indicate the source!

Reproduced in: https: //juejin.im/post/5d0243a3f265da1b8f1abc49

Guess you like

Origin blog.csdn.net/weixin_33827965/article/details/93166130