[译] 新版 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 文档中是否还有其他建议经常出现在你的项目中?

相关推荐
蟾宫曲3 小时前
在 Vue3 项目中实现计时器组件的使用(Vite+Vue3+Node+npm+Element-plus,附测试代码)
前端·npm·vue3·vite·element-plus·计时器
秋雨凉人心3 小时前
简单发布一个npm包
前端·javascript·webpack·npm·node.js
liuxin334455663 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
qq13267029403 小时前
运行Zr.Admin项目(前端)
前端·vue2·zradmin前端·zradmin vue·运行zradmin·vue2版本zradmin
魏时烟5 小时前
css文字折行以及双端对齐实现方式
前端·css
哥谭居民00015 小时前
将一个组件的propName属性与父组件中的variable变量进行双向绑定的vue3(组件传值)
javascript·vue.js·typescript·npm·node.js·css3
踢足球的,程序猿6 小时前
Android native+html5的混合开发
javascript
2401_882726486 小时前
低代码配置式组态软件-BY组态
前端·物联网·低代码·前端框架·编辑器·web
web130933203986 小时前
ctfshow-web入门-文件包含(web82-web86)条件竞争实现session会话文件包含
前端·github
胡西风_foxww6 小时前
【ES6复习笔记】迭代器(10)
前端·笔记·迭代器·es6·iterator