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-hooks
before the first rough look vue
response systems: First, vue
the component initialization will mount data
on the nature of the response processing (loading dependency manager), then compiled into a v-dom stencil process, examples of a Watcher
viewer to observe the entire alignment vnode
, but also access these properties depend on the trigger rely Manager collects dependence (with Watcher
observer associate). When a property changes dependent, it will inform the corresponding Watcher
observer 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
}
}
}
复制代码
withHooks
Providing for the assembly vue hooks
+ jsx
development 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-hooks
a few global variables need to use:
- currentInstance: cache the current instance vue
- isMounting: render rendering whether for the first time
isMounting = !this._vnode
复制代码
Here
_vnode
and$vnode
there is a big difference,$vnode
on 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 attrs
and $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 = ++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 useData
to 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]
}
复制代码
useState
Is a hooks
very central API
one, it internally provided by the closure of a updater updater
, use updater
may 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
}
}
}
复制代码
useEffect
It is also hooks
very important in API
one, 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 destroyed
calls.
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 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 withHooks
that we can play the role of hooks, but to sacrifice a lot vue features, such as props, attrs, components and so on.
vue-hooks
It exposes a hooks
function developer inlet Vue.use(hooks)
Thereafter, the internal logic may be mixed all the sub-components. In this way, we can SFC
use the components in hooks
it.
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, hooks
appears 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-hooks
compared, vue can learn the function abstraction and reuse, but can also play their own advantages responsive tracking. We can see, especially in the react-hooks
view 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-decorator
do ts adaptation, it appears that three editions came out to be a big change.
Finally, hooks really fragrant (escape)
Reference article
- Vue Function-based API RFC
- What Hooks Mean for Vue
- Hooks API to achieve analysis of Vue
- Intensive "Function Component Getting Started"
The article is subject to errors, please point out!
Please indicate the source!
Reproduced in: https: //juejin.im/post/5d0243a3f265da1b8f1abc49