React内存优化:VacationCard组件函数实现方案选型咨询
问题
我正致力于尽可能提升我的React应用内存效率。我有一个VacationCard组件(TypeScript),在项目中存在大量实例:
interface CardProps { vacation: VacationData, userId : number } function VacationCard({vacation , userId}: CardProps) : JSX.Element { const [showFullDescription, setShowFullDescription] = useState(false); const { destination, imageName, description, isFollowing , vacationId , startDate , endDate} = vacation; const [isFollowed, setIsFollowed] = useState<boolean>(isFollowing === 1); // Handling follow requests: const handleFollow = async (): Promise<void> => { // If user us already following - execute unfollow if (isFollowed) { // Call service to unfollow vacation: vacationsService.unfollowVacation(vacationId , userId) .then(()=>setIsFollowed(false)) .catch(err => notifyService.error(err)); } else { // If user is not following , execute follow: vacationsService.followVacation(vacationId , userId) .then(()=>setIsFollowed(true)) .catch(err => notifyService.error(err)); } } const toggleDescription = (): void => { setShowFullDescription(!showFullDescription); }; // If i gonna leave it this way. I will have to use useMemo hook. const getDescriptionPreview = (): string => { const words = description.split(' '); if (words.length <= 50) { return description; } return `${words.slice(0, 50).join(' ')}...`; }; const getDescriptionFull = (): string => { return description; }; return ( <div className="VacationCard"> <button onClick={handleFollow}> {isFollowed ? 'Unfollow' : 'Follow'} | {vacation.followersCount} </button> <p>{destination}</p> <img src={appConfig.vacationsImagesUrl + imageName} alt={destination} /> <p>{format(new Date(startDate), 'd.M.yyyy')} - {format(new Date(endDate), 'd.M.yyyy')}</p> <p>{showFullDescription ? getDescriptionFull() : getDescriptionPreview()}</p> {description.length > 50 && ( <button onClick={toggleDescription}> {showFullDescription ? 'See Less' : 'See More'} </button> )} </div> ); } export default VacationCard;
该组件由Vacations组件调用(完整代码如下):
function Vacations(): JSX.Element { // Use navigate: const navigate = useNavigate(); // Vacations use state: const [vacations, setVacations] = useState<VacationData[]>([]); // Filters use state: const [filterOption, setFilterOption] = useState<string>("all"); // Get user id from auth store: const userId : number = authStore.getState().user.userId; // Logout function logout(): void { authService.logout(); notifyService.success("You Have been logged out"); navigate("/login"); } // Set the user's choice filter: const handleFilters = (e : React.ChangeEvent<HTMLInputElement>)=>{ setFilterOption(e.target.value); } // Filter vacation: const filteredVacations = useMemo(()=>{ switch (filterOption) { // all vacations case "all": return vacations; // Only followed vacations: case "followedOnly": return vacations.filter((v)=> v.isFollowing === 1); // Only upcoming vacations: case "upComingOnly": return vacations.filter((v)=> new Date(v.startDate) > new Date()); // Only ongoing vacations: case "onGoingOnly": return vacations.filter((v)=> new Date(v.startDate) <= new Date() && new Date(v.endDate) >= new Date()); } } , [vacations , filterOption]) // When page is first loading useEffect(()=>{ // Get user state: const authState : AuthState = authStore.getState(); // If user is not connected: if(!userId){ notifyService.error("You must be logged in"); navigate("/login"); return; } // Call service and send back user id: vacationsService.getVacations(userId) .then(vacations => { setVacations(vacations); const unsubscribe = vacationsStore.subscribe(()=>{ const duplicatedVacations = [...vacationsStore.getState().vacations]; setVacations(duplicatedVacations); }); return () => unsubscribe(); }) .catch(err => { if(err.response!.status === 401){ authService.logout(); notifyService.error("You must be logged in"); navigate('/login'); } else{ notifyService.error(err) } }); }, []); return ( <div className="Vacations"> <div> <span className="Logout"><button onClick={logout}>Logout</button></span> <label>Filters: | </label> <label> All <input type="radio" name="options" value={"all"} onChange={handleFilters} defaultChecked /> </label> <span> | </span> <label> Followed <input type="radio" name="options" value={"followedOnly"} onChange={handleFilters}/> </label> <span> | </span> <label> Up Comings <input type="radio" name="options" value={"upComingOnly"} onChange={handleFilters}/> </label> <span> | </span> <label> On Goings <input type="radio" name="options" value={"onGoingOnly"} onChange={handleFilters}/> </label> </div> <div className="Container"> {filteredVacations.map(v => <VacationCard key={v.vacationId} vacation={v} userId={userId}/>)} </div> </div> ); } export default Vacations;
现咨询:为提升内存效率,是创建CardModel以静态方式封装VacationCard的所有函数更优,还是将这些函数移至父组件Vacations并通过props传递给子组件更合适?
优化方案分析
一、创建静态CardModel封装函数的优势
- 内存占用更低:所有
VacationCard实例共享同一组静态函数,不会为每个实例创建新的函数引用,避免大量重复函数占用内存。 - 代码职责清晰:将纯逻辑函数(如
getDescriptionPreview、日期格式化)抽离到独立Model层,组件只负责渲染和状态管理,符合单一职责原则。 - 复用性更强:静态函数可以直接在其他处理假期数据的组件中复用,无需重复编写。
注意:静态函数无法依赖组件内部状态(如isFollowed、showFullDescription),因此handleFollow、toggleDescription这类依赖状态的函数仍需留在组件内。
二、将函数移至父组件传递的劣势
- 内存优化有限:父组件重渲染时,若不用
useCallback包裹函数,传递给子组件的会是新引用,可能触发子组件不必要重渲染;即使使用useCallback,也只是避免函数重复创建,且增加了props传递的额外成本。 - 组件臃肿:父组件原本负责列表管理和过滤,硬塞子组件逻辑会让其职责混乱,维护难度上升。
- 修改成本高:子组件逻辑分散到父组件,后续修改子组件功能时需同时改动父组件,降低开发效率。
三、推荐方案
优先选择创建静态CardModel封装纯逻辑函数,同时对组件内状态相关函数做针对性优化:
- 抽离纯逻辑到静态CardModel:
class CardModel { static getDescriptionPreview(description: string): string { const words = description.split(' '); if (words.length <= 50) { return description; } return `${words.slice(0, 50).join(' ')}...`; } static formatDate(dateStr: string): string { return format(new Date(dateStr), 'd.M.yyyy'); } }
- 用
useCallback包裹状态相关函数,避免重复创建:
const handleFollow = useCallback(async () => { try { if (isFollowed) { await vacationsService.unfollowVacation(vacationId, userId); setIsFollowed(false); } else { await vacationsService.followVacation(vacationId, userId); setIsFollowed(true); } } catch (err) { notifyService.error(err); } }, [isFollowed, vacationId, userId]); const toggleDescription = useCallback(() => { setShowFullDescription(prev => !prev); }, []);
这种方式既减少了大量实例的重复函数内存占用,又保持了组件职责清晰,同时优化了状态相关函数的渲染性能。
内容的提问来源于stack exchange,提问作者Lior Nachmias
相关产品推荐
相关产品推荐

