[译] 新版 React 文档中的九个最佳建议

通常,当一个新的软件开发团队成立时,团队成员会对如何编写代码进行一些讨论。当编程语言或主流框架的维护者对代码的编写有自己想法时,这有助于给团队的这些讨论提供一个默认的起点。

众所周知,React 在某些方面并不具有倾向性,但最新的 React 文档实际上就如何编写 React 代码提出了不少建议。由于这些文档最初是以测试版的形式发布的,我和其他成员越来越多地将它们作为我们团队代码风格讨论的基础。

在这篇文章中,我将推荐 React 文档中的一些建议,这些建议在我团队中讨论频率最高。对于每个建议,我将分享一个简短的摘要和一个代码片段。通过点击 React 文档相关部分的链接,可以找到更多信息和基本原理。

其中一些建议可能会让人感觉像是代码风格的意见,没有任何实际影响。但是,正如 React 文档所解释的那样,在每种情况下,偏离建议都会带来成本或风险。你可以自由地做出自己的决定,但请记住,React 核心团队对这些问题的考虑可能比你或我要多得多。这并不意味着他们总是正确的,但至少听取他们的意见是个好主意。

  • [1. 在为循环中的元素选择 key 时,应使用对同一条目始终保持相同的标识符,而不是使用数组索引](#1. 在为循环中的元素选择 key 时,应使用对同一条目始终保持相同的标识符,而不是使用数组索引 "#item1")
  • [2. 定义组件时,应将其定义在文件/模块的顶层,而不是嵌套在其他组件或函数中](#2. 定义组件时,应将其定义在文件/模块的顶层,而不是嵌套在其他组件或函数中 "#item2")
  • [3. 决定在状态中存储什么内容时,记住可通过已有状态计算得到的内容无需存储在状态中](#3. 决定在状态中存储什么内容时,记住可通过已有状态计算得到的内容无需存储在状态中 "#item3")
  • [4. 在考虑是否使用 useMemo、useCallback 或 React.memo 进行缓存时,请推迟使用缓存,直到发现性能问题](#4. 在考虑是否使用 useMemo、useCallback 或 React.memo 进行缓存时,请推迟使用缓存,直到发现性能问题 "#item4")
  • [5. 将共享代码提取到函数中时,只有在调用到其他 hook 时才将其命名为 hook](#5. 将共享代码提取到函数中时,只有在调用到其他 hook 时才将其命名为 hook "#item5")
  • [6. 当你需要根据 prop 变化调整 state 时,请直接在组件函数中(在渲染过程中)而不是在 effect 中设置状态](#6. 当你需要根据 prop 变化调整 state 时,请直接在组件函数中(在渲染过程中)而不是在 effect 中设置状态 "#item6")
  • [7. 需要获取数据时,优先使用库,而不是 useEffect](#7. 需要获取数据时,优先使用库,而不是 useEffect "#item7")
  • [8. 当需要对用户操作做出响应时,应在 event handler 中编写代码,而不是在 useEffect 中编写代码](#8. 当需要对用户操作做出响应时,应在 event handler 中编写代码,而不是在 useEffect 中编写代码 "#item8")
  • [9. 当一个 useEffect 的依赖导致了你不想要的重复渲染(包括无限循环)时,不要只从数组中移除依赖,也要从 effect 函数中移除依赖](#9. 当一个 useEffect 的依赖导致了你不想要的重复渲染(包括无限循环)时,不要只从数组中移除依赖,也要从 effect 函数中移除依赖 "#item9")

1. 在为循环中的元素选择 key 时,应使用对同一条目始终保持相同的标识符,而不是使用数组索引

React 使用键值在不同渲染中跟踪列表元素。如果元素被添加、删除或重新排序,索引键就会误导 React,从而导致错误。

React 文档中 keys 说明

jsx 复制代码
// 🛑 WRONG
return (
  <ul>
    {items.map((item, index) => (
      <li key={index}>...</li>
  ))}
  </ul>
);

// 🟢 RIGHT, assuming item.id is a stable unique identifier
return (
  <ul>
    {items.map((item, index) => (
      <li key={item.id}>...</li>
  ))}
  </ul>
);

2. 定义组件时,应将其定义在文件/模块的顶层,而不是嵌套在其他组件或函数中

有时,在另一个组件中定义一个组件似乎很方便。但这将导致组件在每次渲染时都被视为不同的组件,从而导致性能低下。

React 文档中组件嵌套说明

jsx 复制代码
// 🛑 WRONG
function ParentComponent() {
  // ...
  function ChildComponent() {...}

  return <div><ChildComponent /></div>;
}

// 🟢 RIGHT
function ChildComponent() {...}

function ParentComponent() {
  return <div><ChildComponent /></div>;
}

3. 决定在状态中存储什么内容时,记住可通过已有状态计算得到的内容无需存储在状态中

这使得状态更新变得容易,同时不会引入错误,因为它避免了不同的状态项因过时而变得不一致。

React 文档中状态构建原则

jsx 复制代码
// 🛑 WRONG
const [allItems, setAllItems] = useState([]);
const [urgentItems, setUrgentItems] = useState([]);

function handleSomeEvent(newItems) {
  setAllItems(newItems);
  setUrgentItems(newItems.filter(item => item.priority === 'urgent'));
}

// 🟢 RIGHT
const [allItems, setAllItems] = useState([]);
const urgentItems = allItems.filter(item => item.priority === 'urgent');

function handleSomeEvent(newItems) {
  setAllItems(newItems);
}

4. 在考虑是否使用 useMemo、useCallback 或 React.memo 进行缓存时,请推迟使用缓存,直到发现性能问题。

虽然总是缓存没有什么大的坏处,但小的坏处是会降低代码的可读性。

React 文档中相关内容:useMemouseCallbackReact.memo

jsx 复制代码
// 🛑 WRONG
const [allItems, setAllItems] = useState([]);
const urgentItems = useMemo(() => (
  allItems.filter(item => item.status === 'urgent'
), [allItems]);

// 🟢 RIGHT (until an observed performance problem)
const [allItems, setAllItems] = useState([]);
const urgentItems = allItems.filter(item => item.priority === 'urgent');

5. 将共享代码提取到函数中时,只有在调用到其他 hook 时才将其命名为 hook

如果你的函数调用了其他 hook,那么它本身就必须是一个 hook,这样 React 加上限制保证 hook 正常运行。如果您的函数不调用其他 hook,那么就没有理由选择让函数受到限制。作为一个非 hook,你的函数将更具通用性,因为它可以从任何地方调用,包括在条件中。

jsx 复制代码
// 🛑 WRONG
function useDateColumnConfig() { // will be subject to hooks restrictions
  return {
    dataType: 'date',
    formatter: prettyFormatDate,
    editorComponent: DateEditor,
  };
}

// 🟢 RIGHT
function getDateColumnConfig() { // can be called anywhere
  return {
    dataType: 'date',
    formatter: prettyFormatDate,
    editorComponent: DateEditor,
  };
}

function useNameColumnConfig() { // has to be a hook since it calls a hook: useTranslation
  const { t } = useTranslation();
  return {
    dataType: 'string',
    title: t('columns.name'),
  };
}

6. 当你需要根据 prop 变化调整 state 时,请直接在组件函数中(在渲染过程中)而不是在 effect 中设置状态

如果计划根据 prop 变化调整 state,最好先确认是否真的需要。如果能在渲染过程中根据 prop 计算出数据(见上文[建议 3](#建议 3 "#item3"))或使用 key 重置所有状态,那就更好了。

如果你确实需要调整部分 state, 考虑 React 文档关于 effects 的一个关键点是有帮助的。 effects "是 React 范式的逃生舱门。它们让您'跳出' React,将您的组件与某些外部系统同步......" 当你只需要根据 prop 变化进行 state 快速更新时,就不需要这种复杂性了。

React 文档中关于根据 prop 更新 state

感谢 React 文档提供的示例代码(略有简化)!

jsx 复制代码
// 🛑 WRONG
function List({ items }) {
  const [selection, setSelection] = useState(null);

  useEffect(() => {
    setSelection(null);
  }, [items]);
  //...
}

// 🟢 RIGHT
function List({ items }) {
  const [prevItems, setPrevItems] = useState(items);
  const [selection, setSelection] = useState(null);

  if (items !== prevItems) {
    setPrevItems(items);
    setSelection(null);
  }
  //...
}

7. 需要获取数据时,优先使用库,而不是 useEffect

使用 useEffect 获取数据时可能会出现一些微妙的错误,除非你编写大量的模板代码来处理这些错误。React 文档提供了推荐了许多优秀的数据获取库。

React 文档中关于数据获取的内容

jsx 复制代码
// 🛑 WRONG
const [items, setItems] = useState();
useEffect(() => {
  api.loadItems().then(newItems => setItems(newItems));
}, []);

// 🟢 RIGHT (one library option)
import {useQuery} from '@tanstack/react-query';

const { data: items } = useQuery(['items'], () => api.loadItems());

8. 当需要对用户操作做出响应时,应在 event handler 中编写代码,而不是在 useEffect 中编写代码

这样可以确保每次事件发生时代码只运行一次。

React 文档中 effects vs. events,或者可以在油管上搜索 effects vs. events。

jsx 复制代码
const [savedData, setSavedData] = useState(null);
const [validationErrors, setValidationErrors] = useState(null);

// 🛑 WRONG
useEffect(() => {
  if (savedData) {
    setValidationErrors(null);
  }
}, [savedData]);

function saveData() {
  const response = await api.save(data);
  setSavedData(response.data);
}

// 🟢 RIGHT
async function saveData() {
  const response = await api.save(data);
  setSavedData(response.data);
  setValidationErrors(null);
}

9. 当一个 useEffect 的依赖导致了你不想要的重复渲染(包括无限循环)时,不要只从数组中移除依赖,也要从 effect 函数中移除依赖

要理解为什么值得这样做可能会有点难;为此,我建议阅读 React 文档中专门介绍 useEffect 的页面。简而言之,使用你未在依赖数组中列出的依赖可能意味着 effect 被用于其他用途,而非 effect 的本意:同步。这迟早会导致难以诊断的 bug。

React 文档中关于移除 effect 依赖的内容

付诸实践

我希望 React 文档中的这些要点能帮助你学习一种新技术、更深入地理解一种技术或向他人解释一种技术。你在 React 项目的开发中有遵循这些建议吗?React 文档中是否还有其他建议经常出现在你的项目中?

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax