写 React 组件的五个常见错误

前言

很早之前阅读的一篇文章,最近翻到仍觉得有所受益,因此翻译让更多的人看到。

原文地址:
Five common mistakes writing react components (with hooks) in 2020

正文

我在编写 react 组件时发现的最常见的错误是,为什么它们是错误,以及如何避免或修复它们。

React 作为一个框架

React 在 web 开发领域已经有一段时间了。它作为敏捷 web 开发工具的地位近年来稳步加强。特别是在新的 hooks 概念发布之后,编写组件变得前所未有的容易。
尽管背后的团队做出了反应,庞大的社区也试图以一种令人印象深刻的方式培训和解释框架的概念,但我仍然看到在使用框架时出现了一些陷阱和常见错误。我列出了过去几年我所看到的所有与反应有关的错误,特别是使用钩子。在这篇文章中,我想向你展示最常见的几种。我也会试着详细解释,为什么我认为他们是错误的,并建议以一种更干净的方式去做。

免责声明

在我们开始这个列表之前,我必须说,下面的大多数事情不是根本错误,或者第一眼看上去不是错误的。它们中的大多数都不太可能影响应用程序的性能或外观。可能除了开发产品的开发人员之外,没有人会注意到这里有什么问题,但我仍然相信,高质量的代码可以带来更好的开发体验,从而产生更好的产品。
与任何软件框架或库一样,关于它有数百万种不同的观点。你在这里看到的一切都是基于我的个人观点,不应该被认为是一个普遍的规则。如果你对 ta 有不同的看法,我很乐意听听。

1. 当不需要渲染时仍使用 useState

React 的核心概念之一是处理状态。您可以通过状态控制整个数据流和呈现。每次再次呈现树时,它极有可能与状态更改绑定在一起。
使用 useState 钩子,您现在还可以在函数组件中定义状态。这是一种处理 React 状态的简洁而简单的方法。但它也可能被滥用,正如我们在下面的例子中看到的那样。
对于下一个示例,我们需要进行一些解释,假设我们有两个按钮,一个按钮是计数器,另一个按钮发送请求或用当前计数触发一个操作。但是,在组件中永远不会显示当前编号。只有当您单击第二个按钮时,请求才需要它。

This is dangerous❌

function ClickButton(props) {
  const [count, setCount] = useState(0);

  const onClickCount = () => {
    setCount((c) => c + 1);
  };

  const onClickRequest = () => {
    apiCall(count);
  };

  return (
    <div>
      <button onClick={onClickCount}>Counter</button>
      <button onClick={onClickRequest}>Submit</button>
    </div>
  );
}

The problem⚡

乍一看,你可能会问这到底有什么问题?这不就是建立国家的目的吗?当然你是对的,这将工作只是很好,可能永远不会有问题。然而,在 react 中,每一个状态的改变都会强制该组件和它的子组件进行渲染。但在上面的例子中,因为我们从来没有在渲染部分使用这种状态,所以每次我们设置计数器时,这将成为一个不必要的渲染,这可能会影响性能或产生意想不到的副作用。

The solution✅

如果你想在你的组件中使用一个变量,它应该在渲染之间保持它的值,但又不强制渲染,你可以使用 useRef 钩子。它将保留该值,但不强制组件重新呈现。

function ClickButton(props) {
  const count = useRef(0);

  const onClickCount = () => {
    count.current++;
  };

  const onClickRequest = () => {
    apiCall(count.current);
  };

  return (
    <div>
      <button onClick={onClickCount}>Counter</button>
      <button onClick={onClickRequest}>Submit</button>
    </div>
  );
}

2. 使用 router.push 而不是 Link

这可能是一个非常简单和明显的问题,而且与 React 本身并没有太大关系,但我在编写反应组件时仍然经常看到这种情况。
假设您将编写一个按钮,通过单击该按钮,用户将被重定向到另一个页面。因为它是一个SPA,所以这个动作将是一个客户端路由机制。所以你需要一些库来做这个。在react中最流行的是 react-router,下面的例子将使用这个库。
因此,添加一个单击侦听器将用户重定向到所需的页面,对吗?

This is dangerous❌

function ClickButton(props) {
  const history = useHistory();

  const onClick = () => {
    history.push('/next-page');
  };

  return <button onClick={onClick}>Go to next page</button>;
}

The problem ⚡

尽管这对于大多数用户来说都是可行的,但是在可访问性方面存在一个巨大的问题。该按钮将不会被标记为链接到其他页面,这使得它几乎不可能被屏幕阅读器识别。你可以在一个新的标签或窗口中打开它吗?很可能不。

The solution✅

在可能的情况下,通过任何用户交互链接到其他页面应该由 组件或普通的 标签处理。

function ClickButton(props) {
  return (
    <Link to="/next-page">
      <span>Go to next page</span>
    </Link>
  );
}

3.通过 useEffect 处理 actions

React 推出的最好、最体贴的钩子之一是 useEffect 钩子。它支持处理与 propsstate 更改相关的操作。尽管它具有有用的功能,但它也经常被用于可能不需要它的地方。
假设有一个组件获取一个项列表并将它们呈现给dom。此外,如果请求成功,我们希望调用“onSuccess”函数,该函数作为道具传递给组件。

This is dangerous ❌

function DataList({ onSuccess }) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [data, setData] = useState(null);

  const fetchData = () => {
    setLoading(true);
    callApi()
      .then((res) => setData(res))
      .catch((err) => setError(err))
      .finally(() => setLoading(false));
  };

  useEffect(() => {
    fetchData();
  }, []);

  useEffect(() => {
    if (!loading && !error && data) {
      onSuccess();
    }
  }, [loading, error, data, onSuccess]);

  return <div>Data: {data}</div>;
}

The problem⚡

有两个 useEffect 钩子,第一个是处理初始渲染的 api 调用,第二个将调用 onSuccess 函数,假设当没有加载,没有错误,但数据在状态,它必须是一个成功的调用。是有意义的对吧?
当然,对于第一次调用,这是真的,可能永远不会失败。但是您也失去了操作和需要调用的函数之间的直接联系。我们不能保证这种情况只会发生在获取操作成功的情况下,这是我们作为开发人员真的不喜欢的事情。

The solution✅

一个直接的解决方案是将 onSuccess 函数设置为调用成功的实际位置:

function DataList({ onSuccess }) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [data, setData] = useState(null);

  const fetchData = () => {
    setLoading(true);
    callApi()
      .then((fetchedData) => {
        setData(fetchedData);
        onSuccess();
      })
      .catch((err) => setError(err))
      .finally(() => setLoading(false));
  };

  useEffect(() => {
    fetchData();
  }, []);

  return <div>{data}</div>;
}

现在,当 onSuccess 被调用时,第一眼看上去就很清楚了,确切地说,是在 api 调用成功的情况中。

4. 单一责任组件

组合组件可能很困难。什么时候可以将一个组件拆分成几个更小的组件?如何构造组件树?在使用基于组件的框架时,所有这些问题每天都会出现。然而,在设计组件时,一个常见的错误是将两个用例合并到一个组件中。让我们以标题为例,它要么显示移动设备上的汉堡按钮,要么显示桌面屏幕上的标签。(这个条件将被神奇的 isMobile 函数处理,它不是这个例子的一部分‍)

This is dangerous ❌

function Header({ menuItems }) {
  return (
    <header>
      <HeaderInner menuItems={menuItems} />
    </header>
  );
}

function HeaderInner({ menuItems }) {
  return isMobile() ? <BurgerButton menuItems={menuItems} /> : <Tabs tabData={menuItems} />;
}

The problem ⚡

通过这种方法,组件 HeaderInner 试图同时成为两种不同的东西,我们都从 Jekyll 先生那里学到了,一次做不止一件事并不是很理想。这也使得在其他地方测试或重用组件变得更加困难。

The solution ✅

将条件提升一级,可以更容易地看到组件的用途,以及它们只有一个职责,即 HeaderTabsBurgerButton,而不是试图同时成为两个东西。

function Header(props) {
  return (
    <header>{isMobile() ? <BurgerButton menuItems={menuItems} /> : <Tabs tabData={menuItems} />}</header>
  );
}

5. 单一责任 useEffects

还记得我们只有 componentWillReceivePropscomponentDidUpdate 方法来钩子到 react 组件的渲染过程的时候吗?它找回了黑暗的记忆,也实现了使用 useEffect 钩子的美丽,特别是你可以拥有你想要的任何数量的它们。
但有时遗忘和在一些事情上使用 useEffect 会带回那些黑暗的记忆。例如,假设您有一个组件,它以某种方式从后端获取一些数据,并根据当前位置显示面包屑。(再次使用 react-router 获取当前位置。)

This is dangerous ❌

function Example(props) {
  const location = useLocation();

  const fetchData = () => {
    /*  Calling the api */
  };

  const updateBreadcrumbs = () => {
    /* Updating the breadcrumbs*/
  };

  useEffect(() => {
    fetchData();
    updateBreadcrumbs();
  }, [location.pathname]);

  return (
    <div>
      <BreadCrumbs />
    </div>
  );
}

The problem ⚡

有两个使用情况,“数据获取”和“显示面包屑”。两者都用同一个 useEffect 钩子更新。这个单一的 useEffect 钩子将在 fetchDataupdateBreadcrumbs 函数或 location 改变时运行。现在的主要问题是,当 location 发生变化时,我们也会调用 fetchData 函数。这可能是我们没有想到的副作用。

The solution ✅

确保分离 effect,他们只用于一个 effect 和意想不到的副作用消失。

function Example(props) {
  const location = useLocation();

  const updateBreadcrumbs = () => {
    /* Updating the breadcrumbs*/
  };

  useEffect(() => {
    updateBreadcrumbs();
  }, [location.pathname]);

  const fetchData = () => {
    /*  Calling the api */
  };

  useEffect(() => {
    fetchData();
  }, []);

  return (
    <div>
      <BreadCrumbs />
    </div>
  );
}

额外的好处是,现在使用情况也在组件中按照逻辑排序。

总结

在 react 中编写组件时有很多陷阱。要理解整个机制并避免每一个小的甚至大的错误是不可能的。但在学习框架或编程语言时,犯错误也很重要,可能没有人能 100% 避免这些错误。
我认为与他人分享你的经验会对他人很有帮助,也会防止他们重蹈覆辙。

结束

我仅翻译文章,也是留下学习记录。有任务问题还请指正,谢谢~

你可能感兴趣的:(React,react,javascript)