[20250119]-前端面试题——React篇

面试问题记录

问题 1

请简单介绍一下React18有哪些更新。

回答:

React 18 的主要更新包括:

  1. 并发渲染:React 18 引入了并发渲染(Concurrent Rendering),使得 React 能在后台进行渲染工作,避免阻塞 UI 更新,提升性能。
  2. 自动批处理:React 18 会自动批处理多次 setState 更新,减少不必要的渲染,提升性能。
  3. useId Hook:提供一个生成唯一 ID 的 Hook,帮助解决 SSR(服务器端渲染)中的 ID 不一致问题。
  4. startTransition API:允许开发者将某些更新标记为低优先级,确保高优先级的渲染不被阻塞。
  5. Suspense 支持 SSRSuspense 现在支持服务端渲染(SSR),使得异步组件加载更加平滑。

这些更新提升了 React 的性能和开发体验,特别是在处理复杂 UI 和异步渲染时。

问题 2

JSX是什么,它和JS有什么区别?

回答:

JSX 是 JavaScript 的语法扩展,用于在 React 中描述 UI 结构,看起来像 HTML,但最终会被编译为 JavaScript 代码(如 React.createElement 调用)。

JSX 和 JS 的区别

  1. 语法:JSX 类似 HTML,嵌套在 JS 中;JS 是标准 JavaScript 语法。
  2. 功能:JSX 用于定义 React 组件的 UI;JS 用于逻辑控制和操作。
  3. 处理:JSX 需编译为 JS 才能运行;JS 可直接运行。
  4. 表达式:JSX 支持在 {} 中嵌入动态 JS 表达式。

问题3

请说一下React 事件机制和JS原生事件机制的区别

回答:

React 事件机制和 JS 原生事件机制的区别

React 的事件机制是对浏览器原生事件机制的一层封装,主要区别如下:


1. 事件绑定方式

  • React:
    React 使用合成事件 (SyntheticEvent) 统一处理跨浏览器的事件兼容性。

    • 所有事件都通过虚拟 DOM 绑定在顶层容器(如 documentroot 节点)。
    • React 内部通过事件委托机制来管理事件监听,减少 DOM 事件绑定的数量。

    示例:

    <button onClick={() => console.log('React event')}>Click Me</button>
    
  • JS 原生:
    原生事件绑定在具体的 DOM 元素上。可以选择直接在 HTML 中写内联事件,或通过 JavaScript 使用 addEventListener 绑定事件。

    示例:

    const button = document.querySelector('button');
    button.addEventListener('click', () => console.log('Native event'));
    

2. 事件模型

  • React:
    React 的事件绑定在顶层,通过事件冒泡机制和事件委托捕获事件。React 的事件模型不会直接操作真实 DOM,而是通过虚拟 DOM 映射真实 DOM。

    • React 中的事件冒泡阶段发生在虚拟 DOM 层面。
    • 事件可以被 stopPropagation()preventDefault() 拦截,但影响的是合成事件。
  • JS 原生:
    原生事件模型分为 捕获阶段目标阶段冒泡阶段,直接绑定在真实 DOM 上。

3. 事件对象

  • React:
    React 的事件对象是 SyntheticEvent,这是对原生事件对象的封装,提供了跨浏览器的统一接口。

    • 属性和方法与原生事件类似,但不完全一致。
    • SyntheticEvent 是轻量的,会被回收,因此不能异步访问。

    示例:

    const handleClick = (e) => {
      console.log(e.nativeEvent); // 原生事件对象
      console.log(e); // React SyntheticEvent
    };
    
  • JS 原生:
    原生事件对象直接提供浏览器的 Event 对象,包含原生 DOM 事件信息。

4. 事件解绑

  • React:
    React 的事件绑定和解绑由内部机制管理,无需手动移除监听器。

    • 当组件卸载时,React 会自动清理事件,避免内存泄漏。
  • JS 原生:
    原生事件需要手动移除监听器,否则可能会造成内存泄漏。
    示例:

    const handleClick = () => console.log('Clicked');
    button.addEventListener('click', handleClick);
    button.removeEventListener('click', handleClick);
    

5. 事件委托

  • React:
    React 内部默认使用事件委托,将所有事件绑定在顶层容器上(如 document)。

    • 因此,不需要手动实现事件委托,子元素的事件冒泡会被 React 捕获并处理。
  • JS 原生:
    如果需要事件委托,需手动绑定事件到父元素并通过事件冒泡机制进行处理。

6. 性能

  • React:

    • 通过事件委托减少了 DOM 事件绑定数量,提高性能。
    • React 事件封装有轻微性能开销,但一般可以忽略。
  • JS 原生:

    • 大量事件绑定在不同元素上可能会影响性能。
    • 更加灵活,但需要手动优化。

总结

对比点React 事件机制JS 原生事件机制
事件绑定绑定在顶层,通过合成事件委托处理直接绑定在具体的 DOM 元素上
事件模型虚拟 DOM 层的事件冒泡捕获、目标、冒泡阶段
事件对象SyntheticEvent(封装,跨浏览器兼容)原生 Event 对象
事件解绑自动清理,无需手动解绑需手动移除事件监听器
事件委托默认内置,通过顶层事件委托需手动实现
性能优化内置优化,通过委托减少绑定数量性能依赖开发者手动优化

问题4

请说说React hooks解决了什么问题? 函数组件与类组件的区别

回答:

React Hooks 解决的问题

  1. 状态管理:在函数组件中引入 useState,无需类组件也能管理状态。
  2. 副作用处理:通过 useEffect 统一处理生命周期逻辑,避免类组件中复杂的生命周期方法。
  3. 代码复用:自定义 Hook 提供更清晰的逻辑复用方式,代替高阶组件 (HOC) 和 render props。
  4. 简化代码:减少类组件中的模板代码,提升开发效率。

函数组件与类组件的区别

  1. 写法

    • 函数组件是无状态组件,直到引入 Hooks 才支持状态管理。
    • 类组件通过 class 声明,依赖 this 来管理状态和生命周期。
  2. 状态和生命周期

    • 函数组件用 useStateuseEffect 管理状态和副作用。
    • 类组件用 state 和生命周期方法。
  3. 性能

    • 函数组件更轻量,性能更优(不涉及 this 和原型链)。
  4. 代码复用

    • 函数组件通过 Hook 实现更灵活的逻辑复用。
    • 类组件通常使用 HOC 和 render props。

函数组件适合现代 React 开发,类组件主要用于维护旧代码。

问题5

React组件通信有哪些方式

回答:

1. 父传子

父组件通过 props 将数据传递给子组件。

父组件文件:Parent.tsx

import React from 'react';
import Child from './Child';

const Parent: React.FC = () => {
  return <Child message="Hello from Parent!" />;
};

export default Parent;

子组件文件:Child.tsx

import React from 'react';

interface ChildProps {
  message: string;
}

const Child: React.FC<ChildProps> = ({ message }) => {
  return <div>{message}</div>;
};

export default Child;

2. 子传父

子组件通过props+回调函数将数据传递给父组件。

父组件文件:Parent.tsx

import React from 'react';
import Child from './Child';

const Parent: React.FC = () => {
  const handleChildData = (data: string) => {
    console.log('Data from Child:', data);
  };

  return <Child onSend={handleChildData} />;
};

export default Parent;

子组件文件:Child.tsx

import React from 'react';

interface ChildProps {
  onSend: (data: string) => void;
}

const Child: React.FC<ChildProps> = ({ onSend }) => {
  return (
    <button onClick={() => onSend('Hello from Child!')}>
      Send to Parent
    </button>
  );
};

export default Child;

3. 兄弟组件通信: 找到这两个兄弟节点共同的⽗节点,结合上⾯两种⽅式由⽗节点转发信息进⾏通信

4. 跨层级组件传值(使用 Context API)

使用 Context API 将数据在多个层级间传递。

Context 文件:DataContext.tsx

import React, { createContext, useContext } from 'react';

export const DataContext = createContext<string>('');

export const useData = () => useContext(DataContext);

interface DataProviderProps {
  children: React.ReactNode;
  value: string;
}

export const DataProvider: React.FC<DataProviderProps> = ({ children, value }) => {
  return <DataContext.Provider value={value}>{children}</DataContext.Provider>;
};

父组件文件:Parent.tsx

import React from 'react';
import { DataProvider } from './DataContext';
import Child from './Child';

const Parent: React.FC = () => {
  return (
    <DataProvider value="Data from Context">
      <Child />
    </DataProvider>
  );
};

export default Parent;

子组件文件:Child.tsx

import React from 'react';
import { useData } from './DataContext';

const Child: React.FC = () => {
  const data = useData();
  return <div>{data}</div>;
};

export default Child;

6. 全局状态管理

使用 Redux、Recoil 或 Zustand 来管理复杂状态。以下是简单的 Redux 示例:

问题6

react中props和state有什么区别

回答:

  • props:父组件传递给子组件的数据,只读,不能修改。
  • state:组件内部的数据,动态可变,通过 setStateuseState 更新。

问题7

React 中 keys 的作用是什么?

回答:

key 是 React 用于识别虚拟 DOM 中元素的唯一标识,帮助高效更新和渲染列表。

作用:

  1. 优化渲染性能:通过 key,React 能快速找到需要更新的元素,避免重复渲染整个列表。
  2. 标识元素唯一性:确保每个列表项有唯一的标识,避免渲染时的错误。

注意:

  • key 通常使用唯一的 ID,如数据库 ID 或索引值。
  • 避免使用数组索引作为 key,除非列表不会改变顺序或增删项。

问题8

React 中 refs 的作用是什么?

回答:

refs 是 React 提供的一种机制,用于访问和操作 DOM 元素或类组件实例。

作用:

  1. 直接访问 DOM:通过 refs,可以在 React 中直接访问和操作 DOM 元素,而不需要通过 stateprops
  2. 触发 DOM 操作:可以用于获取 DOM 元素的值、焦点管理、动画操作等。
  3. 获取组件实例:对于类组件,refs 可以获取组件的实例,进而访问该组件的方法和属性。

使用场景:

  • 聚焦输入框
  • 动画控制
  • 获取子组件的方法

示例:

import React, { useRef } from 'react';

const FocusInput: React.FC = () => {
  const inputRef = useRef<HTMLInputElement>(null);

  const focusInput = () => {
    inputRef.current?.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
};

问题9

说说React diff 算法

回答:

React Diff 算法 是 React 用于优化组件更新和渲染的核心机制。其主要目的是通过比较新旧虚拟 DOM(V-DOM)树,尽可能减少真实 DOM 的操作,提高性能。

核心思想

  1. 最小化 DOM 更新:React 通过虚拟 DOM 进行比对,只更新发生变化的部分,而不是整个 DOM。
  2. 按组件进行比较:React 会先比较组件的类型和属性,只有在有必要的时候才更新其内部元素。

Diff 算法的关键策略

  1. 分层比较

    • React 将整个 UI 拆分为树状结构,每一层比较时,只会比较不同类型的组件。
    • 如果两个节点的类型不同(比如一个是 <div>,另一个是 <span>),React 会直接删除旧节点,创建新节点。
  2. 同级比较

    • 同一个层级的节点(兄弟节点)会根据 key 值进行比较。key 帮助 React 精确识别每个元素的位置,优化重排(reordering)。
  3. 元素更新和删除

    • 如果两个节点具有相同的 key,React 会更新它们,否则 React 会销毁旧节点并创建新节点。
  4. 避免不必要的更新

    • 如果组件的 stateprops 没有变化,React 会跳过重新渲染该组件,提升性能。

具体步骤

  1. 先比较节点类型:如果类型不同,直接销毁旧节点,创建新节点。
  2. 同类型节点比较:如果类型相同,React 会继续比较它们的属性、子节点,计算差异并进行更新。
  3. 递归对比子节点:React 会递归地对比所有子节点,并尽量重用现有节点(基于 key)。

key 的作用

  • 提高效率key 用于标识哪些子节点发生了变化,帮助 React 判断哪些节点需要被移除或重新排序,减少 DOM 操作。

总结

React 的 Diff 算法通过高效地比对虚拟 DOM 来减少不必要的 DOM 更新,使得页面渲染更加高效,从而优化性能。

问题10

说一下常用的hook

回答:

1. useState

  • 用于在函数组件中添加状态管理。
  • 返回一个数组,包含当前状态值和更新状态的函数。
  • 示例:

    const [count, setCount] = useState(0);

2. useEffect

useEffect 用于处理副作用(side effects)。副作用包括数据获取、订阅事件、手动 DOM 操作、定时器等,它们是组件中不直接影响 UI 渲染的操作。

  • 基本用法:

    useEffect(() => {
      // 副作用代码,例如 API 请求、订阅等
    
      return () => {
        // 清理函数:在组件卸载或依赖项变化时执行
      };
    }, [dependencies]);
    
  • 参数:

    • 第一个参数:副作用函数,包含需要执行的代码。可以包含异步操作、DOM 操作等。
    • 第二个参数:依赖数组(dependencies),指定副作用函数在哪些数据变更时重新执行。如果不提供该数组,副作用将在每次渲染后执行;如果提供空数组 [],副作用仅在组件首次渲染时执行一次。
  • 副作用的清理:

    useEffect 允许返回一个清理函数,这个函数会在组件卸载或依赖项变化时执行。适用于清理定时器、取消订阅、清理事件监听器等。

    useEffect(() => {
      const timer = setTimeout(() => {
        console.log('Hello, World!');
      }, 1000);
    
      return () => {
        clearTimeout(timer); // 清理定时器
      };
    }, []);
    
  • 常见使用场景:

    1. 数据获取:

      useEffect(() => {
        fetch('https://api.example.com/data')
          .then((response) => response.json())
          .then((data) => setData(data));
      }, []);  // 只在组件挂载时执行一次
      
    2. 事件订阅:

      useEffect(() => {
        const handleResize = () => {
          console.log('Window resized');
        };
      
        window.addEventListener('resize', handleResize);
      
        // 清理函数:组件卸载时移除事件监听
        return () => {
          window.removeEventListener('resize', handleResize);
        };
      }, []);
      
    3. 清理定时器:

      useEffect(() => {
        const timer = setInterval(() => {
          console.log('Tick');
        }, 1000);
      
        return () => clearInterval(timer); // 清理定时器
      }, []);
      
  • 执行时机:

    useEffect 的副作用函数在每次渲染后执行,但它会在浏览器完成布局和绘制后才会执行,因此不会阻塞页面渲染。执行的时机如下:

    1. 组件首次渲染后: useEffect 会执行。
    2. 依赖项变化时: 当依赖数组中的值变化时,useEffect 会重新执行。
    3. 组件卸载时: 如果 useEffect 返回清理函数,组件卸载时会执行清理操作。

总结:

  • useEffect 用于处理副作用,如数据请求、事件监听、定时器等。
  • 它接受两个参数:副作用函数和依赖项数组。
  • 副作用函数会在组件渲染后执行,可以返回一个清理函数,用于在组件卸载或依赖项更新时清理资源。

3. useContext

  • 用于在函数组件中订阅 React 上下文(Context)。
  • 通过 useContext,你可以访问由上层组件提供的共享数据。
  • 示例:

    const theme = useContext(ThemeContext);

4. useReducer

  • useState 类似,但适用于更复杂的状态管理,特别是在处理多种操作时。
  • useState 更加适合处理多个子状态的复杂逻辑。
  • 示例:

    const [state, dispatch] = useReducer(reducer, initialState);

5. useRef

  • 用于创建可变的引用,且不会触发重新渲染。
  • 通常用于访问 DOM 元素或保存跨渲染周期的数据。
  • 示例:

    const inputRef = useRef(null);

6. useMemo

  • 缓存计算结果: useMemo 会记住上次计算的结果,只有在依赖项发生变化时,才会重新计算。这可以减少不必要的计算,提高性能。
  • 优化渲染: 对于一些计算量大的操作,useMemo 可以有效防止每次渲染都进行重复计算,尤其是在渲染频繁的场景下。
  • 示例:

    const memoizedValue = useMemo(() => {
      return computeExpensiveValue(a, b);
    }, [a, b]);
    
    // computeExpensiveValue 是需要缓存的计算函数。
    // [a, b] 是依赖数组,只有当 a 或 b 改变时,computeExpensiveValue 才会重新执行,否则返回缓存的值。
  • 常见场景:

    1. 避免不必要的重新计算:

      const result = useMemo(() => expensiveComputation(data), [data]);
    2. 避免渲染时重复生成对象或数组:

      const memoizedArray = useMemo(() => [1, 2, 3], []);
  • 注意事项:

    • useMemo 主要用于优化性能。对于简单计算,React 已经很优化,不必使用 useMemo
    • 过度使用 useMemo 可能导致性能下降,因此需要根据实际情况使用。

7. useCallback

  • 缓存函数: 它返回一个 memoized(记忆化的)版本的回调函数,该函数只有在依赖项发生变化时才会更新。
  • 避免不必要的渲染: 当你将回调函数作为 prop 传递给子组件时,useCallback 可以确保子组件不会因为父组件的重新渲染而收到新的函数引用,从而触发不必要的渲染。
  • 示例:

    const memoizedCallback = useCallback(() => {
      doSomething(a, b);
    }, [a, b]);
    
    // doSomething 是需要缓存的回调函数。
    // [a, b] 是依赖数组,只有当 `a` 或 `b` 改变时,`memoizedCallback` 才会重新创建,否则返回缓存的函数。
  • 常见场景:

    1. 避免不必要的函数重创建:

      const handleClick = useCallback(() => {
        console.log("Button clicked");
      }, []);
      
    2. 优化子组件渲染: 当父组件将函数传递给子组件时,useCallback 确保子组件不会因每次渲染时父组件传递的不同函数引用而重新渲染。

      const ParentComponent = () => {
        const handleChange = useCallback((e) => {
          console.log(e.target.value);
        }, []);
      
        return <ChildComponent onChange={handleChange} />;
      };
      
  • useCallbackuseMemo 的区别:

    • useMemo 用于缓存计算值,而 useCallback 用于缓存函数。
    • 本质上,useCallback(fn, deps)useMemo(() => fn, deps) 相同,都是返回一个记忆化的值或函数。
  • 注意事项:

    • useCallback 主要用于性能优化,尤其在函数作为 prop 传递给子组件时。
    • 对于简单的函数,React 会自动优化,所以不需要过度使用 useCallback

8. useLayoutEffect

  • 类似于 useEffect,但会在所有 DOM 更新后同步执行,适用于需要直接操作 DOM 的场景。
  • 示例:

    useLayoutEffect(() => {
      // 直接操作 DOM
    }, []);

9. useImperativeHandle

  • 允许父组件通过 ref 访问子组件暴露的特定实例值或方法。
  • 示例:

    // **ChildComponent.tsx (子组件)** 使用 forwardRef 将父组件传递的 ref 转发给子组件,并通过 useImperativeHandle 暴露 customMethod 方法
    ****
    import React, { useImperativeHandle, forwardRef } from 'react';
    
    interface ChildComponentHandle {
      customMethod: () => void;
    }
    
    // 子组件,通过 `useImperativeHandle` 暴露方法
    const ChildComponent = forwardRef<ChildComponentHandle>((props, ref) => {
      useImperativeHandle(ref, () => ({
        customMethod: () => {
          alert("Custom method called from the parent!");
        }
      }));
    
      return <div>Child Component</div>;
    });
    
    export default ChildComponent;
    // **ParentComponent.tsx (父组件)** 创建一个 childRef 来引用子组件,点击按钮时调用子组件暴露的 customMethod 方法
    import React, { useRef } from 'react';
    import ChildComponent from './ChildComponent';
    
    const ParentComponent: React.FC = () => {
      const childRef = useRef<any>(null);
    
      const callChildMethod = () => {
        // 调用子组件暴露的 customMethod 方法
        if (childRef.current) {
          childRef.current.customMethod();
        }
      };
    
      return (
        <div>
          <h1>Parent Component</h1>
          <button onClick={callChildMethod}>Call Child Method</button>
          <ChildComponent ref={childRef} />
        </div>
      );
    };
    
    export default ParentComponent;
    

这些 Hook 是 React 中常用的工具,可以帮助我们更灵活地处理状态、生命周期、性能优化等任务。

问题11

React 高阶组件是什么,和普通组件有什么区别,适用什么场景

回答:

高阶组件(HOC,Higher-Order Component)是 React 中一种设计模式,它是一个函数,接收一个组件并返回一个新的组件。这个新的组件通常会增强原组件的功能,或者给原组件添加额外的逻辑。

HOC 的特点:

  1. 接受一个组件:HOC 是一个接受组件作为参数的函数。
  2. 返回一个新的组件:它返回一个新的组件,并在新组件中可以添加一些额外的逻辑、状态、行为等。
  3. 不会修改原组件:HOC 不会改变原组件,而是包装并增强它。

与普通组件的区别:

  1. 普通组件

    • 普通组件是直接渲染 UI 的组件。
    • 它不具备增强其他组件功能的能力,通常只管理自己的状态和行为。
  2. 高阶组件

    • HOC 是一个用于增强其他组件的“组件工厂”,它不直接渲染 UI,而是通过包装组件来扩展功能。
    • 它可以通过传递 props 或通过增加一些额外的逻辑(如条件渲染、生命周期管理等)来增强被包装组件。

示例

import React from 'react';

// 一个简单的组件
const SimpleComponent = ({ name }: { name: string }) => {
  return <div>Hello, {name}!</div>;
};

// 高阶组件
function withLogging(WrappedComponent: React.ComponentType<any>) {
  return function EnhancedComponent(props: any) {
    console.log('Component rendered:', WrappedComponent.name);
    return <WrappedComponent {...props} />;
  };
}

// 使用高阶组件包装 SimpleComponent
const EnhancedComponent = withLogging(SimpleComponent);

export default EnhancedComponent;

在上面的示例中,withLogging 是一个高阶组件,它增强了 SimpleComponent,使得每次渲染时都会打印日志。

适用场景:

  1. 逻辑复用

    • 当多个组件需要共享相同的逻辑时,可以使用 HOC 来封装这些逻辑。例如,数据获取、认证检查、权限控制、事件监听等。
  2. 组件增强

    • 如果希望给现有的组件添加功能(如动态样式、状态管理、生命周期钩子等),可以使用 HOC 来包装它们。
  3. 代码复用

    • HOC 允许在不同的组件中复用一些跨组件的功能,而不需要重复编写代码。

常见的 HOC 用法:

  1. 条件渲染:根据某些条件决定是否渲染一个组件。
  2. 权限控制:检查用户是否有权限访问某个组件或页面。
  3. 数据获取:在组件加载时自动获取数据并将数据作为 props 传递给组件。
  4. 事件绑定和解除:在 HOC 中绑定和解除事件监听器。

总结:

高阶组件是 React 中用于组件功能增强和逻辑复用的强大工具。通过接受一个组件并返回一个新的组件,HOC 可以在不修改原组件的情况下,添加额外的功能和逻辑。适用于需要共享逻辑、权限控制、条件渲染等场景。

问题12

什么操作会触发 React 重新渲染组件

回答:

  1. State 变化:当使用 useStateuseReducer 等 hooks 更新状态时,React 会触发组件重新渲染。
  2. Props 变化:当父组件传递给子组件的 props 发生变化时,React 会重新渲染子组件。
  3. Context 变化:当 React Context 的值发生变化时,所有使用该 Context 的组件都会重新渲染。例如,使用 useContext 钩子的组件会在所订阅的 Context 值发生变化时重新渲染。
  4. key 变化:当组件的 key 值发生变化时,React 会将其视为一个新的组件,导致组件重新渲染(通常在列表渲染中使用 key)。

问题13

对React中Fragment的理解,它的使用场景是什么?

回答:

Fragment 是 React 中用来将多个子元素分组在一起,而不会在 DOM 中创建额外的节点。它常用于以下场景:

  1. 避免增加额外的 DOM 元素: 当需要返回多个元素但不想增加多余的 div 或其他容器时使用。

    return (
      <>
        <h1>Hello</h1>
        <p>World</p>
      </>
    );
  2. 列表渲染时避免不必要的父元素: 用于避免为每个列表项添加额外的 DOM 包装元素。
  3. key 一起使用: 当需要给多个元素加 key 时,可以使用 Fragment,而不增加额外的 DOM 节点。

    return (
      <>
        {items.map((item, index) => (
          <React.Fragment key={index}>
            <h3>{item}</h3>
            <p>{item} description</p>
          </React.Fragment>
        ))}
      </>
    );

简写形式 <></> 更为常用。

问题14

什么是受控组件和非控组件

回答:

受控组件是指 React 控制组件的状态,也就是说,组件的值(如表单输入的内容)由 React state 来管理,并且通过 props 传递给组件。

在受控组件中,表单元素的状态(比如 input 的值)由组件的 state 来管理,而当用户输入内容时,组件会更新该状态。

  • 特点:

    • 值由 state 管理,React 组件的 state 是唯一的来源。
    • 通过 onChange 等事件来更新 state。
  • 示例:

    import { useState } from 'react';
    
    const ControlledInput = () => {
      const [value, setValue] = useState('');
    
      const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        setValue(e.target.value);
      };
    
      return <input type="text" value={value} onChange={handleChange} />;
    };
    

非受控组件是指组件的状态由 DOM 自己管理,React 不直接控制表单元素的值。React 仅通过 ref 获取当前值,并不会在 state 中保存输入的内容。

  • 特点:

    • 组件的状态是由 DOM 节点 控制的。
    • 不通过 React state,而是通过 ref 直接访问 DOM 元素。
  • 示例:

    import { useRef } from 'react';
    
    const UncontrolledInput = () => {
      const inputRef = useRef<HTMLInputElement>(null);
    
      const handleSubmit = () => {
        alert('Input value: ' + inputRef.current?.value);
      };
    
      return (
        <div>
          <input type="text" ref={inputRef} />
          <button onClick={handleSubmit}>Submit</button>
        </div>
      );
    };
    

总结:

  • 受控组件: React 完全控制组件的状态,使用 state 和事件处理来管理。
  • 非受控组件: 组件的状态由 DOM 控制,React 仅通过 ref 访问。

问题15

React中的props为什么是只读的?

回答:

在 React 中,props 是只读的,因为它们用于从父组件传递数据到子组件,遵循单向数据流的原则。子组件不能修改 props,这样可以确保数据流向清晰、组件行为可预测,并避免副作用。如果需要修改数据,应该使用 state

问题16

Hook 的使用限制有哪些?

回答:

  1. 只能在函数组件或自定义 Hook 中使用,不能在类组件或普通函数中调用。
  2. 调用顺序必须一致,不能在条件语句、循环或嵌套函数中调用。
  3. 只能在组件的顶层调用,不能在回调或事件处理函数中使用。
  4. 自定义 Hook 必须以 use 开头,确保 React 能识别。
  5. 不能在类组件中使用,只能在函数组件中。

问题17

React的严格模式如何使用,有什么用处?

回答:

React 的严格模式通过 <React.StrictMode> 组件启用,它会在开发模式下对应用进行额外的检查和警告,帮助开发者发现潜在的问题。它不会影响生产环境的行为,只在开发模式下生效。

  • 使用方式:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import App from './App';
    
    ReactDOM.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>,
      document.getElementById('root')
    );
    
  • 严格模式的用处:

    1. 帮助发现不安全的生命周期方法
      React 严格模式会检测是否使用了过时或不安全的生命周期方法(如 componentWillMountcomponentWillUpdatecomponentWillReceiveProps)并发出警告,鼓励开发者使用更新的生命周期方法(如 getDerivedStateFromPropsgetSnapshotBeforeUpdate)。
    2. 检测副作用
      严格模式会在渲染过程中多次调用组件的 render 方法,帮助检测不必要的副作用(比如不必要的状态更新或副作用的重复执行)。这有助于开发者优化组件的渲染和副作用处理。
    3. 验证 React 对象
      它还会检查应用中是否存在不安全的副作用或其他潜在的错误,比如检查开发者是否在组件中使用了不合法的 refs、setState 等。
    4. 支持未来的 React 特性
      严格模式还为未来的 React 特性提供支持,帮助开发者在早期发现潜在的不兼容性。

总结:

React 严格模式是一个开发工具,帮助开发者发现不安全的代码、潜在的错误和不推荐的做法,增强代码质量。它只在开发环境中启用,对生产环境没有影响。

问题18

如何自定义hook

回答:

自定义 Hook 是一个函数,其名称必须以 use 开头,并且可以使用 React 内置的 Hook(如 useStateuseEffect 等)。自定义 Hook 允许在多个组件之间复用状态逻辑。

自定义 Hook 示例:

import { useState, useEffect } from 'react';

// 自定义 Hook: 使用 localStorage 保存和读取值
function useLocalStorage(key: string, initialValue: string) {
  const [storedValue, setStoredValue] = useState(() => {
    const item = window.localStorage.getItem(key);
    return item ? item : initialValue;
  });

  const setValue = (value: string) => {
    setStoredValue(value);
    window.localStorage.setItem(key, value);
  };

  return [storedValue, setValue];
}

// 在组件中使用自定义 Hook
function MyComponent() {
  const [name, setName] = useLocalStorage('name', 'John Doe');

  return (
    <div>
      <p>Stored name: {name}</p>
      <button onClick={() => setName('Jane Black')}>Change Name</button>
    </div>
  );
}

总结:

自定义 Hook 是函数,允许复用逻辑,必须以 use 开头。它可以使用 React 内置 Hook 来管理状态、副作用等。

本文由mdnice多平台发布


Kenis
1 声望0 粉丝

独立开发者,瞎折腾软硬件,全栈开发,全平台自动化,CV大法。木工,胶佬。。。