You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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封装纯逻辑函数,同时对组件内状态相关函数做针对性优化:

  1. 抽离纯逻辑到静态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');
  }
}
  1. 用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 15:07:03