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依赖传[]时,副作用函数只在mounted阶段调用。
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会在mounted和updated阶段各执行一次,这里借助useRef声明一个持久化的变量,来跳过mounted阶段。
useWatch
export function useWatch(getter, cb, options) { ensureCurrentInstance() if (isMounting) { currentInstance.$watch(getter, cb, options) } }
使用方式同$watch。这里加了一个是否初次渲染判断,防止re-render产生多余Watcher观察者。
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首先会计算一次依赖值并缓存,调用$watch来观察依赖属性变化,并更新对应的缓存值。