
来源 | https://segmentfault.com/a/1190000045356274
在web前端开发的世界里,React 已经成为构建现代 Web 应用最主流的框架之一。
但随着项目的不断增长,代码的复杂度也会急速攀升。如果没有良好的习惯与技巧,不仅会影响开发效率,还会让代码变得难以维护。
今天这篇文章总结了 25 个 React 最佳实践小技巧,它们既涵盖了代码结构、状态管理,也包含性能优化和可读性提升,帮助你写出更高效、更优雅、更可维护的 React 代码。
1. 使用自闭合组件
// 不好的写法<Component></Component>// 推荐写法<Component />
2. 推荐使用Fragment组件而不是 DOM 元素来分组元素
在 React 中,每个组件必须返回单个元素。不要将多个元素包装在 <div> 或 <span> 中,而是使用 <Fragment> 来保持 DOM 整洁。
不好的写法:使用 div 会使 DOM 变得杂乱,并且可能需要更多 CSS 代码。
import Header from "./header";import Content from "./content";import Footer from "./footer";const Test = () => {return (<div><Header /><Content /><Footer /></div>);};
推荐写法: <Fragment> 包装元素而不影响 DOM 结构。
import Header from "./header";import Content from "./content";import Footer from "./footer";const Test = () => {return (// 如果元素不需要添加属性,则可以使用简写形式<></><Fragment><Header /><Content /><Footer /></Fragment>);};
3. 使用 React fragment 简写 <></>(除非你需要设置一个 key 属性)
不好写法:下面的代码有点冗余。
const Test = () => {return (<Fragment><Header /><Content /><Footer /></Fragment>);};
推荐写法:
const Test = () => {return (<><Header /><Content /><Footer /></>);};
除非你需要一个 key 属性。
const Tools = ({ tools }) => {return (<Container>{tools?.map((item, index) => {<Fragment key={`${item.id}-${index}`}><span>{ item.id }</span><span>{ item.name }</span><Fragment>})}</Container>)}
4. 优先分散使用 props,而不是单独访问每个 props
不好的写法: 下面的代码更难阅读(特别是在项目较大时)。
const TodoLists = (props) => (<div className="todo-list">{props.todoList?.map((todo, index) => (<div className="todo-list-item" key={todo.uuid}><p onClick={() => props.seeDetail?.(todo)}>{todo?.uuid}:{todo.text}</p><div className="todo-list-item-btn-group"><button type="button" onClick={() => props.handleEdit?.(todo, index)}>编辑</button><buttontype="button"onClick={() => props.handleDelete?.(todo, index)}>删除</button></div></div>))}</div>);export default TodoLists;
推荐写法: 下面的代码更加简洁。
const TodoLists = ({ todoList, seeDetail, handleEdit, handleDelete }) => (<div className="todo-list">{todoList?.map((todo, index) => (<div className="todo-list-item" key={todo.uuid}><p onClick={() => seeDetail?.(todo)}>{todo?.uuid}:{todo.text}</p><div className="todo-list-item-btn-group"><button type="button" onClick={() => handleEdit?.(todo, index)}>编辑</button><button type="button" onClick={() => handleDelete?.(todo, index)}>删除</button></div></div>))}</div>);export default TodoLists;
5. 设置 props 的默认值时,在解构时进行
不好的写法: 你可能需要在多个地方定义默认值并引入新变量。
const Text = ({ size, type }) => {const Component = type || "span";const comSize = size || "mini";return <Component size={comSize} />;};
推荐写法,直接在对象解构里给出默认值。
const Text = ({ size = "mini", type: Component = "span" }) => {return <Component size={comSize} />;};
6. 传递字符串类型属性时删除花括号。
不好的写法:带花括号的写法
<button type={"button"} className={"btn"}>按钮</button>
推荐写法: 不需要花括号
<button type="button" className="btn">按钮</button>
7. 在使用 value && <Component {...props}/> 之前确保 value 值是布尔值,以防止显示意外的值。
不好的写法: 当列表的长度为 0,则有可能显示 0。
const DataList = ({ data }) => {return <Container>{data.length && <List data={data} />}</Container>;};
推荐写法: 当列表没有数据时,则不会渲染任何东西。
const DataList = ({ data }) => {return <Container>{data.length > 0 && <List data={data} />}</Container>;};
8. 使用函数(内联或非内联)避免中间变量污染你的上下文
不好的写法: 变量 totalCount 和 totalPrice 使组件的上下文变得混乱。
const GoodList = ({ goods }) => {if (goods.length === 0) {return <>暂无数据</>;}let totalCount = 0;let totalPrice = 0;goods.forEach((good) => {totalCount += good.count;totalPrice += good.price;});return (<>总数量:{totalCount};总价:{totalPrice}</>);};
推荐写法: 将变量 totalCount 和 totalPrice 控制在一个函数内。
const GoodList = ({ goods }) => {if (goods.length === 0) {return <>暂无数据</>;}// 使用函数const {totalCount,totalPrice,} = () => {let totalCount = 0,totalPrice = 0;goods.forEach((good) => {totalCount += good.count;totalPrice += good.price;});return { totalCount, totalPrice };};return (<>总数量:{totalCount};总价:{totalPrice}</>);};
个人更喜欢的写法: 封装成 hooks 来使用。
const useTotalGoods = ({ goods }) => {let totalCount = 0,totalPrice = 0;goods.forEach((good) => {totalCount += good.count;totalPrice += good.price;});return { totalCount, totalPrice };};const GoodList = ({ goods }) => {if (goods.length === 0) {return <>暂无数据</>;}const { totalCount, totalPrice } = useTotalGoods(goods);return (<>总数量:{totalCount};总价:{totalPrice}</>);};
9. 使用柯里化函数重用逻辑(并正确缓存回调函数)
不好的写法: 表单更新字段重复。
const UserLoginForm = () => {const [{ username, password }, setFormUserState] = useState({username: "",password: "",});return (<><h1>登陆</h1><form><div class="form-item"><label>用户名:</label><inputplaceholder="请输入用户名"value={username}onChange={(e) =>setFormUserState((state) => ({...state,username: e.target.value,}))}/></div><div class="form-item"><label>密码:</label><inputplaceholder="请输入密码"value={username}type="password"onChange={(e) =>setFormUserState((state) => ({...state,password: e.target.value,}))}/></div></form></>);};
推荐写法: 引入 createFormValueChangeHandler 方法,为每个字段返回正确的处理方法。
const UserLoginForm = () => {const [{ username, password }, setFormUserState] = useState({username: "",password: "",});const createFormValueChangeHandler = (field: string) => {return (e) => {setFormUserState((state) => ({...state,[field]: e.target.value,}));};};return (<><h1>登陆</h1><form><div class="form-item"><label>用户名:</label><inputplaceholder="请输入用户名"value={username}onChange={createFormValueChangeHandler("username")}/></div><div class="form-item"><label>密码:</label><inputplaceholder="请输入密码"value={username}type="password"onChange={createFormValueChangeHandler("password")}/></div></form></>);};
10. 将不依赖组件 props/state 的数据移到组件外部,以获得更干净(和更高效)的代码
不好的写法: OPTIONS 和 renderOption 不需要位于组件内部,因为它们不依赖任何 props 或状态。
此外,将它们保留在内部意味着每次组件渲染时我们都会获得新的对象引用。如果我们将 renderOption 传递给包裹在 memo 中的子组件,则会破坏缓存功能。
const ToolSelector = () => {const options = [{label: "html工具",value: "html-tool",},{label: "css工具",value: "css-tool",},{label: "js工具",value: "js-tool",},];const renderOption = ({label,value,}: {label?: string;value?: string;}) => <Option value={value}>{label}</Option>;return (<Select placeholder="请选择工具">{options.map((item, index) => (<Fragment key={`${item.value}-${index}`}>{renderOption(item)}</Fragment>))}</Select>);};
推荐写法: 将它们移出组件以保持组件干净和引用稳定。
const options = [{label: "html工具",value: "html-tool",},{label: "css工具",value: "css-tool",},{label: "js工具",value: "js-tool",},];const renderOption = ({ label, value }: { label?: string; value?: string }) => (<Option value={value}>{label}</Option>);const ToolSelector = () => {return (<Select placeholder="请选择工具">{options.map((item, index) => (<Fragment key={`${item.value}-${index}`}>{renderOption(item)}</Fragment>))}</Select>);};
笔记: 在这个示例中,你可以通过使用选项元素内联来进一步简化。
const options = [{label: "html工具",value: "html-tool",},{label: "css工具",value: "css-tool",},{label: "js工具",value: "js-tool",},];const ToolSelector = () => {return (<Select placeholder="请选择工具">{options.map((item, index) => (<Option value={item.value} key={`${item.value}-${index}`}>{item.label}</Option>))}</Select>);};
11. 存储列表组件中选定的对象时,存储对象 ID,而不是整个对象
不好的写法: 如果选择了某个对象但随后它发生了变化(即,我们收到了相同 ID 的全新对象引用),或者该对象不再存在于列表中,则 selectedItem 将保留过时的值或变得不正确。
const List = ({ data }) => {// 引用的是整个选中的是对象const [selectedItem, setSelectedItem] = useState<Item | undefined>();return (<>{selectedItem && <div>{selectedItem.value}</div>}<Listdata={data}onSelect={setSelectedItem}selectedItem={selectedItem}/></>);};
推荐写法: 我们通过 ID(应该是稳定的)存储所选列表对象。这确保即使列表对象从列表中删除或其某个属性发生变化,UI 也应该正确。
const List = ({ data }) => {const [selectedItemId, setSelectedItemId] = useState<string | number>();// 我们从列表中根据选中id查找出选定的列表对象const selectedItem = data.find((item) => item.id === selectedItemId);return (<>{selectedItemId && <div>{selectedItem.value}</div>}<Listdata={data}onSelect={setSelectedItemId}selectedItemId={selectedItemId}/></>);};
12. 如果需要多次用到 prop 里面的值,那就引入一个新的组件
不好的写法: 由于 type === null 的检查使得代码变得混乱。
const CreatForm = ({ type }) => {const formList = useMemo(() => {if (type === null) {return [];}return getFormList({ type });}, [type]);const onHandleChange = useCallback((id) => {if (type === null) {return;}// do something},[type]);if (type === null) {return null;}return (<>{formList.map(({ value, id, ...rest }, index) => (<item.componentvalue={value}onChange={onHandleChange}key={id}{...rest}/>))}</>);};
推荐写法: 我们引入了一个新组件 FormLists,它采用定义的表单项组件并且更加简洁。
const FormList = ({ type }) => {const formList = useMemo(() => getFormList({ type }), [type]);const onHandleChange = useCallback((id) => {// do something},[type]);return (<>{formList.map(({ value, id, ...rest }, index) => (<item.componentvalue={value}onChange={onHandleChange}key={id}{...rest}/>))}</>);};const CreateForm = ({ type }) => {if (type === null) {return null;}return <FormList type={type} />;};
13. 将所有状态(state)和上下文(context)分组到组件顶部
当所有状态和上下文都位于顶部时,很容易发现哪些因素会触发组件重新渲染。
不好的写法: 状态和上下文分散,难以跟踪。
const LoginForm = () => {const [username, setUsername] = useState("");const onHandleChangeUsername = (e) => {setUserName(e.target.value);};const [password, setPassword] = useState("");const onHandleChangePassword = (e) => {setPassword(e.target.value);};const theme = useContext(themeContext);return (<div class={`login-form login-form-${theme}`}><h1>login</h1><form><div class="login-form-item"><label>用户名:</label><inputvalue={username}onChange={onHandleChangeUsername}placeholder="请输入用户名"/></div><div class="login-form-item"><label>密码:</label><inputvalue={password}onChange={onHandleChangePassword}placeholder="请输入密码"type="password"/></div></form></div>);};
推荐写法: 所有状态和上下文都集中在顶部,以便于快速定位。
const LoginForm = () => {// contextconst theme = useContext(themeContext);// stateconst [password, setPassword] = useState("");const [username, setUsername] = useState("");// methodconst onHandleChangeUsername = (e) => {setUserName(e.target.value);};const onHandleChangePassword = (e) => {setPassword(e.target.value);};return (<div class={`login-form login-form-${theme}`}><h1>login</h1><form><div class="login-form-item"><label>用户名:</label><inputvalue={username}onChange={onHandleChangeUsername}placeholder="请输入用户名"/></div><div class="login-form-item"><label>密码:</label><inputvalue={password}onChange={onHandleChangePassword}placeholder="请输入密码"type="password"/></div></form></div>);};
14. 利用 children 属性来获得更清晰的代码(以及性能优势)
利用子组件 props 来获得更简洁的代码(和性能优势)。使用子组件 props 有几个好处:
-
好处 1:你可以通过将 props 直接传递给子组件而不是通过父组件路由来避免 prop 混入。 -
好处 2:你的代码更具可扩展性,因为你可以轻松修改子组件而无需更改父组件。 -
好处 3:你可以使用此技巧避免重新渲染组件(参见下面的示例)。
不好的写法: 每当 Timer 渲染时,OtherSlowComponent 都会渲染,每次当前时间更新时都会发生这种情况。
const Container = () => <Timer />;const Timer = () => {const [time, setTime] = useState(0);useEffect(() => {const intervalId = setInterval(() => setTime(new Date()), 1000);return () => {clearInterval(intervalId);};}, []);return (<><h1>当前时间:{dayjs(time).format("YYYY-MM-DD HH:mm:ss")}</h1><OtherSlowComponent /></>);};
推荐写法: Timer 呈现时,OtherSlowComponent 不会呈现。
const Container = () => (<Timer><OtherSlowComponent /></Timer>);const Timer = ({ children }) => {const [time, setTime] = useState(0);useEffect(() => {const intervalId = setInterval(() => setTime(new Date()), 1000);return () => {clearInterval(intervalId);};}, []);return (<><h1>当前时间:{dayjs(time).formate("YYYY-MM-DD HH:mm:ss")}</h1>{children}</>);};
15. 使用复合组件构建可组合代码
像搭积木一样使用复合组件,将它们拼凑在一起以创建自定义 UI。这些组件在创建库时效果极佳,可生成富有表现力且高度可扩展的代码。
以下是一个以reach.ui为示例的代码:
<Menu><MenuButton>操作吧 <span aria-hidden>▾</span></MenuButton><MenuList><MenuItem onSelect={() => alert("下载")}>下载</MenuItem><MenuItem onSelect={() => alert("复制")}>创建一个复制</MenuItem><MenuLink as="a" href="https://reach.tech/menu-button/">跳转链接</MenuLink></MenuList></Menu>
16. 使用渲染函数或组件函数 props 使你的代码更具可扩展性
假设我们想要显示各种列表,例如消息、个人资料或帖子,并且每个列表都应该可排序。
为了实现这一点,我们引入了一个 List 组件以供重复使用。我们可以通过两种方式解决这个问题:
不好的写法:选项 1。
List 处理每个项目的渲染及其排序方式。这是有问题的,因为它违反了开放封闭原则。每当添加新的项目类型时,此代码都会被修改。
List.tsx:
export interface ListItem {id: string;}// 不好的列表组件写法// 我们还需要了解这些接口type PostItem = ListItem & { title: string };type UserItem = ListItem & { name: string; date: Date };type ListNewItem =| { type: "post"; value: PostItem }| { type: "user"; value: UserItem };interface BadListProps<T extends ListNewItem> {type: T["type"];items: Array<T["value"]>;}const SortList = <T extends ListNewItem>({ type, items }: BadListProps<T>) => {const sortItems = [...items].sort((a, b) => {// 我们还需注意这里的比较逻辑,这里或者直接使用下方导出的比较函数return 0;});return (<><h2>{type === "post" ? "帖子" : "用户"}</h2><ul className="sort-list">{sortItems.map((item, index) => (<li className="sort-list-item" key={`${item.id}-${index}`}>{(() => {switch (type) {case "post":return (item as PostItem).title;case "user":return (<><span>{(item as UserItem).name}</span><span> - </span><em>加入时间: {(item as UserItem).date.toDateString()}</em></>);}})()}</li>))}</ul></>);};export function compareStrings(a: string, b: string): number {return a < b ? -1 : a == b ? 0 : 1;}
推荐写法:选项 2。
List 采用渲染函数或组件函数,仅在需要时调用它们。
List.tsx:
export interface ListItem {id: string;}interface ListProps<T extends ListItem> {items: T[]; // 列表数据header: React.ComponentType; // 头部组件itemRender: (item: T) => React.ReactNode; // 列表项itemCompare: (a: T, b: T) => number; // 列表项自定义排序函数}const SortList = <T extends ListItem>({items,header: Header,itemRender,itemCompare,}: ListProps<T>) => {const sortedItems = [...items].sort(itemCompare);return (<><Header /><ul className="sort-list">{sortedItems.map((item, index) => (<li className="sort-list-item" key={`${item.id}-${index}`}>{itemRender(item)}</li>))}</ul></>);};export default SortList;
17. 处理不同情况时,使用 value === case && <Component /> 以避免保留旧状态
不好的写法: 在如下示例中,在切换时计数器 count 不会重置。
发生这种情况的原因是,在渲染同一组件时,其状态在currentTab更改后保持不变。
tab.tsx:
const tabList = [{label: "首页",value: "tab-1",},{label: "详情页",value: "tab-2",},];export interface TabItem {label: string;value: string;}export interface TabProps {tabs: TabItem[];currentTab: string | TabItem;onTab: (v: string | TabItem) => void;labelInValue?: boolean;}const Tab: React.FC<TabProps> = ({tabs = tabList,currentTab,labelInValue,onTab,}) => {const currentTabValue = useMemo(() => (labelInValue ? (currentTab as TabItem)?.value : currentTab),[currentTab, labelInValue]);return (<div className="tab">{tabs?.map((item, index) => (<divclassName={`tab-item${currentTabValue === item.value ? " active" : ""}`}key={`${item.value}-${index}`}onClick={() => onTab?.(labelInValue ? item : item.value)}>{item.label}</div>))}</div>);};export default Tab;
Resource.tsx:
export interface ResourceProps {type: string;}const Resource: React.FC<ResourceProps> = ({ type }) => {const [count, setCount] = useState(0);const onHandleClick = () => {setCount((c) => c + 1);};return (<div className="tab-content">你当前在{type === "tab-1" ? "首页" : "详情页"},<button onClick={onHandleClick} className="btn" type="button">点击我</button>增加访问{count}次数</div>);};
推荐写法: 根据 currentTab 渲染组件或在类型改变时使用 key 强制重新渲染组件。
function App() {const [currentTab, setCurrentTab] = useState("tab-1");return (<><Tab currentTab={currentTab} onTab={(v) => setCurrentTab(v as string)} />{currentTab === "tab-1" && <Resource type="tab-1" />}{currentTab === "tab-2" && <Resource type="tab-2" />}</>);}// 使用key属性function App() {const [currentTab, setCurrentTab] = useState("tab-1");return (<><Tab currentTab={currentTab} onTab={(v) => setCurrentTab(v as string)} /><Resource type={currentTab} key={currentTab} /></>);}
18. 始终使用错误边界处理组件渲染错误
默认情况下,如果你的应用程序在渲染过程中遇到错误,整个 UI 都会崩溃。
为了防止这种情况,请使用错误边界来:
-
即使发生错误,也要保持应用程序的某些部分正常运行。 -
显示用户友好的错误消息并可选择跟踪错误。
19. 使用 crypto.randomUUID 或 Math.random 生成 key
map 调用(也就是列表渲染)中的 JSX 元素始终需要 key。
假设你的元素还没有 key。在这种情况下,你可以使用 crypto.randomUUID、Math.random 或 uuid 库生成唯一 ID。
crypto.randomUUID。
20. 确保你的列表项 id 是稳定的(即:它们在渲染中是不会发生变化的)
尽可能的让 id/key 可以稳定。
否则,React 可能会无用地重新渲染某些组件,或者触发一些功能异常,如下例所示。
不好的写法: 每次 App 组件渲染时 selectItemId 都会发生变化,因此设置 id 的值将永远不会正确。
const App = () => {const [items, setItems] = useState([]);const [selectItemId, setSelectItemId] = useState(undefined);const loadItems = () => {fetchItems().then((res) => setItems(res));};// 请求列表useEffect(() => {loadItems();}, []);// 添加列表id,这是一种很糟糕的做法const newItems = items.map((item) => ({ ...item, id: crypto.randomUUID() }));return (<Listitems={newItems}selectedItemId={selectItemId}onSelectItem={setSelectItemId}/>);};
推荐写法: 当我们获取列表项的时候添加 id。
const App = () => {const [items, setItems] = useState([]);const [selectItemId, setSelectItemId] = useState(undefined);const loadItems = () => {// 获取列表数据并通过 id 保存fetchItems().then((res) =>// 一旦获得结果,我们就会添加“id”setItems(res.map((item) => ({ ...item, id: crypto.randomUUID() }))));};// 请求列表useEffect(() => {loadItems();}, []);return (<Listitems={items}selectedItemId={selectItemId}onSelectItem={setSelectItemId}/>);};
21. 策略性地使用 key 属性来触发组件重新渲染
想要强制组件从头开始重新渲染?只需更改其 key 属性即可。
在下面的示例中,我们使用此技巧在切换到新选项卡时重置错误边界。
Resource.tsx:
export interface ResourceProps {type: string;}const Resource: React.FC<ResourceProps> = ({ type }) => {const [count, setCount] = useState(0);const onHandleClick = () => {setCount((c) => c + 1);};// 新增抛出异常的代码useEffect(() => {if (type === "tab-1") {throw new Error("该选项不可切换");}}, []);return (<div className="tab-content">你当前在{type === "tab-1" ? "首页" : "详情页"},<button onClick={onHandleClick} className="btn" type="button">点击我</button>增加访问{count}次数</div>);};
App.tsx:
import { ErrorBoundary } from "react-error-boundary";const App = () => {const [currentTab, setCurrentTab] = useState("tab-1");return (<><Tab currentTab={currentTab} onTab={(v) => setCurrentTab(v as string)} /><ErrorBoundaryfallback={<div className="error">组件渲染发生了一些错误</div>}key={currentTab}// 如果没有key属性,当currentTab值为“tab-2”时也会呈现错误><Resource type={currentTab} /></ErrorBoundary></>);};
22. 使用 ref 回调函数执行诸如监控大小变化和管理多个节点元素等任务。
你知道可以将函数传递给 ref 属性而不是 ref 对象吗?
它的工作原理如下:
-
当 DOM 节点添加到屏幕时,React 会以 DOM 节点作为参数调用该函数。 -
当 DOM 节点被移除时,React 会以 null 调用该函数。
在下面的示例中,我们使用此技巧跳过 useEffect。
不好的写法: 使用 useEffect 关注输入框焦点
const FocusInput = () => {const ref = useRef<HTMLInputElement>();useEffect(() => {ref.current?.focus();}, []);return <input ref={ref} type="text" />;};
推荐写法: 我们在输入可用时立即聚焦输入。
const FocusInput = () => {const ref = useCallback((node) => node?.focus(), []);return <input ref={ref} type="text" />;};
23、限制功能组件文件中的返回语句数量
功能组件中的多个返回语句使得很难看到组件返回的内容。
对于我们可以搜索渲染术语的类组件来说,这不是问题。
一个方便的技巧是尽可能使用不带括号的箭头函数(VSCode 有一个针对此的操作)。
不好的写法: 更难发现组件返回语句。
export interface UserInfo {id: string;name: string;age: number;}export interface UserListProps {users: UserInfo[];searchUser: string;onSelectUser: (u: UserInfo) => void;}const UserList: React.FC<UserListProps> = ({users,searchUser,onSelectUser,}) => {// 多余return语句const filterUsers = users?.filter((user) => {return user.name.includes(searchUser);});const onSelectUserHandler = (user) => {// 多余return语句return () => {onSelectUser(user);};};return (<><h2>用户列表</h2><ul>{filterUsers.map((user, index) => {return (<li key={`${user.id}-${index}`} onClick={onSelectUserHandler(user)}><p><span>用户id</span><span>{user.id}</span></p><p><span>用户名</span><span>{user.name}</span></p><p><span>用户年龄</span><span>{user.age}</span></p></li>);})}</ul></>);};
推荐写法: 组件仅有一个返回语句。
export interface UserInfo {id: string;name: string;age: number;}export interface UserListProps {users: UserInfo[];searchUser: string;onSelectUser: (u: UserInfo) => void;}const UserList: React.FC<UserListProps> = ({users,searchUser,onSelectUser,}) => {const filterUsers = users?.filter((user) => user.name.includes(searchUser));const onSelectUserHandler = (user) => () => onSelectUser(user);return (<><h2>用户列表</h2><ul>{filterUsers.map((user, index) => (<li key={`${user.id}-${index}`} onClick={onSelectUserHandler(user)}><p><span>用户id</span><span>{user.id}</span></p><p><span>用户名</span><span>{user.name}</span></p><p><span>用户年龄</span><span>{user.age}</span></p></li>))}</ul></>);};
24. 优先使用命名导出而不是默认导出
让我们比较一下这两种方法:
//默认导出export default function App() {// 组件内容}// 命名导出export function App() {// 组件内容}
我们现在就像如下这样导入组件:
// 默认导入import App from "/path/to/App";// 命名导入import { App } from "/path/to/App";
默认导出存在如下一些问题:
-
如果组件被重命名,编辑器将不会自动重命名导出。
例如,如果将 App 重命名为 Index,我们将得到以下内容:
// 默认导入名字并未更改import App from "/path/to/Index";// 命名导入名字已更改import { Index } from "/path/to/Index";
-
很难看出从具有默认导出的文件中导出了什么。
例如,在命名导入的情况下,一旦我们输入 import { } from "/path/to/file",当我将光标放在括号内时就会获得自动完成功能。
-
默认导出很难重新再导出。
例如,如果我想从 index 文件重新导出 App 组件,我必须执行以下操作:
export { default as App } from "/path/to/App";
使用命名导出的解决方案更加直接。
export { App } from "/path/to/App";
因此,建议默认使用命名导出。
25、永远不要为可以从其他 state 或 props 派生的值创建新的 state
state 越多 = 麻烦越多。
每个 state 都可能触发重新渲染,并使重置 state 变得麻烦。
因此,如果可以从 state 或 props 中派生出值,则跳过添加新的 state。
不好的做法:filteredUsers 不需要处于 state 中。
const FilterUserComponent = ({ users }) => {const [filters, setFilters] = useState([]);// 创建了新的stateconst [filteredUsers, setFilteredUsers] = useState([]);const filterUsersMethod = (filters, users) => {// 过滤逻辑方法};useEffect(() => {setFilteredUsers(filterUsersMethod(filters, users));}, [users, filters]);return (<Card><Filters filters={filters} onChangeFilters={setFilters} />{filteredUsers.length > 0 && <UserList users={filteredUsers} />}</Card>);};
推荐做法: filteredUsers 由 users 和 filters 决定。
const FilterUserComponent = ({ users }) => {const [filters, setFilters] = useState([]);const filterUsersMethod = (filters, users) => {// 过滤逻辑方法};const filteredUsers = filterUsersMethod(filters, users);return (<Card><Filters filters={filters} onChangeFilters={setFilters} />{filteredUsers.length > 0 && <UserList users={filteredUsers} />}</Card>);};
写在最后
React 的学习从来不只是“会写”,而是要“写得好”。这 25个小技巧并不是高深的黑科技,而是日常开发中积累的经验与最佳实践。
真正的高手,不是炫技,而是能用最简洁、稳定的方式解决问题。希望你能把这些技巧应用到实际项目中,不仅提升代码质量,也提升自己作为开发者的专业度。
学习更多技能
请点击下方公众号
![]()


