如何在class组件中使用hooks

11,692 阅读3分钟

问题

前不久在开发中遇到一个问题,在一个基于class component的react项目,想要使用像hooks里的useEffect这样的函数,来监听某些props的变化,每当这些props变化时,都要执行一遍指定的函数。
如果是在一个使用hooks的组件里,想要实现这样的效果直接使用useEffect即可,但是问题就在于目前的这个组件是用class写的,而且组件也很庞大复杂,如果想要用hooks重写,一是时间不允许,二是测试不允许,重写后必须得重新测试(测试同学OS:已经测试过的功能你怎么又让我重测一遍,(╯`□′)╯~ ╧╧,不干!)。
那么摆在面前的问题来了:如何在class组件中使用hooks函数?
首先第一步肯定是先翻看一下官方文档,看一下有没有标准的解决方案,但是很不幸的是: 这就意味着,Hooks不是一个使项目更简洁的小功能,而是React一个完全不同于class组件的全新的核心概念。
既然官方已经明确说了,class组件里hooks函数是不生效的,那么是不是我们的问题就没有解了呢?当然不是,在JavaScript的世界里,一切皆有解决办法。

解决办法

以一个简单的useScreenWidthhook函数为例,它的目的是获取全屏的宽度,并且去监听浏览器窗口的变化,更新宽度:

import { useEffect, useState } from 'react';

export function useScreenWidth(): number {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handler = (event: any) => {
      setWidth(event.target.innerWidth);
    };
    // 监听浏览器窗口变化
    window.addEventListener('resize', handler);
    // 组件unmount时要解除监听
    return () => {
      window.removeEventListener('resize', handler);
    };
  }, []);

  return width;
}

useState

返回两个值:一个是state状态值,另一个是它的setter函数。

useEffect

有两个参数:第一个是要执行的函数,第二个是要监听的变量组成的数组。如果监听数组中某一个值发生了变化,那么第一个参数(要执行的函数)就会在下一轮渲染时执行。 第二个参数有三种情况:

  • 没有任何参数:useEffect在每一次重渲染都会执行
  • 空数组[]useEffect只会在第一次渲染时执行,因为空数组不会有变化
  • [arg1, arg2, ...]:数组中任意值发生了变化,useEffect在下一次渲染都会执行

方法1:将Hook包装成HOC

HOC是React中复用组件的高级用法,它的本质是一个函数,它的输入参数是一个组件,返回相同的组件以及一些额外的props。在我们的例子里,可以让hook函数作为props传递到目标组件中:

import React from 'react';
import { useScreenWidth } from '../hooks/useScreenWidth';

export const withHooksHOC = (Component: any) => {
  return (props: any) => {
    const screenWidth = useScreenWidth();

    return <Component width={screenWidth} {...props} />;
  };
};

最后一步就是将我们的目标组件用上述的withHooksHOC包装起来,那么我们就可以将width传递给了目标组件:

import React from 'react';
import { withHooksHOC } from './withHooksHOC';

interface IHooksHOCProps {
  width: number;
}

class HooksHOC extends React.Component<IHooksHOCProps> {
  render() {
    return <p>width: {this.props.width}</p>;
  }
}

export default withHooksHOC(HooksHOC);

方法2:将Hook包装成函数组件

另外一种方法就是将hook变成函数组件,它接收一个参数为widthchildren函数,然后将width作为render prop传递:

import { FunctionComponent } from 'react';
import { useScreenWidth } from '../hooks/useScreenWidth';

type ScreenWidthChildren = (screenWidth: number) => React.ReactNode;

interface IScreenWidthProps {
  children: ScreenWidthChildren;
}

export const ScreenWidth: FunctionComponent<IScreenWidthProps> = ({
  children,
}) => {
  const screenWidth: number = useScreenWidth();

  return children(screenWidth);
};

使用:

import React from 'react';
import { ScreenWidth } from './ScreenWidth';

export class HooksRenderProps extends React.Component {
  render() {
    return (
      <ScreenWidth>
        {(width) => <p style={{ fontSize: '48px' }}>width: {width}</p>}
      </ScreenWidth>
    );
  }
}