学习如何轻松构建可伸缩的 React 应用程序:典型反例和最佳实践。
# 反例
# 内联样式 或 CSS
内联样式使用起来非常简单,只需要在元素上添加一个 style
属性即可。
import React from "react";
const App = () => {
return (
<div>
<h1>Inline Styles</h1>
<p style={{ color: "red", fontSize: "20px" }}>This is a paragraph with inline styles.</p>
</div>
);
};
export default App;
但是,如果你需要在其他地方使用相同样式,那么你需要复制粘贴样式。
代码语言:javascript复制import React from "react";
const App = () => {
return (
<div>
<h1>Inline Styles</h1>
<p style={{ color: "red", fontSize: "20px" }}>This is a paragraph with inline styles.</p>
<p style={{ color: "red", fontSize: "20px" }}>This is a paragraph with inline styles.</p>
</div>
);
};
export default App;
总得来说,内联样式不是一个好的选择,因为它们会导致:
- 难以复用
- 难以维护,并且代码不易阅读
- 影响性能,每次重新渲染时,样式对象都会被重新计算
# 大组件
React 使用可重用组件作为应用程序的基本单元。然而,我们有时会编写过于冗长和难以阅读的组件,包括从逻辑到显示呈现的所有内容。这会导致调试和修复困难。
# Props 穿透
当我们需要在组件树中传递数据时,我们可以使用 props
。但是,当我们需要在组件树中传递函数时,我们就会遇到问题。这是因为,当我们在组件树中传递函数时,我们需要将函数传递给每个组件,这会导致组件树变得非常深。
可以在必要的时候,使用 Context
或 Redux
来解决这个问题。
# 不要传入所有 props
当我们使用 props
时,可能将所有 props
传递给子组件,这会导致子组件不必要的重新渲染,并不是所有 props
都是子组件需要的。
import React from "react";
const App = () => {
return <Component {...props} />;
};
export default App;
# 在遍历中不使用 key
当我们想要向用户呈现列表时,通常使用 map
方法循环遍历列表或数组,并将其显示给用户。但是,列表中的每个项目都需要是唯一的,因为 React 使用 key
来跟踪 DOM 中的所有记录。如果没有 key
,React 将无法知道添加、删除或修改了什么。
import React from "react";
const App = () => {
const list = ["item1", "item2", "item3"];
return (
<div>
<h1>Keys</h1>
<ul>
{list.map((item) => (
<li>{item}</li>
))}
</ul>
</div>
);
};
export default App;
# 不要使用索引作为 key
当我们使用 map
方法遍历列表或数组时,我们可以使用索引作为 key
。但是,这是一个反模式,React 无法识别哪个项目是添加/删除/重新排序的,因为索引是根据数组中项目的顺序在每次渲染时给出的。虽然它通常可以正确渲染,但仍然有一些情况会导致失败。
当重新渲染时,组件将被销毁并重新创建。这将导致在渲染列表时出现一些不一致性。
代码语言:javascript复制import React from "react";
const App = () => {
const list = ["item1", "item2", "item3"];
return (
<div>
<h1>Keys</h1>
<ul>
{list.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
};
export default App;
# 使用嵌套的三元运算符
三元运算符是一种简单的方法,用于根据条件渲染组件。但是,当我们使用嵌套的三元运算符时,代码会变得非常难以阅读。
代码语言:javascript复制import React from "react";
const App = () => {
const isUserLoggedIn = true;
const isUserAdmin = true;
return (
<div>
<h1>Conditional Rendering</h1>
{isUserLoggedIn ? isUserAdmin ? <p>Admin</p> : <p>User</p> : <p>Guest</p>}
</div>
);
};
export default App;
解决这个问题的最好方法是创建不同的三元运算符语句或使用 if…else
语句。
import React from "react";
const App = () => {
const isUserLoggedIn = true;
const isUserAdmin = true;
const UserComp = () => {
if (isUserLoggedIn) {
if (isUserAdmin) {
return <p>Admin</p>;
} else {
return <p>User</p>;
}
} else {
return <p>Guest</p>;
}
};
return (
<div>
<h1>Conditional Rendering</h1>
<UserComp />
</div>
);
};
export default App;
# 命名模糊
在我们的应用程序中,往往没有意识到如何命名变量、文件夹、文件等等,随意编写名称。
基本上,有两种命名约定,如下所示:
- Pascal Case(大驼峰命名法)
- Camel Case(小驼峰命名法)
无论您的组织或项目将哪种大小写规范作为标准,请记住,您命名文件夹、变量、函数或方法时应该让它们有一定的意义,不仅对您有意义,也对将来阅读或与它们一起工作的其他人有意义。
# 直接修改 State
当我们想要更新 state
时,我们可以直接修改 state
,但这是一个反模式,因为它会导致组件重新渲染。我们应该使用 setState
方法来更新 state
。
import React, { useState } from "react";
const App = () => {
const [count, setCount] = useState(0);
const increment = () => {
// count = count 1; // This is a bad practice
setCount(count 1); // This is a good practice
};
return (
<div>
<h1>State</h1>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
export default App;
# 不要直接为 HTML 标签设置样式
代码语言:javascript复制/* bad practice */
h1 {
color: red;
}
直接为 HTML 标签设置样式会导致样式冲突,因为它会影响整个应用程序中的所有标签。
# 避免不必要的 div
我们很容易在应用程序渲染中大多数时候使用 div
元素。当我们编写组件时,第一个在渲染中插入 div
元素的想法就会浮现,无论是在类组件的 render
方法中还是在函数式组件的返回语句中。虽然这种做法有效,但它并没有为浏览器提供足够的信息。
使用具有语义的标签,它可以向浏览器提供关于 React 应用程序中的部分足够的信息,如 header
、section
、nav
等。HTML 语义标签还有助于 SEO。
# 不要直接访问 props
当我们想要访问 props
时,我们可以直接访问 props
,但这是一个反模式,在多个地方使用时可能会变得混乱,推荐使用解构赋值来访问 props
。
import React from "react";
const App = (props) => {
return (
<div>
<h1>Props</h1>
<p>{props.name}</p>
</div>
);
};
export default App;
更好的做法是使用解构赋值:
代码语言:javascript复制import React from "react";
const App = ({ name }) => {
return (
<div>
<h1>Props</h1>
<p>{name}</p>
</div>
);
};
export default App;
# 最佳实践
# 样式
# 使用 CSS 模块
- CSS 模块允许您在 CSS 文件中编写样式,但将它们作为 JavaScript 对象进行使用以进行其他处理和安全性
- CSS 模块会自动使类名和动画名称唯一,不必担心选择器名称冲突
- CSS 模块的命名方式为
[name].modules.css
,其中name
是文件名
/* App.modules.css */
.container {
background-color: #f5f5f5;
padding: 20px;
border-radius: 5px;
}
代码语言:javascript复制
import React from "react";
import styles from "./App.modules.css";
const App = () => {
return (
<div className={styles.container}>
<h1>React Best Practices</h1>
</div>
);
};
export default App;
# 使用 styled-components
styled-components
是一个 JavaScript 库,它允许您使用组件化的、样式化的 JavaScript 来编写 CSS
import React from "react";
import styled from "styled-components";
const Container = styled.div`
background-color: #f5f5f5;
padding: 20px;
border-radius: 5px;
`;
const App = () => {
return (
<Container>
<h1>React Best Practices</h1>
</Container>
);
};
export default App;
# 样式预处理器(SASS/LESS)
SASS 和 LESS 是非常好的 CSS 预处理器。CSS 预处理器是一种程序,它可以让您从预处理器自己独特的语法中生成 CSS。
# 文件 / 文件夹结构
大多数时候,我们不确定如何设置文件夹和文件结构以最好地服务于我们正在开发的应用程序。
但我们应该记住的一件事是文件/文件夹结构很重要,因为它给应用程序提供了方向感,告诉您在哪里放置/查找某些内容。在制定文件结构时,您可以依据个人喜好,但也要始终考虑到接下来接手代码的人。
# 命名规范
当变量、函数、方法以及文件/文件夹的命名得当时,追踪应用程序中的问题就变得简单,因为您知道每个变量或函数在做什么。
通常建议在为应用程序命名时牢记这一点。
# 使用 useMemo 和 useCallback 进行渲染
使用 useMemo
和 useCallback
是在使用 React hooks 时非常有效的性能优化方法。
useMemo
用于缓存计算结果并返回其值。这个计算不会在每次渲染时都执行。它接受两个参数,即箭头函数和依赖数组。依赖数组是可选的,但如果传递了参数,则仅当参数发生更改时,函数才会再次运行,并返回结果值。
useCallback
的工作方式与 useMemo
相同,但主要区别在于 useMemo
返回记忆的值,而 useCallback
返回记忆的函数。
import React, { useState, useMemo, useCallback } from "react";
const App = () => {
const [count, setCount] = useState(0);
const [name, setName] = useState("");
const increment = useCallback(() => {
setCount(count 1);
}, [count]);
const handleChange = useCallback((e) => {
setName(e.target.value);
}, []);
const memoizedValue = useMemo(() => {
return count * 2;
}, [count]);
return (
<div>
<h1>useMemo and useCallback</h1>
<p>{count}</p>
<button onClick={increment}>Increment</button>
<p>{name}</p>
<input type="text" value={name} onChange={handleChange} />
<p>{memoizedValue}</p>
</div>
);
};
export default App;
# 抽离复用的逻辑
在 React 应用程序中,提取可重复使用的逻辑非常重要。可以将整个应用程序中要使用的逻辑提取到一个组件中,并在任何时候使用。这是另一种 DRY 技术,这将使您免受许多代码行的影响,并隔离错误。
# 使用 try/catch
无论我们的应用程序多么完美,都难免会出现错误。错误可能来自于 API,甚至可能来自于用户输入,我们没有预料到或在测试期间没有考虑到。这就是为什么始终使用 try
和 catch
包装您的逻辑或 API 调用非常重要,以便捕获意外错误。
# 输出 Error 日志
尽管我们捕获错误,但我们也需要记录它们。记录这些错误可以告诉我们应用程序操作生命周期中确切发生了什么。我们可以将此错误记录到文件中,或创建一个服务,将这些错误推送到 API 或甚至数据库中。这是非常重要的,通常是应用程序在生产环境中出现问题时的第一个排查点,它可以挽救全局。
# 测试代码
在开发应用程序时,大多数开发人员不喜欢编写测试代码(我也不例外),但随着时间的推移,我开始尝试于编写单元测试和集成测试。这非常重要,因为随着应用程序的增长,实施测试以避免问题将变得非常重要。测试可以巩固代码,并确保您的功能和逻辑按预期工作。
这也很重要,因为您可以对边缘案例并进行测试,而不像运行应用程序并从屏幕传递参数测试那样遇到许多问题。这节省了大量时间,并且可以确保您的代码已经准备好生产,同时减少了测试人员发现的可能错误的数量。
# 使用 Prettier
Prettier 是一种开源工具,可强制执行代码一致性。它可以使您的代码看起来整洁易读,遵守您设定的规则。
# 使用 Linter
使用 Linter 及其规则可以帮助您组织代码,提醒您某些 JavaScript 错误。在您的应用程序中使用 Linter 和其规则可以让您的工作更轻松。
# 使用 JS 还是 TS
使用 JavaScript 的好处:
- 无需学习新的语言
- 无需编译、配置
使用 TypeScript 的好处:
- 类型安全,代码更加可靠
- 接口:定义复杂类型,有助于检查传递的类型,因此确保减少错误的数量