译:像专家一样使用 React:我希望早点知道的 10 件事

原文:https://www.frontendjoy.com/p/react-like-a-pro-10-things-i-regret-not-knowing-earlier
作者:Ndeye Fatou Diop
译者:ChatGPT 4 Turbo

如果你是一个被 React 搞得焦头烂额的初级开发者,你并不孤单。

当我刚开始时,我犯了很多错误——如果我一开始就知道这十件事,这些错误本可以避免。

让我帮你绕过这些弯路。

目录

1. 使用 children 属性大幅提升应用性能

children 属性不仅仅用于传递嵌套元素。

它是一个强大的工具,具有以下优势:

  • 避免属性钻取:直接将属性传递给子组件,而不是通过父组件路由。
  • 编写可扩展代码:无需修改父组件即可修改子组件。
  • 避免"慢速"组件不必要的重渲染(见下面的例子 👇)。

错误示范: 每当 Dashboard 渲染时(即每次当前时间更新时),MyVerySlowComponent 都会重新渲染。

function App() {
  // 其他逻辑...
  return <Dashboard />;
}

function Dashboard() {
  const [currentTime, setCurrentTime] = useState(new Date());
  useEffect(() => {
    const intervalId = setInterval(() => {
      setCurrentTime(new Date());
    }, 1_000);
    return () => clearInterval(intervalId);
  }, []);

  return (
    <>
      <h1>{currentTime.toTimeString()}</h1>
      <MyVerySlowComponent />
    </>
  );
}

💡 你可以在这里看到使用 React Developer Tool 的分析器展示的性能问题。

正确示范: MyVerySlowComponent 不会不必要地重新渲染。

function App() {
  return (
    <Dashboard>
      <MyVerySlowComponent />
    </Dashboard>
  );
}

function Dashboard({ children }) {
  const [currentTime, setCurrentTime] = useState(new Date());
  useEffect(() => {
    const intervalId = setInterval(() => {
      setCurrentTime(new Date());
    }, 1_000);
    return () => clearInterval(intervalId);
  }, []);

  return (
    <>
      <h1>{currentTime.toTimeString()}</h1>
      {children}
    </>
  );
}

💡 你可以在这里看到优化后的效果。

2. 何时使用 Refs 而不是 State

Refs 非常适合那些不应触发重新渲染的值。

与其默认使用 state,不如先问问自己:“这个值改变时需要触发重新渲染吗?” 如果答案是否定的,就使用 ref。

它们非常适合跟踪可变值,如计时器、DOM 元素或在渲染之间保持但不影响 UI 的值。

错误示范:我们在 state 中存储 intervalId。当 intervalId 状态改变时组件会重新渲染,即使 UI 保持不变。

function Timer() {
  const [time, setTime] = useState(new Date());
  const [intervalId, setIntervalId]= useState();

  useEffect(() => {
    const id = setInterval(() => {
      setTime(new Date());
    }, 1_000);
    setIntervalId(id);
    return () => clearInterval(id);
  }, []);

  const stopTimer = () => {
    intervalId && clearInterval(intervalId);
  };

  return (
    <>
      <>当前时间:{time.toLocaleTimeString()} </>
      <button onClick={stopTimer}>停止计时器</button>
    </>
  );
}

正确示范:我们将 intervalId 存储为 ref。这意味着我们不会有额外的状态触发重新渲染。

function Timer() {
  const [time, setTime] = useState(new Date());
  const intervalIdRef = useRef();
  const intervalId = intervalIdRef.current;

  useEffect(() => {
    const id = setInterval(() => {
      setTime(new Date());
    }, 1_000);
    intervalIdRef.current = id;
    return () => clearInterval(id);
  }, []);

  const stopTimer = () => {
    intervalId && clearInterval(intervalId);
  };

  return (
    <>
      <>当前时间:{time.toLocaleTimeString()} </>
      <button onClick={stopTimer}>停止计时器</button>
    </>
  );
}

3. 优先使用命名导出而不是默认导出

命名导出使重构和调试更容易。

错误示范:重命名需要手动更新。

export default function Button() {}
// 之后...
import Button from './Button';

正确示范:重命名会自动发生。

export function Button() {}
// 之后...
import { Button } from './Button';

你可以在这篇文章中找到更多关于命名导入的论据 👉 101 个 React 技巧和窍门

4. 尽可能避免使用 useEffect

我处理过的每一次应用崩溃背后都藏着 useEffect 😅。

说真的,尽量避免使用 useEffect

  • 它很容易导致过度渲染、代码晦涩等问题。
  • 它使代码流程更难追踪。

相反,考虑是否可以不使用它来实现相同的结果。

错误示范:在 useEffect 中调用 onToggled

function Toggle({ onToggled }) {
    const [on, setOn] = React.useState(false);
    const toggle = () => setOn(!on);
    
    useEffect(() => {
        onToggled(on);
    }, [on]);

    return (
        <button onClick={toggle}>
            {on ? 'on' : 'off'}
        </button>
    );
}

正确示范:在相关时机调用 onToggled

function Toggle({ onToggled }) {
    const [on, setOn] = React.useState(false);
    const handleToggle = () => {
        const next = !on;
        setOn(next);
        onToggled(next);
    };
    return (
        <button onClick={handleToggle}>
            {on ? 'on' : 'off'}
        </button>
    );
}

你可以在这里找到更多避免使用 useEffect 的技巧 👉 你可能不需要 Effect

5. 理解 React 的生命周期

了解生命周期阶段可以避免 bug 和性能问题:

  • 挂载:组件首次渲染时。
  • 更新:当某些状态改变并且 React 重新渲染时。
  • 卸载:当组件从 DOM 中移除时。

6. 使用 ESLint 追踪低级错误

ESLint 可以帮你避免微妙的 bug,特别是在使用 hooks 时。

错误示范useEffect 中缺少依赖项。

useEffect(() => {
  console.log(data);
}, []); // 缺少依赖项!

正确示范:使用 ESLint 和插件如 eslint-plugin-react-hooks

7. 使用 React DevTools 更智能地调试

React DevTools 对调试性能问题来说是黄金工具。使用"Profiler"标签来找出慢速组件。

错误示范:仅依赖 console.log 和计时器来猜测应用为什么慢。

正确示范:使用 Profiler 来:

  • 找出过度渲染的组件。
  • 通过火焰图定位昂贵的渲染。

💡 在这个很棒的指南中学习如何使用它。

8. 使用错误边界避免整体崩溃

默认情况下,如果你的应用在渲染过程中遇到错误,整个 UI 都会崩溃 💥。

为了防止这种情况,使用错误边界来:

  • 即使发生错误,也能保持应用的部分功能。
  • 显示用户友好的错误消息,并可选择跟踪错误。

💡 提示:你可以使用 react-error-boundary

9. 像专业人士一样组织代码

整洁的代码就是可维护的代码。

你可以遵循这些简单的提示:

  • 将组件与相关资源(如样式、测试等)放在一起
  • 保持文件小巧
  • 将相关组件分组到文件夹中

错误示范:当你删除 Button.js 时,很容易忘记 Button.css

src/
  components/
    Button.js
    Modal.js
styles/
  Button.css

正确示范:我们将相关文件放在一起。

src/
  components/
    Button/
      Button.js
      Button.css
      Button.test.js

10. React 官网拥有你需要的一切

不要忽视官方 React 文档。它们包含了大量示例、解释和最佳实践。

错误示范:无休止地搜索基本的 React 概念,最终落在过时的博客文章上。

正确示范:将官方文档加入书签,遇到问题时首先查阅它。

总结

掌握 React 需要时间。

但这些经验教训将帮你避免我早期的错误。

继续编码,保持好奇心,记住——你一定能行!