面试题React

229 阅读28分钟

1、什么是UI = f(state)

  • 是react设计的核心原则,它将UI界面看成状态的函数映射,通过声明式的方式简化了UI开发,不需要像jQuery那样命令式的去操dom
  • 状态是因,UI是果
  • 状态变化,UI自动同步,无需手动操作dom
  • 保证相同的状态输入,一定能够得到相同的UI界面
  • 这种模式让代码更可预测,更易于维护

2、虚拟dom到底是什么

  • 本质是一个js对象,是一个用来描述真实dom的轻量级副本
  • 操作真实dom的性能开销往往是比较大的,react采用虚拟dom,在状态更新之前,会先通过diff算法比较新旧虚拟dom,找到最小差异,然后在统一的一次性更新为真实dom,尽可能的减少操作真实dom,来提高应用的性能
  • 当然也不是什么时候,使用虚拟dom都能提高性能,如果只是简单的一次性操作dom,使用虚拟dom反而会增加性能开销,因为虚拟dom本身会创建对象,占用内存,diff算法的比较过程也需要一定的时间,但是在大型项目中,虚拟dom对性能的提升效果是显著的
  • 所以虚拟dom一个“以空间换时间“的设计,用js的内存开销,换取真实dom的操作性能优化

3、vite相比于webpack快在哪

  • webpack的痛点:

    • 开发时:

      • 需要先递归解析所有模块依赖,构建出完整的依赖树,然后将所有模块打包成一个或者多个bundle.js才能启动开发服务器。
      • 对于大型项目,这个过程往往比较耗时,几分钟甚至十几分钟,因为即使只是改一个文件,webpack也需要重新处理整个依赖链,效率比较低
    • 依赖处理:

      • webpack使用的是自己js解析器,速度会比较慢
    • 热更新:

      • webpack需要重新构建被修改的模块,以及依赖的模块组,甚至可能触发整个bundle重新生成,对于复杂项目,这个过程往往需要几百毫秒到几十秒,且容易出现“更新失效”,需要手动刷新页面
    • 生产环境:采用自研打包引擎

  • vite解决方案:

    • 开发时:

      • vite利用浏览器原生支持的 ES module,完全跳过了打包步骤
      • 按需编译,只处理当前用到的模块,启动速度非常快(毫秒级的)
    • 依赖处理:

      • vite使用的是Go语言编写的esbuild,速度比js的解析器快10-100倍
    • 热更新:

      • vite的热更新基于原生的ESM精确更新,只会重新编译被修改的模块,通过ESM的依赖图,vite能够精确通知浏览器只更新这个模块及其直接依赖
    • 生产环境:采用基于Rollup二次封装的打包器,专注于按需打包

  • 适用场景和优缺点:

    • webpack:

      • 更适用于大型项目,需要精细化打包配置项目
      • webpack的生态强大
      • 兼容性更强
    • vite:

      • 适用于中小型项目,不需要复杂打包的场景
      • vite是新生的技术,生态相对没有那么强大
      • 兼容性也没有那么好,对于旧版本的IE浏览器,需要配置额外插件@vitejs/plugin-legacy(通过 Babel 转译 + 自动注入 polyfill),会增加一定构建体积和构建耗时

4、函数组件和类组件的本质区别

  • 函数组件:

    • 编程范式:函数式编程,无实例,纯函数
    • 状态管理:依赖hook函数suseState
    • 生命周期:没有自己的声明周期,可以用useEffect钩子来模拟
    • this处理:没有this,避免了复杂的this绑定问题
    • 性能开销:轻量,无实例,优化简单(React.memo
    • 逻辑复用:自定义hooks,简洁,无嵌套
  • 类组件:

    • 编程范式:面向对象编程,有实例
    • 状态管理:依赖this.state,存储在实例上
    • 生命周期:有自己的生命周期方法
    • this处理:有this,需要手动绑定this
    • 性能开销:有实例开销,优化复杂(shouldComponentUpdate
    • 逻辑复用:高阶组件,可能会有产生嵌套地狱

5、react为什么强调props的不可变性

  • 在React中,props的不可变性是React的核心设计原则之一,主要原因如下

    • 为了保证组件的可预测性:

      • 数据都是从父组件通过props传下来的,统一在父组件中维护,子组件只能读取,避免子组件和父组件同时修改数据,导致状态不一致,调试时难以追溯变化来源。
      • 函数组件本质是纯函数(输入相同则输出一定相同),如果props可能被修改,那相同的输入就有可能得到不同的输出,组件会变得不可预测
    • 方便配合React.memoshouldComponentUpdate来优化渲染性能:

      • 在React中,父组件重新渲染时,会创建全新的props,React.memoshouldComponentUpdate默认通过浅比较(比较引用是否相同)来判断props是否变化,从而前端子组件是否需要重新渲染
      • 如果props的属性可以被改变,修改props的某个属性时,应用地址不会变化,使用React.memoshouldComponentUpdate来优化渲染就失去了意义。
    • 保证单向数据流的一致性:

      • 数据只会从父组件传到子组件,如果子组件需要修改数据,也是通过父组件提供的修改方法,这样便于追踪变化,使得调试变得简单

6、useState的函数式更新有什么好处

  • 确保使用最新的状态值

    import { useState } from 'react';
    ​
    export default function About() {
      const [count, setCount] = useState(0);
    ​
      const handleClick = ()=>{
        //页面显示1,而非2  
        setCount(count + 1)
        setCount(count + 1)
          
        //页面显示预期结果2
        setCount(count=>count + 1)
        setCount(count=>count + 1)
      }
      return (
        <>
        <h1>the count value is {count}</h1>
        <h1 onClick={handleClick}>Hello About</h1>
        </>
      );
    }
    
  • 避免闭包陷阱

    import { useState } from 'react';
    ​
    export default function About() {
      const [count, setCount] = useState(0);
    ​
      const handleClick = ()=>{
          //页面一直显示1
          setInterval(()=>{
              setCount(count + 1)
          },1000)      
          
          //页面显示1,2,3,4...
          setInterval(()=>{
          setCount(count=> count + 1)
        },1000)
      }
      return (
        <>
        <h1>the count value is {count}</h1>
        <h1 onClick={handleClick}>Hello About</h1>
        </>
      );
    }
    
  • 简化复杂状态依赖的更新逻辑

    function TodoList() {
      const [todos, setTodos] = useState([]);
    ​
      // 函数式更新:基于前一次的 todos 数组添加新项
      const addTodo = (text) => {
        setTodos(prevTodos => [...prevTodos, { id: Date.now(), text }]);
      };
    ​
      // 函数式更新:基于前一次的 todos 数组删除项
      const deleteTodo = (id) => {
        setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
      };
    ​
      return <div>{/* ... */}</div>;
    }
    

7、如何优化useContext

useContext是React中用于跨组件层级共享数据的强大工具,但是如果使用不当,可能会造成组件的不必要重渲染,影响性能。

  • 核心问题:为什么useContext可能导致性能问题

    • Context.Providervalue变化时,所有使用useContext(context)的组件都会强制重新渲染,无论他们是否依赖value中变化的部分。
  • 优化方案:

    • 按功能拆分context,尽可能的精细化context的自责,减少单个context的影响范围

    • 缓存value

      • 问题:每次 Provider 组件重渲染,value 都会被重新创建(引用变化),即使里面的状态没有实际变化,消费组件也会因为 “引用变化” 而触发重渲染。

      • 解决:使用useMemo缓存value,避免无效引用变化,阻止不必要的渲染

      • 示例:

        function UserProvider({ children }) {
          const [user, setUser] = useState(null);
          // 只有 user 或 setUser 变化时,value 才会重新创建(引用变化)
          const value = useMemo(() => ({ user, setUser }), [user, setUser]); 
        ​
          return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
        }
        
    • 使用React.memo:

      • 问题:即使context拆得再好,消费组件还是可能会因为父组件的重新渲染而触发重新渲染

      • 解决:可以使用React.memo包裹消费组件,避免因为父组件重新渲染而自身不必要的重新渲染

      • 示例:

        // 只有当依赖的 Context 变化,或接收的 props 变化时,才会重渲染
        const UserInfo = React.memo(() => {
          const { user } = useContext(UserContext);
          return <div>{user?.name}</div>;
        });
        

8、useState和useReducer

  • useState

    • 可以用于任何类型的状态管理,但是通常用于简单类型的状态管理

    • 语法const [state, setState] = useState(initialState)

      • initialState可以是任何类型

        • 如果是函数类型,该函数只会在组件首次渲染时执行一次,避免每次重复计算,函数返回值为状态的初始值
  • useReducer

    • 通常用于管理逻辑复杂状态

    • 语法const [state, dispatch] = useReducer(reducer, initialState, init?)

      • reducer(state,action)=>newState,一个纯函数,接收(state,action)两个参数,返回新状态,action描述更新的对象,通常包含type、payload
      • initialState:状态的初始值,与useState类似
      • init可选:惰性初始化函数,只在组件首次渲染时执行一次(通过计算得到状态的初始值,与useState的函数式类似)

9、useImperativeHandle和useFowardRef

import { forwardRef, useImperativeHandle, useRef } from 'react';
​
// 子组件:结合 forwardRef 和 useImperativeHandle
const CustomInput = forwardRef((props, ref) => {
  // 子组件内部的 DOM ref
  const inputRef = useRef(null);
​
  // 自定义子组件通过 ref 暴露给父组件的方法
  useImperativeHandle(ref, () => ({
    // 暴露 focus 方法
    focus: () => {
      inputRef.current.focus();
    },
    // 暴露 clear 方法
    clear: () => {
      inputRef.current.value = '';
    },
    // 可以暴露属性
    getValue: () => inputRef.current.value
  }), []); // 依赖为空,只初始化一次
​
  return <input {...props} ref={inputRef} />;
});
​
// 父组件:通过 ref 访问子组件暴露的自定义方法
function Parent() {
  const customInputRef = useRef(null);
​
  return (
    <div>
      <CustomInput ref={customInputRef} placeholder="请输入" />
      <button onClick={() => customInputRef.current.focus()}>聚焦</button>
      <button onClick={() => customInputRef.current.clear()}>清空</button>
      <button onClick={() => console.log(customInputRef.current.getValue())}>
        获取值
      </button>
    </div>
  );
}
  • useFowardRef:使用useFowardRef包裹子组件,子组件可以接收两个参数,props和ref,ref是父组件使用useRef生成的ref容器,子组件接收到之后,可以直接绑定在dom上,这样父组件就可以借助传下来的ref,访问到子组件内部的真实dom
  • useImperativeHandle:有的时候,我们不希望自己定义的组件,在被外部调用时随意调用组件内的dom方法,只给外部组件调用我们允许的方法,这个时候就可以使用useImperativeHandle来暴露一些我们允许的方法

10、react的并发特性

简单来说React 的并发特性是 “可中断的渲染” 和 “优先级调度” 的底层能力,是React 18引入的核心底层能力,它允许React中断、暂停、恢复甚至放弃渲染工作,从而优化应用在复杂场景下的响应性;其核心目的是:在处理耗时任务(如大量计算、复杂UI渲染)时,不阻塞用户交互(如点击、输入、滚动等),让应用保持流畅

  • 可中断的渲染:React的渲染过程分为Render 阶段Commit 阶段,并发特性让Render 阶段变得可中断,当高优先级的任务(如用户输入)到来时,先处理高优先级的任务Render,完成后再决定是恢复处理低优先级的Render,还是直接丢弃(如果已过时)

    • Render 阶段:计算组件树的新状态,生成新的虚拟dom(不操作真实dom)
    • Commit 阶段:将虚拟dom的变化应用到真实dom(此阶段不可中断,因为涉及dom操作,必须原子化执行)
  • 优先级调度:React 会为不同类型的任务分配优先级,确保高优先级任务(如用户交互)优先执行

11、开发者如何利用React的并发特性:useTransition和useDeferredValue

  • useTransition用来标记低优先级更新

    import { useTransition, useState } from 'react';
    ​
    function SearchBox() {
      const [input, setInput] = useState('');
      const [list, setList] = useState([]);
      // isPending 表示过渡任务是否在进行中
      const [isPending, startTransition] = useTransition();
    ​
      const handleInput = (e) => {
        const value = e.target.value;
        // 1. 高优先级:立即更新输入框(不被阻塞)
        setInput(value);
        // 2. 低优先级:标记为过渡任务,可被中断
        startTransition(() => {
          // 模拟耗时的列表筛选(如从大量数据中过滤)
          const filteredList = heavyFilter(value); 
          setList(filteredList);
        });
      };
    ​
      return (
        <div>
          <input value={input} onChange={handleInput} />
          {isPending ? <p>加载中...</p> : (
            <ul>{list.map(item => <li key={item}>{item}</li>)}</ul>
          )}
        </div>
      );
    }
    

    语法const [isPending, startTransition] = useTransition();

    isPending:过渡任务是否在进行中

    startTransition(callBack):标记需要过渡的任务,callBack的执行可以被中断

  • useDeferredValue为值创建延迟版本(类似防抖)

    import { useDeferredValue, useState } from 'react';
    ​
    function SearchBox() {
      const [input, setInput] = useState('');
      // 为 input 创建延迟版本,低优先级更新
      const deferredInput = useDeferredValue(input);
    ​
      return (
        <div>
          <input value={input} onChange={(e) => setInput(e.target.value)} />
          {/* 依赖延迟值,低优先级渲染 */}
          <SearchResults query={deferredInput} />
        </div>
      );
    }
    

12、如何理解react组件的单一职责

  • 核心思想:一个组件只负责一件事,当它需要变化时,只有一个原因;好的组件就像乐高积木,每一个组件都可以独立存在,又可以互相组合实现逻辑复用。

  • 为什么需要单一职责:

    • 提高组件复用性
    • 降低维护成本
    • 减少副作用和冲突
    • 便于测试

13、容器组件和展示组件已经过时了吗

在React社区中,“容器组件”和“展示组件”的划分是早起非常流行的一种设计模式,但随着React的发展,尤其是hooks的出现,这种模式的严格边界正在被弱化,但它的核心思想(关注点分离) 并没有过时,而是以更灵活的形式延续。

为什么要区分容器组件展示组件?核心目的是为了分离数据逻辑UI渲染

  • 容器组件:专注于数据逻辑处理,并且将数据传递给展示组件,不用关注UI展示的细节
  • 展示组件:专注UI的展示细节处理,不用关心数据的处理逻辑,无副作用可复性高

hooks带来的改变:随着hooks的出现,函数组件也能拥有自己的状态和生命周期,我们可以很轻松的通过自定义一个hooks实现逻辑的复用,不用再使用容器组件来嵌套实现逻辑的复用,不用再严格的区分UI展示状态处理组件,在一个函数组件中,返回UI的实现,通过hooks实现逻辑的复用。

14、react中的组合与继承:为什么推荐组合?

在React中,组合继承是两种实现逻辑复用的方式

  • 组合:核心是将组件视为零件,通过组合搭建负责功能,类似乐高积木(组合与 React 的 “声明式编程” 理念一致,并且具有灵活性、低耦合、清晰性等特点
  • 继承:核心是子类继承父类的属性和方法,通过类的继承关系,复用父组件的逻辑或者UI(容易造成组件组件耦合过重,难以维护

15、react中的错误边界

在React中,错误边界(Error Boundary)是一种特殊组件,有利于捕获处理其子组件树中抛出的 js 错误,避免错误导致整个应用崩溃,同事提供有好的错误提示页面。

在React 16 之前,组件中抛出的错误可能导致整个应用崩溃,用户只能看见白屏,很不友好。

如何创建错误边界组件?

错误边界必须是类组件(目前函数组件还不支持错误边界,因为它依赖类组件的生命周期方法)需要实现以下两个生命周期方法中的至少一个:

  • static getDerivedStateFromError(error)

    • 用于更新状态,让组件在下一次渲染时显示备用 UI。
    • 接收 error 作为参数,返回一个对象(用于更新 state)。
  • componentDidCatch(error, errorInfo)

    • 用于记录错误信息(如发送到日志服务)。
    • 接收错误对象 error 和包含堆栈信息的 errorInfo

    错误边界组件实例

    class ErrorBoundary extends React.Component {
      constructor(props) {
        super(props);
        this.state = { hasError: false }; // 初始状态:无错误
      }
    ​
      // 捕获错误后更新状态,显示备用 UI
      static getDerivedStateFromError(error) {
        return { hasError: true }; // 将 hasError 设为 true
      }
    ​
      // 记录错误信息
      componentDidCatch(error, errorInfo) {
        console.error("错误边界捕获到错误:", error, errorInfo);
        // 可在此处将错误发送到日志服务,如 Sentry
      }
    ​
      render() {
        // 如果有错误,显示备用 UI
        if (this.state.hasError) {
          return this.props.fallback || <h2>发生错误,请刷新页面重试。</h2>;
        }
    ​
        // 无错误时,渲染子组件树
        return this.props.children;
      }
    }
    

    使用错误边界组件示例

    function App() {
      return (
        <div>
          <h1>我的应用</h1>
          {/* 包裹可能出错的组件 */}
          <ErrorBoundary fallback={<h2>用户列表加载失败</h2>}>
            <UserList /> {/* 可能抛出错误的组件 */}
          </ErrorBoundary>
          
          {/* 其他不受影响的组件 */}
          <Footer />
        </div>
      );
    }
    

    错误边界的限制:错误边界不能捕获以下场景的错误

    • 自身的错误(只能捕获子组件树中的错误)
    • 事件处理器中的错误(如 onClickonChange 等,React 希望开发者自行处理这些错误,如用 try/catch
    • 异步代码中的错误(如 setTimeoutfetch 回调等,需自行用 try/catch 处理)
    • 服务端渲染期间的错误

17、怎么设计一个组件库

  • 明确设计目标与边界

    • 使用场景:是内部业务组件(贴合特定产品),还是公共组件(类似antd、element)
    • 组件范围:包含哪些组件,基础组件(按钮、文本输入框),业务组件(表单、表格)
    • 技术栈:结合团队成员技术情况,以及新技术的成熟度、上手难度等,综合考虑技术选用
  • 核心设计原则

    • 单一职责:每个组件只做一件事,避免过渡封装,复用更方便

    • API 设计一致性

      • 命名规范:props命名统一,见名之意(onChange处理变化、onClick处理点击、disabled处理禁用)
      • 行为一致:相似组件把持相同交互逻辑(SelectCheckboxvalueonChange行为一致)
      • 避免冗余:避免重复功能的props(如同时提供isDisableddisabled
    • 可定制与扩展性

      • 样式定制:支持主题(颜色,字体)定制,通过css变量或者主题配置实现
      • 行为扩展:允许通过props覆盖默认行为(如renderXXX自定义渲染函数)
      • 插槽机制:通过children或者具名插槽(如header、footer)支持灵活内容插入
    • 可访问性(A11y):确保组件支持键盘导航、屏幕阅读器等辅助工具

    • 受控组件优先,兼容非受控组件

  • 扩展与维护

    • 文档与示例
    • 国际化
    • 支持按需引入组件和样式
    • 社区反馈:搜集用户issues,持续迭代组件功能和维护组件

18、原子设计

原子设计是一种 “自下而上” 的组件构建方法论,通过将 UI 分解为原子、分子、有机体、模板和页面 5 个层级,实现组件的高度复用和 UI 一致性。在 React 中,这种思想与组件化理念完美契合,尤其适合大型应用或需要长期维护的项目。

核心价值在于:用最小的成本构建一致、灵活、可扩展的 UI 系统,让开发者从重复劳动中解放出来,专注于业务逻辑而非 UI 细节。

tailwind css是一个原子化工具类

19、如何实现一个受保护的路由

封装一个路由保护组件,检查是否有权限,有权限渲染路由元素或路由出口<Outlet />,没有权限渲染指定页面(使用<Navigate />来实现路由跳转)

// ProtectedRoute.jsx - 受保护路由组件
import { Navigate, Outlet } from 'react-router-dom';
​
// 权限验证逻辑
const ProtectedRoute = ({ element, requiresAuth, roles = [] }) => {
  // 1. 获取用户登录状态和角色(实际项目中从Context/Redux获取)
  const { isAuthenticated, userRole } = JSON.parse(
    localStorage.getItem('user') || '{"isAuthenticated": false, "userRole": null}'
  );
​
  // 2. 未登录且需要权限时,重定向到登录页
  if (requiresAuth && !isAuthenticated) {
    return <Navigate to="/login" state={{ from: window.location.pathname }} replace />;
  }
​
  // 3. 已登录但角色不匹配时,重定向到无权限页
  if (requiresAuth && roles.length > 0 && !roles.includes(userRole)) {
    return <Navigate to="/unauthorized" replace />;
  }
​
  // 4. 权限通过,渲染对应组件
  return element || <Outlet />;
};
​
export default ProtectedRoute;

路由配置表

// routes.js - 路由表配置
import Home from './pages/Home';
import Login from './pages/Login';
import Dashboard from './pages/Dashboard'; // 需要登录
import AdminPanel from './pages/AdminPanel'; // 需要管理员权限
import NotFound from './pages/NotFound';
​
// 路由配置数组
const routes = [
  {
    path: '/',
    element: <Home />,
    requiresAuth: false, // 不需要登录
  },
  {
    path: '/login',
    element: <Login />,
    requiresAuth: false,
  },
  {
    path: '/dashboard',
    element: <Dashboard />,
    requiresAuth: true, // 需要登录
    roles: [], // 空数组表示所有登录用户可访问
  },
  {
    path: '/admin',
    element: <AdminPanel />,
    requiresAuth: true,
    roles: ['admin'], // 仅管理员可访问
  },
  {
    path: '*',
    element: <NotFound />,
    requiresAuth: false,
  },
];
​
export default routes;

App组件

// App.jsx - 应用入口,渲染路由表
import { useRoutes, Navigate } from 'react-router-dom';
import routes from './routes';
import ProtectedRoute from './ProtectedRoute';
import Unauthorized from './pages/Unauthorized';
​
const App = () => {
  // 转换路由配置为React Router可识别的格式
  const renderedRoutes = routes.map((route) => ({
    path: route.path,
    element: (
      <ProtectedRoute
        requiresAuth={route.requiresAuth}
        roles={route.roles}
      >
        {route.element}
      </ProtectedRoute>
    ),
  }));
​
  // 额外添加无权限页面路由
  renderedRoutes.push({
    path: '/unauthorized',
    element: <Unauthorized />,
  });
​
  // 使用useRoutes渲染路由表
  return useRoutes([
    ...renderedRoutes,
    // 根路径重定向
    { path: '/', element: <Navigate to="/home" replace /> }
  ]);
};
​
export default App;

20、redux-toolkit解决了哪些痛点

Redux Toolkit(简称 RTK)是 Redux 官方推出的工具集,旨在解决传统 Redux 开发中的一系列痛点,简化 Redux 代码编写流程,让开发者更专注于业务逻辑而非模板代码,主要解决一下痛点

  1. 消除”样板代码“冗余问题:统 Redux 开发需要手动编写action type 常量action 创建函数reducer 开关语句,代码冗长且重复

    • 传统写法:

      // 传统 Redux:大量样板代码
      // action type
      const INCREMENT = 'counter/increment';
      const DECREMENT = 'counter/decrement';
      ​
      // action 创建函数
      export const increment = (payload) => ({ type: INCREMENT, payload });
      export const decrement = (payload) => ({ type: DECREMENT, payload });
      ​
      // reducer
      const initialState = { value: 0 };
      export default function counterReducer(state = initialState, action) {
        switch (action.type) {
          case INCREMENT:
            return { ...state, value: state.value + action.payload };
          case DECREMENT:
            return { ...state, value: state.value - action.payload };
          default:
            return state;
        }
      }
      
    • RTK:

      // Redux Toolkit:简洁代码
      import { createSlice } from '@reduxjs/toolkit';
      ​
      const counterSlice = createSlice({
        name: 'counter', // 自动生成 action type 的前缀(如 "counter/increment")
        initialState: { value: 0 },
        reducers: {
          // 直接定义 reducer 逻辑,自动生成对应的 action 创建函数
          increment: (state, action) => {
            state.value += action.payload; // 内部使用 Immer 库,支持“直接修改”状态(实际是 immutable 更新)
          },
          decrement: (state, action) => {
            state.value -= action.payload;
          }
        }
      });
      ​
      // 自动生成的 action 创建函数
      export const { increment, decrement } = counterSlice.actions;
      // 自动生成的 reducer
      export default counterSlice.reducer;
      
  2. 简化 Immutable 状态更新:Redux 要求状态更新必须是不可变(immutable) 的,传统方式需要手动使用扩展运算符(...)或 Object.assign,嵌套结构时尤为繁琐易错

    • 传统写法:

      // 传统 Redux:手动 immutable 更新
      case 'user/updateAddress':
        return {
          ...state,
          user: {
            ...state.user,
            address: {
              ...state.user.address,
              city: action.payload.city // 嵌套层级越深,代码越复杂
            }
          }
        };
      
    • RTK写法:

      // 传统 Redux:手动 immutable 更新
      case 'user/updateAddress':
        return {
          ...state,
          user: {
            ...state.user,
            address: {
              ...state.user.address,
              city: action.payload.city // 嵌套层级越深,代码越复杂
            }
          }
        };
      
  3. 简化 Store 配置流程

    • 传统写法:

      // 传统 Redux:复杂的 store 配置
      import { createStore, applyMiddleware, combineReducers } from 'redux';
      import thunk from 'redux-thunk';
      import { composeWithDevTools } from 'redux-devtools-extension';
      import counterReducer from './counter';
      import userReducer from './user';
      ​
      const rootReducer = combineReducers({
        counter: counterReducer,
        user: userReducer
      });
      ​
      const store = createStore(
        rootReducer,
        composeWithDevTools(applyMiddleware(thunk)) // 手动集成中间件和 devTools
      );
      
    • RTK写法:

      // Redux Toolkit:简化 store 配置
      import { configureStore } from '@reduxjs/toolkit';
      import counterReducer from './counterSlice';
      import userReducer from './userSlice';
      ​
      export const store = configureStore({
        reducer: {
          counter: counterReducer,
          user: userReducer // 自动合并 reducer,无需 combineReducers
        }
        // 自动集成 thunk、Redux DevTools,无需手动配置
      });
      
  4. 简化异步逻辑处理

    • 传统

      // 传统 Redux:手动处理异步 action
      const fetchUserStart = () => ({ type: 'user/fetchStart' });
      const fetchUserSuccess = (data) => ({ type: 'user/fetchSuccess', payload: data });
      const fetchUserFailure = (error) => ({ type: 'user/fetchFailure', payload: error });
      ​
      // 异步 thunk action
      export const fetchUser = (userId) => {
        return async (dispatch) => {
          dispatch(fetchUserStart());
          try {
            const response = await fetch(`/api/users/${userId}`);
            const data = await response.json();
            dispatch(fetchUserSuccess(data));
          } catch (error) {
            dispatch(fetchUserFailure(error.message));
          }
        };
      };
      
    • RTK

      // Redux Toolkit:简化异步逻辑
      import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
      ​
      // 1. 自动生成异步 action(包含 pending/fulfilled/rejected 状态)
      export const fetchUser = createAsyncThunk(
        'user/fetchUser', // action type 前缀
        async (userId, { rejectWithValue }) => {
          try {
            const response = await fetch(`/api/users/${userId}`);
            return await response.json();
          } catch (error) {
            return rejectWithValue(error.message); // 传递错误信息
          }
        }
      );
      ​
      // 2. 在 slice 中处理异步结果
      const userSlice = createSlice({
        name: 'user',
        initialState: { data: null, loading: false, error: null },
        extraReducers: (builder) => {
          builder
            .addCase(fetchUser.pending, (state) => {
              state.loading = true;
              state.error = null;
            })
            .addCase(fetchUser.fulfilled, (state, action) => {
              state.loading = false;
              state.data = action.payload;
            })
            .addCase(fetchUser.rejected, (state, action) => {
              state.loading = false;
              state.error = action.payload;
            });
        }
      });
      
  5. 规范 Redux 最佳实践

    • 传统:传统 Redux 没有强制的代码组织规范,开发者可能将 action、reducer 分散在多个文件中,导致项目结构混乱。
    • RTK:通过 createSlice 鼓励 “切片(Slice) ” 模式:将一个功能模块的 action、reducer 集中在一个 slice 文件中,形成独立的功能单元,使代码结构更清晰、更易维护。

    总结

    1. 消除样板代码(自动生成 action、reducer);
    2. 简化 immutable 状态更新(内置 Immer);
    3. 简化 store 配置(自动集成中间件和 devTools);
    4. 简化异步逻辑(createAsyncThunk 处理异步流程);
    5. 规范代码组织(切片模式)。

21、immer.js为什么在redux生态中如此重要

Immer.js 在 Redux 生态中占据重要地位,核心原因是它解决了 Redux 开发中最棘手的问题之一 ——不可变状态更新的复杂性。Redux 要求状态必须是不可变的(Immutable),但手动处理不可变更新不仅繁琐易错,还会显著降低开发效率。Immer 通过 “以可变的方式编写不可变代码” 的创新思路,完美适配了 Redux 的设计理念,成为 Redux Toolkit 的核心依赖。

Redux 的核心设计原则之一是 “状态是只读的,只能通过 action 和 reducer 生成新状态” 。这意味着:

  • 不能直接修改原状态(如 state.count = 1 是禁止的);
  • 必须通过复制原状态并修改副本的方式更新(如 return { ...state, count: 1 })。

Immer 如何解决不可变更新的痛点?

Immer的核心是通过“代理 proxy”机制创建”草稿状态(Draft)“,允许开发者以直接修改的方式操作草稿,最后由 Immer 自动转化为不可变的状态。

总结

Immer.js 之所以在 Redux 生态中至关重要,是因为它解决了 Redux 开发中最实际的痛点 ——不可变状态更新的复杂性。通过允许开发者以 “可变的方式编写不可变代码”,Immer 大幅简化了 Redux reducer 的编写,降低了学习门槛,同时保证了状态的可预测性和调试体验。Redux Toolkit 将 Immer 作为核心依赖后,这种优势被进一步放大,使得现代 Redux 开发变得简洁高效。可以说,Immer 是 Redux 生态从 “繁琐” 走向 “易用” 的关键推动力之一。

22、定位react性能瓶颈的常用工具

  1. React官方工具

    • React Developer Tools(浏览器扩展)

      • Components 面板:查看组件层级、props、state,识别不必要的重渲染。
      • Profiler 面板:记录组件渲染次数、耗时,标记 “昂贵渲染”(耗时超过阈值的组件)。
    • React Profiler API(代码级埋点)

      import { Profiler } from 'react';
      ​
      // 回调函数:记录性能数据
      function onRenderCallback(
        id, // 标记 Profiler 的 id
        phase, // "mount" 或 "update"
        actualDuration, // 本次渲染实际耗时
        baseDuration, // 预估不缓存时的耗时
        startTime, // 渲染开始时间
        commitTime, // 提交到 DOM 的时间
        interactions // 关联的用户交互
      ) {
        console.log(`组件 ${id} 渲染耗时: ${actualDuration}ms`);
      }
      ​
      // 包裹需要监控的组件树
      function App() {
        return (
          <Profiler id="UserList" onRender={onRenderCallback}>
            <UserList users={users} />
          </Profiler>
        );
      }
      
    • 适用场景:生产环境性能监控、特定组件的渲染耗时统计、与用户交互(如点击、输入)关联的性能分析

  2. 浏览器原生工具(F12)

    • Chrome DevTools(Performance 面板) :用于分析整个应用的运行时性能,包括 JavaScript 执行、DOM 渲染、布局(Layout)、绘制(Paint)等

      • 使用步骤:

        1. 打开 Chrome DevTools → Performance 面板。
        2. 点击 “录制” 按钮,操作应用(如滚动、点击)。
        3. 停止录制,查看性能火焰图。
      • 关注指标:

        • Long Tasks:耗时超过 50ms 的任务,会阻塞主线程,导致卡顿。
        • Rendering 部分:Layout(重排)和 Paint(重绘)的耗时,频繁的重排重绘是性能杀手。
        • React 相关:火焰图中标记为 ReactDOM.renderupdateComponent 的调用栈,可定位渲染瓶颈。
      • 优势:全面分析前端性能,不仅限于 React,能发现 JS 执行、DOM 操作等底层问题。

    • Chrome DevTools(Memory 面板) :用于检测内存泄漏(内存占用持续增长且不释放)

      • 使用步骤:

        1. 打开 Memory 面板,选择 “Allocation Sampling”。
        2. 操作应用(如反复切换组件),点击 “Take snapshot” 记录内存快照。
        3. 对比多次快照,查看是否有组件实例、事件监听等未被正确回收。
      • React 常见内存泄漏场景:

        • 未清除的定时器(setInterval)或事件监听(window.scroll)。
        • 闭包中意外保留的组件引用。
        • 第三方库(如图表、地图)未正确销毁实例。
  3. 第三方工具库

    • Lighthouse(性能评分与优化建议) :Google 推出的自动化性能分析工具,可生成综合性能报告

      • 功能:

        • 评估加载性能、交互响应性、SEO 等指标。
        • 针对 React 应用,会检测代码分割(Code Splitting)、树摇(Tree Shaking)等优化点。
        • 提供具体优化建议(如减少未使用的 JavaScript、优化图片加载)。
      • 使用方式:

        • Chrome DevTools → Lighthouse 面板。
        • 命令行工具:lighthouse https://your-app.com
      • 适用场景:整体性能评估,尤其是首屏加载性能优化。

    • why-did-you-render(检测不必要的重渲染) :一个专门用于 React 的库,能自动检测并警告不必要的组件重渲染

      // 安装:npm install why-did-you-render --save-dev
      import React from 'react';
      if (process.env.NODE_ENV === 'development') {
        const whyDidYouRender = require('why-did-you-render');
        whyDidYouRender(React, {
          trackAllPureComponents: true, // 跟踪纯组件
        });
      }
      
      • 功能:当组件因 props/state 未变化而重渲染时,在控制台输出警告,说明重渲染原因(如父组件传递了新的引用类型 props)。
      • 优势:比 React DevTools 更精准,能直接定位重渲染的触发原因,适合优化组件渲染逻辑。

23、react常用性能优化手段

  • React.memo:缓存组件渲染结果,只要props不变,组件就不会重新渲染

  • useMemo:缓存复杂计算的结果,只要依赖项不变,就不会重新计算

  • useCallback:缓存函数的引用,当函数作为子组件的props传递时,每次组件重新渲染都会重新生成函数,导致子组件接收的props变化,触发子组件不必要的重新渲染。

  • React.lasy:路由懒加载,只有在组件被访问到的时候,才会去加载解析对应模块的代码

  • SSR服务端渲染:渲染工作在服务端进行,客户端只需要展示服务端生成的静态页面,并处理水合(hydration)过程就可以了,极大的减小了首屏加载时间

  • 防抖、节流:减少不必要的渲染、或者函数执行,从而提升应用性能

  • 虚拟列表react-window:只渲染用户看得见的地方

  • 为列表配置合理的key:配合react的diff算法,提高页面的更新效率

  • react 18的并发特性API:

    • useTranstion:标记优先级的渲染任务
    • useDeferredValue:为值创建延迟版本

24、react虚拟列表怎么实现

虚拟列表(Virtual List)是处理大量数据列表渲染的高效方案,其核心原理是只渲染可视区域内的列表项,而非全部数据,从而大幅减少 DOM 节点数量和渲染开销。

实现方案:采用固定定位和绝对定位方式,在可视区域渲染真实数据,非可视区域,渲染一个空的div仅用来撑起高度,以展示出滚动条

第三方库方案:react-window

25、react中的代码分割

代码分割是 React 大型应用性能优化的关键手段,核心是利用 import() 动态导入语法,结合 lazySuspense 实现组件的按需加载。实际开发中,建议优先基于路由分割,再根据需求补充组件级分割,同时注意平衡代码块数量和加载体验。

26、SSR服务端渲染

在服务端计算和渲染生成静态页面,再发送给客户端,浏览器对js进行水合之后,绘制到屏幕上

优点:有利于SEO搜索引擎优化、减少首屏加载时间

27、什么是hydration

是服务端渲染(SSR)或静态生成(SSG)流程中的关键步骤,指的是将服务端返回的静态 HTML 转换为可交互的客户端 React 组件的过程。

工作原理

  1. 服务端输出:服务端渲染时,React 会生成两部分内容:

    • 静态 HTML 结构(用于快速展示)。
    • 用于客户端 Hydration 的 “数据标记”(如 data-reactrootdata-reactid),帮助客户端识别 DOM 节点与虚拟 DOM 的对应关系。
  2. 客户端 Hydration 流程

    • 浏览器先渲染服务端返回的静态 HTML,用户看到初步内容。
    • 客户端加载 React 运行时和应用代码。
    • React 调用 hydrateRoot(React 18+)或 hydrate(旧版)方法,传入服务端渲染的 DOM 根节点和组件树。
    • React 对比服务端生成的虚拟 DOM 与客户端实际 DOM,建立映射关系,绑定事件监听器(如 onClick),恢复组件状态(如 useState 的初始值)。
    • 完成后,页面从 “静态” 变为 “动态”,用户可以与组件交互。

28、react testing library

React Testing Library(RTL)是一个专注于以用户视角测试 React 组件的测试库,它的核心理念是:测试组件的行为而非实现细节。这种理念让测试更贴近真实用户体验,同时使测试更健壮(不易因组件内部实现变化而失效)。

29、react-hook-from

react-hook-form 是一个专注于性能和用户体验的 React 表单库,它通过利用 React Hooks 简化表单处理逻辑,同时提供高效的表单验证、状态管理和错误处理能力。与传统表单库(如 Formik)相比,它的核心优势是减少重渲染次数更简洁的 API

  1. 性能优化

    • 采用非受控组件(Uncontrolled Components)的理念,减少不必要的重渲染(通过 ref 而非 state 跟踪输入值)。
    • 只在需要时更新表单状态,避免整体表单重渲染。
  2. 简洁的 API

    • 基于 Hooks 设计,代码侵入性低,学习成本低。
    • 最少的代码即可实现复杂表单逻辑。
  3. 强大的验证

    • 内置支持 Yup、Zod 等验证库,也可自定义验证规则。
    • 实时验证、提交验证、字段级验证等多种验证方式。
  4. 良好的类型支持

    • 全面支持 TypeScript,提供类型安全的表单处理。

30、如何在react中优雅的使用css

  • 中大型项目:优先选择 CSS Modules + Sass(平衡可控性和原生特性)或 Styled Components(动态样式需求多)。
  • 快速开发 / 小型项目Tailwind CSS 效率最高,适合追求速度和一致性的场景。
  • 动态样式密集型场景CSS-in-JS 库(如 styled-components)更灵活。

31、react的动画方案有哪些

  1. 原生css过渡、动画(transition@keyframes)
  2. React Transition Group(官方推荐的过渡管理)
  3. React Spring 物理动画(第三方动画库)