专业编程基础技术教程

网站首页 > 基础教程 正文

2023 年新 React 文档中的九个最佳范式

ccvgpt 2024-08-07 18:54:54 基础教程 15 ℃

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

本文主要内容来自Josh Justice发表的《The nine best recommendations in the new React docs》,但是对文章的很多内容进行了部分修改。

2023 年新 React 文档中的九个最佳范式

前言

React 最新的文档实际上就如何编写 React 代码提出了很多建议。 自从这些文档首次以测试版形式发布以来,获得了更多开发者的关注,比如作为团队有关代码风格的对话基础。

该文章将重点介绍 React 文档中的建议,这些建议在团队关于 React 代码风格的讨论中最常出现。 同时,每个内容都将分享一个简短的摘要,通常还有一个代码片段。当然,通过关注 React 文档相关部分的链接,可以找到更多信息和基本原理。

其中一些建议可能是代码风格的意见,没有真正的后果。 然而,正如 React 文档所解释,在每种情况下偏离建议都会带来成本或风险。 当然,开发者可以自由做出决定,但请记住,React 核心团队对某些事情的思考肯定比普通开发者要多得多。 但这并不意味着他们总是对的,但至少听听他们的意见是个好主意。

为循环中的元素选择 key 时,对于同一条目始终相同的标识符,而不是数组索引

React 使用 key 来跟踪渲染中的列表元素。 如果添加、删除或重新排序元素,那么索引键将误导 React,从而导致错误。

//  错误范式
return (
  <ul>
    {items.map((item, index) => (
      <li key={index}>…</li>
    ))}
  </ul>
);
//  正确,假设 item.id 是一个稳定的唯一标识符
return (
  <ul>
    {items.map((item, index) => (
      <li key={item.id}>…</li>
    ))}
  </ul>
);

更多细节可以参考文档:https://react.dev/learn/rendering-lists#why-does-react-need-keys

定义组件时将其定义在文件/模块的顶层,而不是嵌套在另一个组件或函数中

有时,在另一个组件中定义一个组件似乎很常见。 但这会导致组件在每次渲染时都被视为不同的组件,从而导致应用性能下降。

//  错误范式
function ParentComponent() {
  // ...
  function ChildComponent() {…}
  return <div><ChildComponent /></div>;
}

//  正确范式
function ChildComponent() {…}
function ParentComponent() {
  return <div><ChildComponent /></div>;
}

更多细节可以参考文档:https://react.dev/learn/your-first-component#nesting-and-organizing-components

当决定 state 中存储内容时,可以只存储可用于 compute 所需内容的最小表示形式

这使得 state 很容易更新,而不会引入错误,可以防止不同的状态彼此失效或变得不一致。

//  错误范式
const [allItems, setAllItems] = useState([]);
const [urgentItems, setUrgentItems] = useState([]);
// 处理事件
function handleSomeEvent(newItems) {
  setAllItems(newItems);
  setUrgentItems(newItems.filter((item) => item.priority === 'urgent'));
}

//  正确范式
const [allItems, setAllItems] = useState([]);
const urgentItems = allItems.filter((item) => item.priority === 'urgent');

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

更多细节可以参考文档:https://react.dev/learn/choosing-the-state-structure#principles-for-structuring-state

请推迟使用 useMemo、useCallback 或 React.memo 进行缓存,直到观察到性能问题

尽管使用缓存并没有什么缺点,但会使代码的可读性较差。

//  错误范式
const [allItems, setAllItems] = useState([]);
const urgentItems = useMemo(() => (
  allItems.filter(item => item.status === 'urgent'
), [allItems]);

//  正确范式(直到观察到性能问题)
const [allItems, setAllItems] = useState([]);
const urgentItems = allItems.filter(item => item.priority === 'urgent');

更多细节可以参考文档:

  • https://react.dev/reference/react/useMemo#should-you-add-usememo-everywhere
  • https://react.dev/reference/react/useCallback#should-you-add-usecallback-everywhere
  • https://react.dev/reference/react/memo#should-you-add-memo-everywhere

将共享代码提取到函数中时,如果调用其他钩子,则仅将其命名为钩子

如果函数调用其他 Hook,本身需要是一个 Hook 以便 React 可以对允许工作的 Hook 施加限制。

如果函数不调用其他 Hook,则没有理由选择接受这些限制。 作为非 Hook,函数将更加通用,因为可以从任何地方调用,包括在条件语句中。

//  错误范式
function useDateColumnConfig() {
  // 受限于Hooks约束
  return {
    dataType: 'date',
    formatter: prettyFormatDate,
    editorComponent: DateEditor,
  };
}

//  正确范式
function getDateColumnConfig() {
  //可以在任何地方调用
  return {
    dataType: 'date',
    formatter: prettyFormatDate,
    editorComponent: DateEditor,
  };
}

function useNameColumnConfig() {
  // 受限约束
  const { t } = useTranslation();
  return {
    dataType: 'string',
    title: t('columns.name'),
  };
}

更多细节可以参考文档:https://react.dev/learn/reusing-logic-with-custom-hooks#should-all-functions-called-during-rendering-start-with-the-use-prefix

当需要更新 state 以响应 prop 更改时,请直接在组件函数中(渲染期间)设置状态,而不是在 Effect 中

如果需要更新状态以响应 prop 更改,那么最好首先确认确实需要这样做。 如果可以在渲染期间导出数据或使用 key 重置状态则是首选。

如果确实需要调整部分状态,那么考虑 React 文档中关于 Effect 的一个关键点,即Effect 是 React 范式的最后一扇窗,可以让开发者走出 React,并将组件与一些外部系统同步。当只需要快速更新状态以响应 prop 更改时,则不需要这种复杂性

//  错误范式
function List({ items }) {
  const [selection, setSelection] = useState(null);
  useEffect(() => {
    setSelection(null);
  }, [items]);
  //使用useEffect
}
//  正确范式
function List({ items }) {
  const [prevItems, setPrevItems] = useState(items);
  const [selection, setSelection] = useState(null);
  if (items !== prevItems) {
    // 使用state修改
    setPrevItems(items);
    setSelection(null);
  }
}

更多细节可以参考文档:https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes

当需要获取数据时,更喜欢使用库而不是 useEffect

useEffect 数据获取可能会出现隐藏的错误,除非编写大量样板来处理, React 文档提供了许多关于更好获取数据的库的建议和优化。

//  错误范式
const [items, setItems] = useState();
useEffect(() => {
  api.loadItems().then((newItems) => setItems(newItems));
}, []);

//  正确范式(库的选择)
import { useQuery } from '@tanstack/react-query';
const { data: items } = useQuery(['items'], () => api.loadItems());

更多细节可以参考文档:https://react.dev/learn/synchronizing-with-effects#what-are-good-alternatives-to-data-fetching-in-effects

事件处理取代 Effect

当需要采取行动来响应事件时,可以将代码编写在事件处理程序中,而不是在 useEffect 中,从而确保每次事件发生时代码仅运行一次。

const [savedData, setSavedData] = useState(null);
const [validationErrors, setValidationErrors] = useState(null);

//  错误范式
useEffect(() => {
  if (savedData) {
    setValidationErrors(null);
  }
}, [savedData]);

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

//  正确范式:事件处理
async function saveData() {
  const response = await api.save(data);
  setSavedData(response.data);
  setValidationErrors(null);
}

更多细节可以参考文档:

  • https://react.dev/learn/synchronizing-with-effects#what-are-effects-and-how-are-they-different-from-events
  • https://youtu.be/eFGeStq8dZo?si=hHaDYzy3pDT4mpSx

当 useEffect 依赖项导致不希望的重新渲染(包括无限循环)时,不要只是从数组中删除依赖项:也要从 Effect 函数中删除依赖项

简而言之,使用未在依赖项数组中列出的依赖项可能意味着该 Effect 正在用于除效果预期用途之外的其他用途,比如:同步。 这可能迟早会导致难以诊断的错误。

参考资料

https://blog.testdouble.com/posts/2023-10-16-react-docs-recommendations/#8-when-you-need-to-take-an-action-in-response-to-an-event-occurring-write-the-code-in-an-event-handler-not-in-a-useeffect

Tags:

最近发表
标签列表