背景 hooks 百度翻译为钩子,不要把 Hooks 和 Vue 的 生命周期钩子(Lifecycle Hooks) 弄混了,Hooks 是 React 在 V16.7.0-alpha 版本中引入的,而且几天后 Vue 发布了其概念验证版本。 最近尤大发布了一个最新的npm包 Hook是react中得一项新功能提案,可以让开发人员在不编写Class的情况下使用状态和其他React功能。
定义 Hooks 主要是对模式的复用提供了一种更明确的思路 —— 避免重写组件本身,并允许有状态逻辑的不同部分能无缝地进行协同工作。
无状态函数式组件也非常受欢迎,但由于它们只能单纯地渲染,所以它们的用途仅限于展示任务。
Hooks 允许我们使用函数调用来定义组件的有状态逻辑,从而解决这些问题。这些函数调用变得更具有组合性、可复用性,并且允许我们在使用函数式组件的同时能够访问和维护状态。
为什么 Vue 中需要 Hooks? Hooks 在 Vue 中必须提供什么。这似乎是一个不需要解决的问题。毕竟,类并不是 Vue 主要使用的模式。Vue 提供无状态函数式组件(如果需要它们),但为什么我们需要在函数式组件中携带状态呢?我们有 mixins 用于组合可以在多个组件复用的相同逻辑。问题解决了。
源码解读 h函数是createElement,生产一个VNode节点,即html DOM节点
createElement(也就是h)是vuejs里的一个函数。这个函数的作用就是生成一个 VNode节点,render 函数得到这个 VNode 节点之后,返回给 Vue.js 的 mount 函数,渲染成真实 DOM 节点,并挂载到根节点上。
1、useEffect 做了什么?
通过使用这个 Hook,通知 React 组件需要在渲染后执行什么操作。React 将记住传递的 function(把这个 function 成为 “effect”),并在执行 DOM 更新后调用这个 function。在这个效果中,主要的功能仍旧是设置 document.title,但是也可以执行数据获取,或者是调用其他的命令式的 API。
isMounting:是否为首次渲染
vue options上声明的几个本地变量:
_state:放置响应式数据
_refsStore:放置非响应式数据,且返回引用类型
_effectStore:存放副作用逻辑和清理逻辑
_computedStore:存放计算属性
vue-hooks暴露了一个hooks函数,开发者在入口Vue.use(hooks)之后,可以将内部逻辑混入所有的子组件。这样,我们就可以在SFC组件中使用hooks啦。
Hooks 和 mixins 之间的主要区别之一是 Hooks 实际上可以互相传值 _vnode初始化为null,在mounted阶段会被赋值为当前组件的v-dom Hooks的思路是将一个组件拆分为较小的函数,而不是基于生命周期方法强制拆分。 seEffect提供了类似于 componentDidMount等生命周期钩子的功能 vue里面的mounted
hooks的方法 useData useState只能在hooks或者widthHooks中使用
hooks中的数据是根据useState出现的顺序来定的
借助withHooks,我们可以发挥hooks的作用,但牺牲来很多vue的特性,比如props,attrs,components等。
所谓的 “Effect” 对应的概念叫做 “side effect”。指的是状态改变时,相关的远端数据异步请求、事件绑定、改变 DOM 等;因为此类操作要么会引发其他组件的变化,要么在渲染周期中并不能立刻完成,所以就称其为“副作用”。
REACT useEffect 能够在组件 render 之后进行不同类型的副作用。某些 effect 可能需要清理,因此可以在 effect 中返回一个 function:
参考文档 reacthttp://www.ptbird.cn/react-hoot-useEffect.html vuehttps://www.jianshu.com/p/f1e6597b19de 简书http://www.sohu.com/a/321909448_500651 精度vue-hookshttps://juejin.im/post/5c7784d5f265da2de713629c 掘金https://mp.weixin.qq.com/s/p2f3jsko91iGhrbtjgmt7g?utm_medium=hao.caibaojian.com&utm_source=hao.caibaojian.com 云前端https://1byte.io/react-hooks/ reacthttps://blog.csdn.net/liuyingv8/article/details/84068075 react 30分钟
传统vue组件的缺点
跨组件代码难以复用
大组件,维护困难,颗粒度不好控制,细粒度划分时,组件嵌套存层次太深-影响性能
类组件,this不可控,逻辑分散,不容易理解
mixins具有副作用,逻辑互相嵌套,数据来源不明,且不能互相消费
Q: 1,currentInstance是如何记录当前实例的 当前hooks文件的this就是当前的vue实例,将this赋值给currentInstance,然后将_effectStore等赋值给当前vue实例即可
2,currentInstance是如何成为proxy对象的 未知 currentInstance为当前vue实例,this即为proxy对象
3,hooks如何解决minix的问题的
数据消费 hooks能够方位当前vue实例的数据,可以相互消费
数据来源 hooks为我们手动调用的,所以数据来源为哪里就显然易见了
4,beforeMount里面,将currentInstance赋值了又置为空 赋值后,触发了render函数,注册了事件,置空当前变量
5,reder时,h的两次的用义 foo函数中的h函数是为了将jsx转为option对象,第二个h函数是为了option对象转为虚拟dom
6,id递增是为了每次获取新值? vue-hooks将数据的获取与设置以id来代替,访问id即可得到映射的值,每个vue实例中的数据所对应的id是固定的
7, currentInstance.$on(‘hook:mounted’)的emit在哪里 vue源码支持,详见截图
8,hooks是否能够生命data或者computed,props 能访问,是否能定义还未知 不需要定义,直接在vue实例中的hooks钩子中return即可,template就能
mixins混入的问题是什么?vue-hooks是怎么解决其问题的 mixins 不能相互消费和使用状态,但 Hooks 可以。 hooks的用法?
9,什么时候会多次渲染
10,hooks钩子在哪个生命周期后面执行 beforeMount
11,不能放在条件或循环中
对 useState() 的调用次数必须是一样的。
与各状态对应的 useState()的调用顺序是一样的。
12,自定义hooks是什么,解决什么问题,怎么使用,会有什么问题?
13,不全局使用vue-hooks,只在相应的hooks文件import可以吗? 不行,withHooks依旧是返回一个vue component的配置项options,后续的hooks相关的属性都挂载在本地提供的options上。
14,不能申明相同的属性_state,会被覆盖
vue-hooks解决的问题
实现了mixins的功能,并且解决了mixins的两个问题
允许相互传递状态
明确指出了逻辑来自哪里 使用 Hooks,函数的返回值会记录消费的值。
vue-hooks是简化组件定义、复用状态逻辑的一种最新尝试,且结合 Vue 实例的特点提供了适用的 Hooks
hooks.js中的this为当前vue实例
react-hooks hooks只能出现在函数作用域的顶级,不能出现在条件语句、循环语句中、嵌套函数中。
总结 withHooks 返回一个包装过的 Vue 实例配置
hooks 以 mixin 的形式发挥作用,注入两个生命周期
用模块局部变量 currentInstance 记录了 Hooks 生效的 Vue 实例
使用方式 withHooks为vue组件提供了hooks+VNode,使用方式如下:
withHooks 返回一个包装过的 Vue 实例配置
使用注意点 如果 useState 被包裹在 condition 中,那每次执行的下标就可能对不上,导致 useState 导出的 setter 更新错数据。
源码解读 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 let currentInstance = null //缓存当前的vue实例 let isMounting = false // render是否为首次渲染 let callIndex = 0 // 当前数据对应的索引,当往options上挂载属性时,使用callIndex作为唯一当索引标识 function ensureCurrentInstance() { // 是否有实例 if (!currentInstance) { // 无效的挂钩调用:只能在传递给withhooks的函数中调用挂钩 throw new Error( `invalid hooks call: hooks can only be called in a function passed to withHooks.` ) } } export function useState(initial) { ensureCurrentInstance() const id = ++callIndex const state = currentInstance.$data._state // 通过闭包提供了一个更新器updater const updater = newValue => { state[id] = newValue } if (isMounting) { currentInstance.$set(state, id, initial) } // 下一次的render过程,不会在重新使用$set初始化 return [state[id], updater] } // 负责副作用处理和清理逻辑 // 这里的副作用可以理解为可以根据依赖选择性的执行的操作 // 没必要每次re-render都执行,比如dom操作,网络请求等。 // 而这些操作可能会导致一些副作用,比如需要清除dom监听器,清空引用等等。 export function useEffect(rawEffect, deps) { ensureCurrentInstance() const id = ++callIndex // 初始化时,声明了清理函数和副作用函数,并将effect的current指向当前的副作用逻辑, // 在mounted阶段调用一次副作用函数,将返回值当成清理逻辑保存。 // 同时根据依赖来判断是否在updated阶段再次调用副作用函数。 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指向当前的副作用逻辑,在mounted阶段调用一次副作用函数 effect.current = rawEffect currentInstance._effectStore[id] = { effect, cleanup, deps } // \vue-dev\src\core\instance\lifecycle.js currentInstance.$on('hook:mounted', effect) currentInstance.$on('hook:destroyed', cleanup) if (!deps || deps.length > 0) { currentInstance.$on('hook:updated', effect) } } else { // 非首次渲染时,会根据deps依赖来判断是否需要再次调用副作用函数, // 需要再次执行时,先清除上一次render产生的副作用, // 并将副作用函数的current指向最新的副作用逻辑,等待updated阶段调用。 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 } } } // f初始化会返回一个携带current的引用,current指向初始化的值 export function useRef(initial) { ensureCurrentInstance() const id = ++callIndex const { _refsStore: refs } = currentInstance return isMounting ? (refs[id] = { current: initial }) : refs[id] } // 挂载一个响应式数据,但是没有提供更新器 export function useData(initial) { const id = ++callIndex const state = currentInstance.$data._state if (isMounting) { currentInstance.$set(state, id, initial) } return state[id] } // useEffect依赖传[]时,副作用函数只在mounted阶段调用。 export function useMounted(fn) { useEffect(fn, []) } // useEffect依赖传[]且存在返回函数,返回函数会被当作清理逻辑在destroyed调用。 export function useDestroyed(fn) { useEffect(() => fn, []) } // 如果deps固定不变,传入的useEffect会在mounted和updated阶段各执行一次, // 这里借助useRef声明一个持久化的变量,来跳过mounted阶段。 export function useUpdated(fn, deps) { const isMount = useRef(true) useEffect(() => { if (isMount.current) { isMount.current = false } else { return fn() } }, deps) } export function useWatch(getter, cb, options) { ensureCurrentInstance() // 加了一个是否初次渲染判断,防止re-render产生多余Watcher观察者。 if (isMounting) { currentInstance.$watch(getter, cb, options) } } export function useComputed(getter) { ensureCurrentInstance() const id = ++callIndex const store = currentInstance._computedStore if (isMounting) { // 先会计算一次依赖值并缓存 store[id] = getter() // 调用$watch来观察依赖属性变化,并更新对应的缓存值。 currentInstance.$watch(getter, val => { store[id] = val }, { sync: true }) } return store[id] } export function withHooks(render) { return { data() { return { _state: {} // 不能申明相同的属性_state,会被覆盖 } }, created() { this._effectStore = {} this._refsStore = {} this._computedStore = {} }, render(h) { callIndex = 0 currentInstance = this // 将当前的 isMounting = !this._vnode // _vnode初始化为null,在mounted阶段会被赋值为当前组件的v-dom,isMounting除了控制内部数据初始化的阶段外,还能防止重复re-render const ret = render(h, this.$attrs, this.$props) // 传入了attrs和$props作为入参,且在渲染完当前组件后 currentInstance = null // 重置全局变量,以备渲染下个组件。 return ret } } } export function hooks (Vue) { Vue.mixin({ // 换入两个生命周期 beforeCreate() { const { hooks, data } = this.$options if (hooks) { this._effectStore = {} this._refsStore = {} this._computedStore = {} this.$options.data = function () { const ret = data ? data.call(this) : {} ret._state = {} // 重置_state属性 return ret } } }, beforeMount() { const { hooks, render } = this.$options if (hooks && render) { this.$options.render = function(h) { callIndex = 0 currentInstance = this isMounting = !this._vnode // _vnode初始化为null,在mounted阶段会被赋值为当前组件的v-dom const hookProps = hooks(this.$props) // 调用hooks方法,将return的字段放到实例本身上,即可得到响应数据 Object.assign(this._self, hookProps) const ret = render.call(this, h) currentInstance = null return ret } } } }) }