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

如何优化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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 21:34:56