如何优化React/Next.js用户编辑页面以减少不必要重渲染
React/Next.js 用户资料页过度重渲染优化方案
我用React/Next.js做了一个用户资料编辑页,用到了react-select的下拉选择器和文本输入框,状态管理用Zustand。功能没问题,但加了下拉组件后出现了过度重渲染的情况,下面是我的代码,求优化方法。
代码文件
src\app\(dashboard)\my-profile\page.tsx
"use client"; import InputTexColProfile from "@/app/(dashboard)/my-profile/components/inputTextBoxCol"; import DDSearch from "@/app/(dashboard)/my-profile/components/DDSearch"; import changeHandler from "./scripts/changeHandlerDD"; import { useEffect } from "react"; import { userStore } from "@/stores/userStore"; import { organisasiStore } from "@/stores/organisasiStore"; import { fetchListUser } from "@/stores/userStore"; import TextInputHandler from "./scripts/changeHandlerInput"; const UserProfile = () => { const { user, listUsers } = userStore((state) => state); const { fetchOrganisasi, direktorat, departemen, divisi } = organisasiStore( (state) => state ); useEffect(() => { if(!listUsers){ fetchListUser() } if(!direktorat || !departemen || !divisi){ fetchOrganisasi(); } }, []); console.log('render user profile') // if (user && direktorat && departemen && divisi && listUsers) { return ( <div className="flex-col w-full h-full max-h-[100%] justify-center"> <div className="flex justify-center p-5"> {!user?.adminValidation ? ( <p className="text-wrap text-center text-xl font-extrabold text-[#A70000] w-[40%]"> Akun anda belum tervalidasi oleh Admin Mohon buat request dan hubungi admin layum </p> ) : ( <p className="text-wrap text-center text-xl font-extrabold text-lightblue w-[40%]"> Akun anda telah tervalidasi </p> )} </div> <div className="flex gap-2 justify-evenly mx-auto h-[70%] w-[80%] bg-lightblue rounded-[20px]"> <div className="grid grid-rows-3 rounded-[20px] w-[45%] my-auto h-[80%] items-center bg-lighterblue"> <InputTexColProfile label="name" text="Nama" defaultVal={user?.name} TextInputHandler={TextInputHandler} disabled={false} /> <DDSearch label="atasan" text="Nama Atasan" isMulti={false} dataArr={listUsers} changeHandler={changeHandler} /> <InputTexColProfile label="jabatan" text="Jabatan" defaultVal={user?.jabatan} TextInputHandler={TextInputHandler} disabled={false} /> </div> <div className="grid grid-rows-3 rounded-[20px] w-[45%] my-auto h-[80%] items-center bg-lighterblue"> <DDSearch label="direktorat" text="Direktorat" isMulti={false} dataArr={direktorat} changeHandler={changeHandler} /> {/* <DDSearch label="divisi" text="Divisi" isMulti={false} dataArr={divisi} changeHandler={changeHandler} /> */} {/* <DDSearch label="departemen" text="Departemen" isMulti={false} dataArr={departemen} changeHandler={changeHandler} /> */} </div> </div> </div> ); // } else { // return <h1>Loading</h1> // } }; export default UserProfile;
src\app\(dashboard)\my-profile\components\DDSearch.tsx
import React from "react"; import LabelBox from "@/app/(dashboard)/my-profile/components/LabelBox"; import dynamic from "next/dynamic"; const Select = dynamic(() => import('react-select'), { ssr: false, }) interface Props { isMulti: boolean; dataArr: any; label: string; text: string; changeHandler: (context: string, data: any) => void } interface OptionsE { value: any; label: string; } type Options = OptionsE[]; const DDSearch = ({ label, text, isMulti, dataArr, changeHandler }: Props) => { console.log('render ' + label) let options: Options if (Array.isArray(dataArr)) { options = dataArr.map((e) => { return { value: e, label: `${e.name}`, }; }); return ( <div className="flex-col w-full justify-start ms-5 max-w-[90%]"> <LabelBox forLabel={label} text={text} /> <Select id={label} isMulti={isMulti} options={options} onChange={(e: any) => { changeHandler(label, e.value); }} name="colors" /> </div> ); } }; export default DDSearch;
src\app\(dashboard)\my-profile\scripts\changeHandlerDD.ts
import { setUser, userStore } from "@/stores/userStore"; const changeHandler = (context: string, data: any) => { const userdata = userStore.getState().user; let obj: any; switch (context) { case "atasan": obj = { ...userdata, atasan: data.name, }; setUser(obj); return; case "direktorat": obj = { ...userdata, direktorat: data.name, }; setUser(obj); return; case "divisi": obj = { ...userdata, divisi: data.name, }; setUser(obj); return; case "departemen": obj = { ...userdata, departemen: data.name, }; setUser(obj); return default: return; } }; export default changeHandler;
优化方案
1. 用React.memo缓存DDSearch组件
DDSearch会随父组件重渲染而重复创建,用React.memo让它仅在props变化时重渲染:
// 修改DDSearch.tsx的导入和导出 import React, { memo } from "react"; // ...组件逻辑 export default memo(DDSearch);
2. 用useMemo缓存options生成逻辑
每次渲染都重新执行dataArr.map生成options是不必要的,用useMemo缓存结果,仅在dataArr变化时重新生成:
import React, { memo, useMemo } from "react"; const DDSearch = ({ label, text, isMulti, dataArr, changeHandler }: Props) => { console.log('render ' + label) const options = useMemo(() => { if (!Array.isArray(dataArr)) return []; return dataArr.map((e) => ({ value: e, label: `${e.name}`, })); }, [dataArr]); if (!options.length) return null; return ( <div className="flex-col w-full justify-start ms-5 max-w-[90%]"> <LabelBox forLabel={label} text={text} /> <Select id={label} isMulti={isMulti} options={options} onChange={(e: any) => { changeHandler(label, e.value); }} name="colors" /> </div> ); }; export default memo(DDSearch);
3. 优化Zustand状态订阅范围
当前订阅了整个store的状态,只要store内任意值变化,组件都会重渲染。改成仅订阅需要的字段,配合Zustand的shallow比较避免无效更新:
// 导入shallow比较工具 import { shallow } from "zustand/shallow"; // 修改状态订阅逻辑 const { user, listUsers } = userStore( (state) => ({ user: state.user, listUsers: state.listUsers, }), shallow ); const { fetchOrganisasi, direktorat, departemen, divisi } = organisasiStore( (state) => ({ fetchOrganisasi: state.fetchOrganisasi, direktorat: state.direktorat, departemen: state.departemen, divisi: state.divisi, }), shallow );
4. 稳定onChange回调函数
在DDSearch内用useCallback包裹onChange逻辑,避免每次渲染创建新的回调引用:
import React, { memo, useMemo, useCallback } from "react"; const DDSearch = ({ label, text, isMulti, dataArr, changeHandler }: Props) => { // ...其他逻辑 const handleChange = useCallback((e: any) => { changeHandler(label, e.value); }, [label, changeHandler]); return ( <div className="flex-col w-full justify-start ms-5 max-w-[90%]"> <LabelBox forLabel={label} text={text} /> <Select id={label} isMulti={isMulti} options={options} onChange={handleChange} name="colors" /> </div> ); }; export default memo(DDSearch);
5. 恢复条件渲染避免无效渲染
取消注释loading逻辑,确保数据未加载完成时不渲染子组件,减少不必要的渲染触发:
if (!user || !direktorat || !departemen || !divisi || !listUsers) { return <h1>Loading</h1> } return ( // 原页面内容 );
6. 优化Zustand状态更新逻辑
在userStore内封装字段更新函数,避免外部直接操作state,同时减少对象重复创建:
// userStore.ts import { create } from "zustand"; export const userStore = create((set) => ({ user: null, listUsers: null, // ...其他状态和方法 updateUserField: (field: string, value: any) => { set((state) => ({ user: { ...state.user, [field]: value, }, })); }, }));
修改changeHandler:
import { userStore } from "@/stores/userStore"; const changeHandler = (context: string, data: any) => { const fieldMap: Record<string, string> = { atasan: "atasan", direktorat: "direktorat", divisi: "divisi", departemen: "departemen", }; const fieldName = fieldMap[context]; if (!fieldName) return; userStore.getState().updateUserField(fieldName, data.name); }; export default changeHandler;
内容的提问来源于stack exchange,提问作者muntun
相关产品推荐
相关产品推荐

