React useEffectEvent
在 React 最新的实验版中引入了一个新的 useEffectEvent
hook, 引入这个新的 hook 出发点是为了解决下面场景中的问题:
function View() {
const [data, setData] = useState<{timestamp: number, value: any}>()
const listener = useEffectEvent((newData) => {
if (!data.timestamp || after(data.timestamp, 5min)) {
setData(newData)
}
})
useEffect(() => {
// effect to subscribe some remote data
remote.subscribe('data', listener)
return () => remote.unsubscribe()
}, [])
return (
<div>{data}</div>
)
}
在 listener
中我们可以拿到所有reactive value 的最新 snapshot ,上面的例子里是 data
.而通常我们的做法可能是,将listener 封装在 useEffect
中然后监听 data
的变化:
function View() {
const [data, setData] = useState<{timestamp: number, value: any}>()
useEffect(() => {
const listener = (newData) => {
if (!data.timestamp || after(data.timestamp, 5min)) {
setData(newData)
}
}
// effect to subscribe some remote data
remote.subscribe('data', listener)
return () => remote.unsubscribe()
}, [data])
return (
<div>{data}</div>
)
}
这种实现的潜在问题是当 data
更新时, useEffect
里的函数会被不断执行。上面的例子中在第一次执行后,以后每次 data
更新时都会经过先unsubscribe
然后再 subscribe
, 这时会造成不必要的 IO 操作,甚至造成race condition。
为什么下面的代码不行呢:
function View() {
const [data, setData] = useState<{timestamp: number, value: any}>()
const listener = useCallback((newData) => {
if (!data.timestamp || after(data.timestamp, 5min)) {
setData(newData)
}
}, [data])
useEffect(() => {
// effect to subscribe some remote data
remote.subscribe('data', listener)
return () => remote.unsubscribe()
}, [])
return (
<div>{data}</div>
)
}
因为在组件 View
第一次渲染的时候 useEffect
里拿到的也是 useCallback
第一次执行后的snapshot,以后每次 useCallback
重新执行后的数据对 useEffect
来说都是不可见的。
useEffectEvent
的简单实现:
const useEffectEvent = (fn) => {
const ref = useRef(fn)
ref.current = fn
return (...args) => ref.current(...args)
}
function View() {
const [data, setData] = useState<{timestamp: number, value: any}>()
const listener = useEffectEvent((newData) => {
if (!data.timestamp || after(data.timestamp, 5min)) {
setData(newData)
}
})
useEffect(() => {
// effect to subscribe some remote data
remote.subscribe('data', listener)
return () => remote.unsubscribe()
}, [data])
return (
<div>{data}</div>
)
}
其实原理就是将 listener
这个函数从 immutable 变成一个mutable的函数,每次执行的时候都可以读到所有 reactive 数据的最新数据。这么做的问题在于我们并不能保证组件 View
在多次渲染的情况得到相同的结果,这时候父级组件就会认为需要重新渲染更新DOM。
See more: