有感这篇文章👉函数式编程看React Hooks(二)事件绑定副作用深度剖析,作者写得条理很清晰。
在此再强调一遍: 为什么要用useCallback去缓存onMouseMove这个函数?因为该函数是一个useEffect的依赖项,而且它是个引用类型的值,所以每一次re-render都会是新的函数。倘若该函数未发生变化,其实没有必要每次re-render都生成新的,这也是useCallbak最主要的作用。
下面延伸一丢丢,还是讲hooks的依赖项。从我自己还原的部分HashRouter代码讲起:
export default function HashRouter(props) {
let locationState = null;
const [location, setlocation] = useState({
pathname: window.location.hash.slice(1) || "/",
state: locationState
});
const handleHashChange = useCallback(() => {
console.log('执行 useCallback里面的函数') // 只要hashchange就会执行
setlocation({
...location,
pathname: window.location.hash.slice(1), // 新pathname的值并没有依赖上一个pathname
state: locationState
})
}, []); // 这里并没有添加依赖
useEffect(() => {
console.log('执行useEffect函数') // 只会打印一次
window.addEventListener('hashchange', handleHashChange)
return () => {
window.removeEventListener('hashchange', handleHashChange)
}
}, []); // 这里也没有添加依赖
const val = {
location,
history: {
push: to => {
if (typeof to === 'object') {
let {
pathname,
state
} = to;
window.location.hash = pathname;
locationState = state;
} else {
window.location.hash = to;
}
}
}
}
return <ReactRouterContext.Provider value={val}>
{
props.children
}
</ReactRouterContext.Provider>
}
请看代码中的注释部分。是的,我在上述useCallback和useEffect中都没有添加依赖项。其实这是不好的,会造成疑惑和不解。
但另一方面,它实现了缓存函数&&只绑定一次hashchange事件&&在hashchange时正常切换页面的设计初衷:
- 实现缓存handleHashChange这个方法。它只在初次render的 时候生成,之后re-render都返回的是缓存的函数。为什么让依赖项是空这么设计呢?因为它确实没有需要的依赖项。handleHashChange内部的setlocation方法的值,都不依赖useState返回的变量。浏览器事件的特殊之处就在于,只要不卸载页面或者手动解绑,绑定之后就一定会触发。
- useEffect虽然只执行一次,但是只需要在初次执行的时候给hashchange绑定函数即可,不必每次re-render都重新去绑定和解绑。
当我一开始对比文章开头引用的文章和自己这段代码时,也感到疑惑,再仔细去筛查,发现:在setlocation时,是从外部获取新值的这个原因促成的。所以才能够实现依赖项为空,且照样能刷新的目的。
下面我们引入一组对照组,就可以发现确实如此:
export default function HashRouter(props) {
let locationState = null;
const [location, setlocation] = useState({
pathname: window.location.hash.slice(1) || "/",
state: locationState
});
const [count, setcount] = useState({ // +++++ 新增这一段 +++++
number: 0
})
const handleHashChange = useCallback(() => {
setcount({
number: count.number+1 // +++++ 新增这一段 +++++
});
setlocation({
...location,
pathname: window.location.hash.slice(1),
state: locationState
})
}, []);
useEffect(() => {
window.addEventListener('hashchange', handleHashChange)
return () => {
window.removeEventListener('hashchange', handleHashChange)
}
}, []);
console.log('count--------', count); // +++++ 新增这一段 +++++ //会发现count只会增加到1,然后就不变了。因为 handleHashChange的依赖项是空。依赖项放置count就可以实现count递增了。
console.log('location', location); // +++++ 新增这一段 +++++
// 这个之所以会变化,是因为它的计算,是从window.location.hash中设置新值的,而不是依赖前一个值
const val = {
location,
history: {
push: to => {
if (typeof to === 'object') {
let {
pathname,
state
} = to;
window.location.hash = pathname;
locationState = state;
} else {
window.location.hash = to;
}
}
}
}
return <ReactRouterContext.Provider value={val}>
{
props.children
}
</ReactRouterContext.Provider>
}
再看看执行这段代码的打印结果:
这个结果就和上面推荐的文章的结果相同了。