- 译:101个React技巧#1组件组织
- 译:101个React技巧#2有效的设计模式与技术
- 译:101个React技巧#3Keys&Refs
- 译:101个React技巧#4组织React代码
- 译:101个React技巧#5高效状态管理
- 译:101个React技巧#6React代码优化
- 译:101个React技巧#7React代码调试技巧
- 译:101个React技巧#8测试 React代码
- 译:101个React技巧#9React hook
- 译:101个React技巧#10必知的React库/工具
- 译:101个React技巧#11React与Visual Studio Cod
- 译:101个React技巧#12React 与 TypeScript
- 译:101个React技巧#13其他技巧
1. 使用自闭合标签保持代码简洁
// ❌ 不好:过于冗长
<MyComponent></MyComponent>
// ✅ 好
<MyComponent/>
2. 优先使用 fragments 而非 DOM 节点(如 div、span 等)来组合元素
在 React 中,每个组件必须返回单个元素。不要使用 <div> 或 <span> 包裹多个元素,而是使用 <Fragment> 来保持 DOM 整洁。
❌ 不好: 使用 div 会使你的 DOM 变得混乱,可能需要更多的 CSS 代码。
function Dashboard() {
return (
<div>
<Header />
<Main />
</div>
);
}
✅ 好: <Fragment> 可以包裹元素而不影响 DOM 结构。
function Dashboard() {
return (
<Fragment>
<Header />
<Main />
</Fragment>
);
}
3. 使用 React 片段简写 <></>(除非你需要设置 key)
❌ 不好: 以下代码不必要地冗长。
<Fragment>
<FirstChild />
<SecondChild />
</Fragment>
✅ 好: 除非你需要 key,否则 <></> 更简洁。
<>
<FirstChild />
<SecondChild />
</>;
// 由于需要 key,这里必须使用 `Fragment`。
function List({ users }) {
return (
<div>
{users.map((user) => (
<Fragment key={user.id}>
<span>{user.name}</span>
<span>{user.occupation}</span>
</Fragment>
))}
</div>
);
}
4. 优先展开 props 而不是逐个访问它们
❌ 不好: 以下代码更难阅读(尤其是在代码规模变大时)。
// 我们在代码中到处使用 `props…`。
function TodoList(props) {
return (
<div>
{props.todos.map((todo) => (
<div key={todo}>
<button
onClick={() => props.onSelectTodo(todo)}
style={{
backgroundColor: todo === props.selectedTodo ? "gold" : undefined,
}}
>
<span>{todo}</span>
</button>
</div>
))}
</div>
);
}
✅ 好: 以下代码更简洁。
function TodoList({ todos, selectedTodo, onSelectTodo }) {
return (
<div>
{todos.map((todo) => (
<div key={todo}>
<button
onClick={() => onSelectTodo(todo)}
style={{
backgroundColor: todo === selectedTodo ? "gold" : undefined,
}}
>
<span>{todo}</span>
</button>
</div>
))}
</div>
);
}
5. 设置 props 的默认值时,在解构它们时进行设置
❌ 不好: 你可能需要在多个地方定义默认值,并引入新的变量。
function Button({ onClick, text, small, colorScheme }) {
let scheme = colorScheme || "light";
let isSmall = small || false;
return (
<button
onClick={onClick}
style={{
color: scheme === "dark" ? "white" : "black",
fontSize: isSmall ? "12px" : "16px",
}}
>
{text ?? "Click here"}
</button>
);
}
✅ 好: 你可以在顶部的一个地方设置所有默认值。这使得其他人更容易找到它们。
function Button({
onClick,
text = "Click here",
small = false,
colorScheme = "light",
}) {
return (
<button
onClick={onClick}
style={{
color: colorScheme === "dark" ? "white" : "black",
fontSize: small ? "12px" : "16px",
}}
>
{text}
</button>
);
}
6. 传递 string 类型的 props 时去掉花括号。
// ❌ 不好:不需要花括号
<Button text={"Click me"} colorScheme={"dark"} />
// ✅ 好
<Button text="Click me" colorScheme="dark" />
7. 在使用 value && <Component {...props}/> 之前,确保 value 是布尔值,以防止在屏幕上显示意外的值。
❌ 不好: 当列表为空时,0 会显示在屏幕上。
export function ListWrapper({ items, selectedItem, setSelectedItem }) {
return (
<div className="list">
{items.length && ( // 列表为空时为 `0`
<List
items={items}
onSelectItem={setSelectedItem}
selectedItem={selectedItem}
/>
)}
</div>
);
}
✅ 好: 当没有项目时,屏幕上不会显示任何内容。
export function ListWrapper({ items, selectedItem, setSelectedItem }) {
return (
<div className="list">
{items.length > 0 && (
<List
items={items}
onSelectItem={setSelectedItem}
selectedItem={selectedItem}
/>
)}
</div>
);
}
8. 使用函数(内联或非内联)避免用中间变量污染作用域
❌ 不好: 变量 gradeSum 和 gradeCount 污染了组件的作用域。
function Grade({ grades }) {
if (grades.length === 0) {
return <>暂无成绩。</>;
}
let gradeSum = 0;
let gradeCount = 0;
grades.forEach((grade) => {
gradeCount++;
gradeSum += grade;
});
const averageGrade = gradeSum / gradeCount;
return <>平均成绩: {averageGrade}</>;
}
✅ 好: 变量 gradeSum 和 gradeCount 作用域在 computeAverageGrade 函数内。
function Grade({ grades }) {
if (grades.length === 0) {
return <>暂无成绩。</>;
}
const computeAverageGrade = () => {
let gradeSum = 0;
let gradeCount = 0;
grades.forEach((grade) => {
gradeCount++;
gradeSum += grade;
});
return gradeSum / gradeCount;
};
return <>平均成绩: {computeAverageGrade()}</>;
}
💡 注意:你也可以在组件外部定义
computeAverageGrade函数,然后在组件内部调用它。
9. 使用柯里化函数重用逻辑(并正确记忆回调函数)
❌ 不好: 更新字段的逻辑非常重复。
function Form() {
const [{ name, email }, setFormState] = useState({
name: "",
email: "",
});
return (
<>
<h1>班级注册表单</h1>
<form>
<label>
姓名:
<input
type="text"
value={name}
onChange={(evt) =>
setFormState((formState) => ({
...formState,
name: evt.target.value,
}))
}
/>
</label>
<label>
邮箱:
<input
type="email"
value={email}
onChange={(evt) =>
setFormState((formState) => ({
...formState,
email: evt.target.value,
}))
}
/>
</label>
</form>
</>
);
}
✅ 好: 引入 createFormValueChangeHandler 函数,为每个字段返回正确的处理程序。
注意:如果你启用了 ESLint 规则 jsx-no-bind,这个技巧特别有用。你只需将柯里化函数包裹在
useCallback中,就大功告成了!
function Form() {
const [{ name, email }, setFormState] = useState({
name: "",
email: "",
});
const createFormValueChangeHandler = (field) => {
return (event) => {
setFormState((formState) => ({
...formState,
[field]: event.target.value,
}));
};
};
return (
<>
<h1>班级注册表单</h1>
<form>
<label>
姓名:
<input
type="text"
value={name}
onChange={createFormValueChangeHandler("name")}
/>
</label>
<label>
邮箱:
<input
type="email"
value={email}
onChange={createFormValueChangeHandler("email")}
/>
</label>
</form>
</>
);
}
10. 将不依赖组件 props/state 的数据移到组件外部,使代码更简洁(更高效)
❌ 不好: OPTIONS 和 renderOption 不需要放在组件内部,因为它们不依赖任何 props 或 state。
此外,将它们放在组件内部意味着每次组件渲染时都会得到新的对象引用。如果我们将 renderOption 传递给一个用 memo 包裹的子组件,会破坏记忆化。
function CoursesSelector() {
const OPTIONS = ["数学", "文学", "历史"];
const renderOption = (option: string) => {
return <option>{option}</option>;
};
return (
<select>
{OPTIONS.map((opt) => (
<Fragment key={opt}>{renderOption(opt)}</Fragment>
))}
</select>
);
}
✅ 好: 将它们移到组件外部,使组件保持简洁,引用保持稳定。
const OPTIONS = ["数学", "文学", "历史"];
const renderOption = (option: string) => {
return <option>{option}</option>;
};
function CoursesSelector() {
return (
<select>
{OPTIONS.map((opt) => (
<Fragment key={opt}>{renderOption(opt)}</Fragment>
))}
</select>
);
}
💡 注意:在这个例子中,你可以通过内联
option元素来进一步简化代码。
const OPTIONS = ["数学", "文学", "历史"];
function CoursesSelector() {
return (
<select>
{OPTIONS.map((opt) => (
<option key={opt}>{opt}</option>
))}
</select>
);
}
11. 存储列表中选中的项时,存储项的 ID 而不是整个项
❌ 不好: 如果选中了一个项,但随后该项发生了变化(即我们为同一个 ID 得到了一个全新的对象引用),或者该项不再存在于列表中,selectedItem 要么会保留一个过时的值,要么会变得不正确。
function ListWrapper({ items }) {
// 我们引用了整个项
const [selectedItem, setSelectedItem] = useState<Item | undefined>();
return (
<>
{selectedItem != null && <div>{selectedItem.name}</div>}
<List
items={items}
selectedItem={selectedItem}
onSelectItem={setSelectedItem}
/>
</>
);
}
✅ 好: 我们通过项的 ID(应该是稳定的)来存储选中的项。这确保了即使该项从列表中移除或其某个属性发生了变化,UI 也应该是正确的。
function ListWrapper({ items }) {
const [selectedItemId, setSelectedItemId] = useState<number | undefined>();
// 我们从列表中推导选中的项
const selectedItem = items.find((item) => item.id === selectedItemId);
return (
<>
{selectedItem != null && <div>{selectedItem.name}</div>}
<List
items={items}
selectedItem={selectedItem}
onSelectItem={(item) => setSelectedItemId(item?.id)}
/>
</>
);
}
14. 将所有状态和上下文分组到组件顶部
当所有状态和上下文都位于组件顶部时,就很容易发现哪些因素会触发组件重新渲染。
❌ 不好: 状态和上下文分散在各处,难以跟踪。
function App() {
const [email, setEmail] = useState("");
const onEmailChange = (event) => {
setEmail(event.target.value);
};
const [password, setPassword] = useState("");
const onPasswordChange = (event) => {
setPassword(event.target.value);
};
const theme = useContext(ThemeContext);
return (
<div className={`App ${theme}`}>
<h1>Welcome</h1>
<p>
Email: <input type="email" value={email} onChange={onEmailChange} />
</p>
<p>
Password:{" "}
<input type="password" value={password} onChange={onPasswordChange} />
</p>
</div>
);
}
✅ 好: 所有状态和上下文都分组在顶部,便于查看。
function App() {
const theme = useContext(ThemeContext);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const onEmailChange = (event) => {
setEmail(event.target.value);
};
const onPasswordChange = (event) => {
setPassword(event.target.value);
};
return (
<div className={`App ${theme}`}>
<h1>欢迎</h1>
<p>
邮箱: <input type="email" value={email} onChange={onEmailChange} />
</p>
<p>
密码:
<input type="password" value={password} onChange={onPasswordChange} />
</p>
</div>
);
}