前言bb
最近在项目在做多语言国际化配置,我开发中遇到一个场景:需要在组件初始化渲染之前改变语言环境,类似于在类组件中的 componentWillMount 里面做一些事情,然而项目中几乎使用 React Hook 来进行开发,因此我得手动在 Hook 中实现 componentWillMount 钩子。
为何不用 useLayoutEffect
useLayoutEffect 的出现是为了解决 useEffect 的页面闪烁问题。useEffect 是在组件挂载后异步执行的,并且执行事件会更加往后,如果我们在 useEffect 里面改变 state 状态,那么页面会出现闪烁(state 可见性变化导致的)。而 useLayoutEffect 是在渲染之前同步执行的,在这里执行修改 DOM 相关操作,就会避免页面闪烁的情况。
详情参考这篇文章:useEffect 和 useLayoutEffect 的区别
然而,我遇到的场景是在渲染之前,组件初始化的时候去执行相关逻辑,useLayoutEffect 是在函数内部变量初始化之后运行的,因此它并不能满足需求。
componentWillMount 为何被抛弃
众所周知,React 生命周期中有一个 componentWillMount,不过目前已经被 React 标记为过时了(它在 React 中仍然可以用,但官方不建议使用)。该钩子是在 DOM 挂载之前调用,我们可以利用它在组件初始化渲染之前做一些事情。
But, componentWillMount 有一个很明显的缺陷,就是此钩子在页面初始化渲染之前会执行一次或多次,这并不是我们想要的,并且,我们可以利用 constructor() 来替代 componentWillMount。
因此,目前该钩子正慢慢被 React 移除,官方表示,componentWillMount 只能继续使用至 React 17,这就是说到了 React18 就不能用咯。
Hook 的生命周期
在 React Hook 中,函数组件并没有 constructor 的概念,我们大多数是通过 useEffect 来模拟组件的生命周期,比如componentDidMount 和 componentWillUnMount
componentDidMount:当组件挂载完成之后调用,一般用来监听浏览器事件、请求接口加载数据componentWillUnMount:在组件销毁之前调用,通常是用来做一些清除动作,比如移除事件监听、清除定时器等等
我们可以利用 useEffect 来这么实现这两个生命周期:
useEffect(() => {
// 类似于 componentDidMount
window.addEventListener('mousemove', () => {});
return () => {
// 类似于 componentWillUnMount
window.removeEventListener('mousemove', () => {}) }
}, [])
useComponentWillMount 的实现
然而要模拟实现 componentWillMount,我们只能另辟蹊径,这里我们来手写一个 useComponentWillMount hook:
const useComponentWillMount = (cb) => {
const willMount = useRef(true)
if (willMount.current) cb()
willMount.current = false
}
值得注意的是,这并不能完全等同于 componentWillMount,因为存在代码顺序带来的问题,比如:
console.log('111')
useComponentWillMount(() => console.log('222'))
// output:
// 111
// 222
在这里,111 会在 useComponentWillMount 之前执行,而在 class 的 componentWillMount 中,是优先其他代码执行的。
因此,在实际开发中,我们要根据场景和需求,去灵活使用。