2024 年如何更好地构建 React 应用程序?
今天是坚持日更的第130天,如果本文对您有帮助,在文章末尾点击分享、点赞、在看支持我
好的 React 应用程序,不仅仅体现在代码的易读性和可维护性上,还体现在研发效率和性能上。本文将讨论 React 开发人员构建应用程序应该注意的几个常见问题。通过理解和避免这些问题,开发人员可以更高效的开发 React 应用程序。
1.业务逻辑与组件分离
在组件中直接混合业务逻辑(数据获取、转换等)会降低代码的可重用性,增加测试和维护的难度。导致组件紧密耦合,难以独立测试业务逻辑。
因此,在日常开发中应该将业务逻辑与组件分离,创建单独的函数、Hook、或服务来处理业务逻辑,并从组件中调用它们。如下示例:
// Data fetching service
function fetchUserData() {
// ...
}
// Component
function UserDetails() {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUserData().then(setUser);
}, []);
// Render user data
}
// 更推荐使用 hook
2.不要导入多余的内容
导入未使用的组件或模块会增加捆绑包的大小,并对性能产生负面影响。较大的捆绑包尺寸会导致加载时间变慢,并可能带来较差的用户体验。
建议只从模块中导入所需的特定组件或功能,并使用代码分割按需加载组件。
// Import only specific components
import { Button } from './components';
// Code splitting
import React, { lazy, Suspense } from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
3.优化属性传递
属性传递指的是通过多个级别的组件传递属性,通常是不必要的,以达到嵌套组件的目的,在多层组件嵌套场景中比较常见。它会使代码变得难以阅读,更难以维护,并容易出错。
建议使用 React Context 或像 Redux 这样的状态管理库,在组件之间更有效地共享数据,而无需进行属性传递。
// Context
const UserContext = React.createContext();
// Provider
<UserContext.Provider value={{ user }}>
{/* Child components can access user data without props */}
</UserContext.Provider>
// Consumer
<UserContext.Consumer>
{(user) => {
// Use user data here
}}
</UserContext.Consumer>
4.减少不必要的重复渲染
在组件的渲染功能中执行昂贵的计算或操作会导致性能问题,尤其是在频繁重新渲染的情况下。每次渲染都进行重新计算可能会导致运行缓慢并产生潜在的性能瓶颈。
建议使用记忆功能(比如:React.memo、useMemo 或 useCallback)等技术来缓存值,防止不必要的重新呈现。
// Memoized component
const MyComponent = React.memo(function MyComponent(props) {
// ...
});
// Memoized value
const memoizedValue = useMemo(() => computeExpensiveValue(props), [props]);
5.重视代码的组织及可读性
编写杂乱无章的代码会给理解、维护和协作带来困难。杂乱无章的代码难以浏览、调试和重构,从而降低了开发效率。
建议遵循一致的编码风格(比如增加代码规范及约束 ESLint/Stylelint/lslint/Prettier),使用描述性的变量名,正确缩进代码,并将复杂的功能分解为更小的、可重复使用的单元。
// Readable and structured code
function MyComponent() {
const [count, setCount] = useState(0);
function incrementCount() {
setCount(count + 1);
}
return (
<div>
<button onClick={incrementCount}>Increment ({count})</button>
</div>
);
}
// Spaghetti code (avoid!)
function MyComponent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
({count}) + 1
</button>
</div>
);
}
6.正确使用 useEffect Hook
useEffect 钩子是在 React 组件中处理副作用的强大工具,但正确使用它以避免意外后果至关重要。不正确使用 useEffect 可能会导致无限循环、内存泄漏或意外行为。
建议了解 useEffect 的第二个参数,并使用它来控制何时运行副作用。要避免内存泄漏,请注意清理回调函数(尤其在有绑定事件监听的情况下)。
useEffect(() => {
// Side effect that runs only when count changes
}, [count]);
7.合理使用状态
在组件中不必要地管理状态会导致性能问题和不必要的重复渲染。频繁的状态更新会触发重新渲染,即使更改与渲染的UI无关。
建议仔细考虑组件是否需要状态,并优化状态更新以尽量减少重新渲染。使用 useReducer
进行复杂的状态管理。
// Optimized with memoization
const MyComponent = React.memo(() => {
const [text, setText] = useState('');
const filteredText = useMemo(() => text.toUpperCase(), [text]);
return <p>{filteredText}</p>;
});
// Unoptimized (avoids memoization)
const MyComponent = () => {
const [text, setText] = useState('');
return <p>{text.toUpperCase()}</p>;
};
8.做好错误处理和日志记录
如果不能有效地处理错误,就会导致令人沮丧的用户体验和难以调试的问题。未处理的错误可能会导致应用程序崩溃,而不充分的日志记录会使诊断和解决问题变得困难。
建议使用 try-catch 块来优雅地处理错误,并使用 react-error-boundary 等库来处理组件级错误。使用 winston 或 debug 等日志库来记录结构化日志,方便调试。
try {
// Perform operation
} catch (error) {
console.error(error);
// Handle error gracefully
}
// Error boundary example
<ErrorBoundary>
{/* Protected component */}
</ErrorBoundary>
9.用 useReducer 替换多个 useState
当一个组件中的 useState 过多时,会出现代码阅读困难和可维护性差等问题。
建议使用 useReducer 替换多个 useState,增加代码的可维护性。
import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
return (
<>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>decrement</button>
</>
);
}
// convert to
import React, { useReducer } from 'react';
const initialState = { count: 0 };
const INCREMENT = 'increment';
const DECREMENT = 'decrement';
const reducer = (state, action) => {
switch (action.type) {
case INCREMENT:
return { count: state.count + 1 };
case DECREMENT:
return { count: state.count - 1 };
default:
throw new Error();
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const increment = () => {
dispatch({ type: INCREMENT });
};
const decrement = () => {
dispatch({ type: DECREMENT });
};
return (
<>
<p>Count: {state.count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>decrement</button>
</>
);
}
10.避免造轮子
花时间重写现有的库或组件可能是低效和不必要的。复制现有功能会浪费时间和精力,可能导致错误和不一致性。
建议利用现有的、维护良好的库和组件来处理标准功能,如路由、状态管理、表单处理等。只有在真正必要的情况下才编写自定义组件。
结论
总之,如果想精通 React,需要学习其高级特性,并理解和遵守最佳实践,以确保编写出高效和可维护的代码。在日常开发中注意本文提到的常见问题,可以构建高质量的应用程序,大大提升我们的开发效率。
大家都在看