【DailyENJS第14期】如何在 Class 组件中使用 React Hooks

5,043 阅读5分钟

DailyENJS 致力于翻译优秀的前端英文技术文章,为技术同学带来更好的技术视野。

介绍

当我第一次听说 Hooks 时,我非常兴奋。我阅读文档,Hooks 似乎很神奇- 函数部件像类部件一样功能丰富?Hooks 使得诸如生命周期或 state 之类的问题;或者是复杂的语法以及类中某些内部逻辑的低可重用性的问题得到了解决。

Hooks 使得我们不再需要诸如 render-props 或 HOC(高阶组件)之类的复杂模式。这不仅使代码更简洁,而且还减少了出错的可能性。

复杂逻辑的可重用性也得到了极大的改善,因为我们现在可以选择将诸如 state 和“生命周期方法”直接打包到一个函数中。这为在 Hooks 之前无法实现的东西创造了很多可以想象的空间。函数变得更加强大,它们不仅可以在整个项目中重复使用,而且可以在许多不同的项目中重复使用。因为他们实现的逻辑可以完全通用。

我最激动的是将各种 Hook 函数组合成更强大的函数。这些函数也可以再次组合为功能更强大的函数。相信我,开始使用 Hooks 之后,你会离不开它。

问题

如果一切都那么美好和​​神奇,那有什么问题?当社区采用Hooks时,我看不到任何问题。

但是实际情况是,你想在项目中使用基于类的 Hook 逻辑,并且目前无法将这些类组件重写为 Hooks。类可能太复杂了,或者如果你更改它,可能会破坏项目中的许多其他内容。这种方法的商业价值也值得怀疑。如果你转至 React 文档,会看到一个有趣的声明:

但是,就像JavaScript世界中的所有内容一样,有一个解决方案。你可以有效地在类内使用 Hooks 逻辑,而不会违反任何 React 规则。

解决方法

我以一个简单的 useScreenWidth Hook 为例。从 Hook 名称可以看到,其目的是获取实际的屏幕宽度。代码:

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);

    return () => {
      window.removeEventListener('resize', handler);
    };
  }, []);

  return width;
}

useState 和 useEffect 是 React 内置的Hooks。useState 的工作方式是返回两个值:一个是状态值,另一个是它的 setter。通过数组解构,可以将这两个值的名称设置为所需的任何名称。<variable_name>set <variable_name> 是常见的命名。作为参数,你·传入一个初始值-在我们的例子中,这就是当前的屏幕宽度。

useEffect 是一个 hook 函数,它使用两个参数作为输入:第一个是要调用的函数,第二个是 Calling objects 的数组。这意味着我们可以在该数组中传递许多对象,并且仅当数组中的至少一个值在下一个渲染时发生更改时,才会应用 effect(或换句话说,将调用参数函数)。例如。我们可以将一下几种情况作为第二个参数传递:

  • 完全没有参数- useEffect 将在每次渲染的时候调用。
  • [] - 因为空数组永远不会改变,所以 useEffect 仅在第一次渲染时被调用
  • [arg1,arg2,…,argN]-如果数组内部的任何值被更改,都将调用useEffect

根据第二个参数,useEffect 可以模仿类的生命周期方法。例如。如果我们将 [] 作为第二个参数,仅在第一次渲染时被调用,因此它将模仿 componentDidMount

如上所述,钩子的思想与类不同,这就是为什么根本没有生命周期方法的原因,并且不应以这种方式看待钩子。useEffect 应该更多地视为副作用。这就是为什么可以说将 [] 作为第二个参数将模仿 componentDidMount 并赋予相同的行为的原因,但重要的是要理解这两者是不同的。

Using Hooks as HOC

HOC 是重用组件逻辑的高级 React 技术,其使我们能够在现有类组件中使用 Hook 逻辑。因为 HOC 是使一个组件作为输入,并通过一些额外的 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} />;
  };
};

最后一步是用该HOC简单包装我们现有的类组件。然后,我们仅使用width属性作为传递给组件的其他属性。

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

interface IHooksHOCProps {
  width: number;
}

class HooksHOC extends React.Component<IHooksHOCProps> {
  render() {
    return <p style={{ fontSize: '48px' }}>width: {this.props.width}</p>;
  }
}

export default withHooksHOC(HooksHOC);

这不会违反任何 Hooks 规则,因为我们正在函数中使用实际的Hook-就像我们应该使用的那样。将逻辑作为 props 传递也没有问题。

Using Hook as render prop

还有另一种实现我们目标的方法:

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

type ScreenWidthChildren = (screenWidth: number) => any;

interface IScreenWidthProps {
  children: ScreenWidthChildren;
}

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

  return children(screenWidth);
};

现在,我们创建了一个 Function Component,该函数将 children 作为参数。在其中使用了 Hook 逻辑后,我们将返回所需的结果作为子函数。在那之后,逻辑很简单:将创建的 Function Component导入您现有的类中,并向下传递 children 作为render prop。

最后

Hooks 是 React 世界中很大的改变,它们肯定会改变我们对 React 开发的看法。现在看来似乎并不那么简单,但是几年后,class 使用率将逐渐下降。很高兴React团队不强迫我们重写所有项目,也鼓励我们与类一起使用Hooks。他们不会在不久的将来弃用 class,因此我们当然有时间尝试Hooks。拥有直接在类中使用 Hooks 逻辑的方法(如本文中所示),只是尝试使用Hooks的一个优点。

原文: infinum.co/the-capsize…

最后照旧是一个广告贴,最近新开了一个分享技术的公众号,欢迎大家关注👇(目前关注人数可怜🤕)