React Hook 如何实现 componentWillMount

3,156 阅读2分钟

前言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 中,是优先其他代码执行的。

因此,在实际开发中,我们要根据场景和需求,去灵活使用。