跳到主要内容

核心概念✅

什么是 JSX?

答案

JSX(JavaScript XML) 是 React 用来描述 UI 的模版语法,在构建阶段 React 通过(Babel) 等编译工具,转译为 React.createElement 调用。

提示

由于在构建的时候需要把 JSX 转换为 React.createElement ,这也是为什么需要手动导入 React 的原因。在 React 17 之后,React 引入了新的 JSX 转换方式会转变如下代码,解决了手动导入 React 的问题。具体信息参考 介绍全新的 JSX 转换

// 由编译器引入(禁止自己引入!)
import { jsx as _jsx } from 'react/jsx-runtime'

function App () {
return _jsx('h1', { children: 'Hello world' })
}

JSX 的基本结构如下

实时编辑器
function Counter() {
 const [count, setCount] =    useState(0)
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>+</button>
      <strong style={{ padding: '0 5px' }}>{count}</strong>
      <button onClick={() => setCount(count - 1)}>-</button>
    </div>
  );
}
结果
Loading...

为了避免和 HTML 混淆,JSX 默认有如下规则。

  1. 大写字母开头的标签被视为 React 组件,小写字母开头的标签被视为 HTML 元素。
  2. 属性名采用小驼峰命名法,如 classNameonClick 等。
  3. 可以通过 {} 包裹 JavaScript 表达式,如变量、函数调用等。

延伸阅读

JSX 支持哪些功能?

答案
功能描述
{} 表达式插值可以在 {} 中嵌入 JavaScript 表达 嵌入的内容会自动转移,避免 XSS 攻击
空渲染可以返回 nullfalse 来表示不渲染任何内容,如 return null;,在表达式中 {fals}、{null}、{undefined}、{ture} 均渲染为空
原生元素支持可以采用 HTML 支持的元素, 使用小驼峰命名法绑定事件处理函数,如 <button onClick={handleClick}>Click me</button>
样式处理可以直接在 JSX 中使用内联样式对象,如 <div style={{ color: 'red', fontSize: '16px' }}>Hello</div>, 采用 className
自定义组件可以像 HTML 元素一样使用自定义组件,如 <MyComponent prop1={value} />,组件名必须大写开头
自定义属性可以在组件上添加自定义属性,如 <MyComponent customProp="value" />,这些属性会作为 props 传递给组件,没有值的属性为布尔属性
属性扩展可以使用对象展开语法传递多个属性,如 <MyComponent {...props} />,会将 props 对象中的所有属性传递给组件
动态组件可以通过变量或表达式动态渲染组件,如 const Component = condition ? ComponentA : ComponentB; <Component />
插槽默认 props.children,可以通过 props 传递子元素,如 <Parent><Child /></Parent>,在组件中使用 {props.children} 渲染
渲染属性可以通过函数作为子元素传递渲染逻辑,如 <RenderProp render={() => <Child />} />,在组件中调用 props.render() 渲染内容
条件渲染使用三元表达式或逻辑运算符进行条件渲染,如 condition ? <Component /> : null ,也可用 && 进行简化,如 {condition && <Component />} ,注意 condition 必须为布尔值,避免由于值为 0, '' 导致的非预期场景
列表渲染使用 map 方法遍历数组生成列表,如 <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul> ,注意 key 来优化渲染
片段使用 <>...</> 包裹多个元素,避免额外的 DOM 节点,如 <><Child1 /><Child2 /></>,也支持直接返回数组
import React, { useState } from 'react';

// 用于演示的自定义组件
const CustomComponent = ({ customProp, isActive, children }) => (
   <div className={isActive ? 'active' : ''}>
      <p>自定义属性: {customProp}</p>
      {children}
   </div>
);

// 用于动态渲染的组件
const ComponentA = () => <div>这是组件 A</div>;
const ComponentB = () => <div>这是组件 B</div>;

// 使用渲染属性的组件
const RenderPropComponent = ({ render }) => (
   <div className="render-container">
      <h4>渲染属性演示:</h4>
      {render()}
   </div>
);

// 演示插槽的组件
const Parent = ({ children }) => (
   <div className="parent">
      <h4>父组件:</h4>
      <div className="children-slot">{children}</div>
   </div>
);

const JSXFeaturesDemo = () => {
   // 演示用的状态
   const [count, setCount] = useState(0);
   const [showA, setShowA] = useState(true);
   
   // 用于列表渲染的数据
   const fruits = [
      { id: 1, name: '苹果' },
      { id: 2, name: '香蕉' },
      { id: 3, name: '樱桃' }
   ];
   
   // 用于演示XSS保护的潜在危险内容
   const userInput = '<script>alert("XSS")</script> 用户内容';
   
   // 用于属性展开的props
   const buttonProps = {
      className: 'primary-button',
      disabled: count > 10,
      'aria-label': '增加计数器'
   };
   
   // 基于状态的动态组件
   const DynamicComponent = showA ? ComponentA : ComponentB;

   return (
      <div className="jsx-demo">
         <h1>JSX特性演示</h1>
         
         {/* 表达式插值 */}
         <section>
            <h2>表达式插值</h2>
            <p>计数值: {count}</p>
            <p>计算值: {count * 2}</p>
            <p>用户输入(自动转义): {userInput}</p>
         </section>
         
         {/* 空值渲染 */}
         <section>
            <h2>空值渲染</h2>
            <div>
               空值: {null} {false} {undefined} {true}
            </div>
            {count > 5 ? null : <p>当计数 > 5 时这段将消失</p>}
         </section>
         
         {/* 原生元素与事件 */}
         <section>
            <h2>原生元素与事件</h2>
            <button onClick={() => setCount(count + 1)}>
               增加计数
            </button>
         </section>
         
         {/* 样式处理 */}
         <section>
            <h2>样式处理</h2>
            <div style={{ color: 'red', fontSize: '16px', fontWeight: 'bold' }}>
               内联样式文本
            </div>
            <div className="external-styled">
               类样式文本
            </div>
         </section>
         
         {/* 自定义组件 */}
         <section>
            <h2>自定义组件</h2>
            <CustomComponent customProp="你好世界" isActive>
               <span>这是一个子元素</span>
            </CustomComponent>
         </section>
         
         {/* 自定义属性 */}
         <section>
            <h2>自定义属性</h2>
            <div data-testid="demo" data-value={count}>
               带有自定义属性的元素
            </div>
         </section>
         
         {/* 属性展开 */}
         <section>
            <h2>属性展开</h2>
            <button {...buttonProps} onClick={() => setCount(count + 1)}>
               点击我 ({count})
            </button>
         </section>
         
         {/* 动态组件 */}
         <section>
            <h2>动态组件</h2>
            <button onClick={() => setShowA(!showA)}>切换组件</button>
            <DynamicComponent />
         </section>
         
         {/* 插槽演示 */}
         <section>
            <h2>插槽</h2>
            <Parent>
               <p>这个内容作为children传递</p>
               <button>子按钮</button>
            </Parent>
         </section>
         
         {/* 渲染属性 */}
         <section>
            <h2>渲染属性</h2>
            <RenderPropComponent 
               render={() => (
                  <div>由渲染函数创建的内容: {count}</div>
               )} 
            />
         </section>
         
         {/* 条件渲染 */}
         <section>
            <h2>条件渲染</h2>
            {/* 三元表达式 */}
            {count % 2 === 0 ? <p>计数是偶数</p> : <p>计数是奇数</p>}
            
            {/* 逻辑与运算符 (小心非布尔值) */}
            {count > 3 && <p>计数大于3</p>}
            
            {/* 避免非布尔值可能带来的问题 */}
            {Boolean(count) && <p>当计数为0时不会显示</p>}
         </section>
         
         {/* 列表渲染 */}
         <section>
            <h2>列表渲染</h2>
            <ul>
               {fruits.map(fruit => (
                  <li key={fruit.id}>{fruit.name}</li>
               ))}
            </ul>
         </section>
         
         {/* Fragment片段 */}
         <section>
            <h2>Fragment片段</h2>
            <>
               <p>Fragment中的第一个段落</p>
               <p>Fragment中的第二个段落</p>
            </>
         </section>
      </div>
   );
};

export default JSXFeaturesDemo;

延伸阅读

什么是 Virtual DOM, 它的作用是什么?

答案

Virtual DOM(VDOM)是一个对象,用来描述 React UI 的结构和状态。参考 源码 核心结构如下, 类型定义参考 ReactElementType


export type ReactElement = {
$$typeof: any, // 内部属性,标识组件的类型,例如 REACT_ELEMENT_TYPE 等
type: any, // 组件的类型,可以是字符串(如 'div')或函数/类组件
key: any, // 组件的唯一标识符,用于优化渲染
ref: any, // 组件的引用,用于访问 DOM 元素或组件实例
props: any, // 组件的属性,包含所有传递给组件的 props

// 以下是一些内部属性,只在 NODE_ENV === 'development' 时生效,仅做了解
_owner: any, // 内部只读属性,{ current: null | Fiber },记录负责创建此元素的组件关联对应的 Fiber 对象
_store: {validated: 0 | 1 | 2, ...}, // 备份 dom 的状态和其他额外调试信息
_self: self, // 标记组件的上下文
_source: source, // 标记文件名、行号等信息
_debugInfo: null | ReactDebugInfo, // 调试组件的信息,性能等
_debugStack: Error, // 调试栈信息
_debugTask: null | ConsoleTask, // 调试任务信息
};

所以可以直接返回一个对象来实现组件的渲染,例如

实时编辑器

function App() {
   return ({
   // 注意这个属性是必须的,react 只有识别到了内部类型才会触发渲染逻辑
    $$typeof: Symbol.for('react.transitional.element'),
    type: 'h1',
    props: {
        children: [ 
           {
            $$typeof: Symbol.for('react.transitional.element'),
            type: 'Button',
            props: {
               onClick: () => alert('Hello React'),
               children: `Hello React`,
               style: { color: '#7AC8E5', fontWeight: 'bold' }
            },
           }
         ]
    },
  })
}
结果
Loading...

VDOM 核心意义如下

  1. 渲染优化:VDOM 通过在内存中维护一个轻量级的 DOM 树,通过对比差异,增量更新,提升渲染效率
  2. 跨平台渲染:VDOM 使得 React 可以在不同的平台(如 Web、Native 等)上使用相同的渲染逻辑。通过将 VDOM 转换为特定平台的渲染指令,React 可以实现跨平台开发。
  3. 声明式编程:VDOM 使得开发者可以以声明式的方式描述 UI,而不必关注具体的渲染细节。这种方式提高了代码的可读性和可维护性。
提示

一个典型的误区是认为用了 VDOM 一定比操作原生 DOM 快,VDOM 的核心优势是规避了直接操作 DOM 的心智负担。通过对比前后 VDOM 树的差异来进行增量更新,从而保持了一个较好的性能。 可以阅读 Virtual DOM is pure overhead 进一步理解

延伸阅读

Elements 和 Components 有什么区别?

答案
  1. Element(元素) 是一个不可交互的对象,用来构建 React 应用的基础模块。支持树结构,一般由 Component 创建,也可以直接调用 createElement 方法生成。核心结构 ReactJSXElement

    const ReactElement = {
    type: 'h1', // 支持合法的 DOM 标签或者 React 组件
    props: {}, // 传递给 Element 的属性 可以为 null,注意会排除 ref 和 key,children 会被放到 props 中实现树结构
    key: null, // 可选 key 用来表示元素,作为 reconciliation 的依据
    ref: null, // 可选 ref 用来引用元素

    // ... 框架消费的属性
    $$typeof: REACT_ELEMENT_TYPE, // 用于 React 内部标识 Element 类型
    _owner: null, // 父节点
    _store: {}, // 用于存储一些内部状态
    _self: null, // 类组件用于存储 this 指向
    _source: null // 用于存储源码位置,帮助调试
    }
  2. Component(组件)是 React 中创建可复用 UI 的基本代码块,可以是函数组件或者类组件。函数组件和类组件的 render 方法返回 Element 元素。一般用 jsx 语法创建,例如

实时编辑器
function Welcome(props) {
   return <h1>Hello, {props.name}</h1>;
}
结果
Loading...

延伸阅读

什么是 ReactNode 它和 Element 的区别是什么?

答案

参看 ReactNode 定义 ReactNode 可以理解为 React 中可以实际渲染的所有类型的节点,包括 ReactElement、Portal、文本节点、Fragment、Provider 和 Consumer 等。

export type ReactNode =
| React$Element<any>
| ReactPortal
| ReactText
| ReactFragment
| ReactProvider<any>
| ReactConsumer<any>;

而 ReactElement 是 ReactNode 的一个子集,表示一个具体的 React 元素。也就是我们常说的 Virtual DOM(VDOM),它是一个不可变的对象,描述了 UI 的结构和属性。结构参考 React$Element

type React$Element<
+ElementType: React$ElementType,
+P = React$ElementProps<ElementType>,
>: {
+type: ElementType,
+props: P,
+key: React$Key | null,
+ref: any,
};

什么是高阶组件 (Higher-Order Components HOC) ?

答案

高阶组件是一个函数,接受一个组件作为参数返回新的组件。const EnhancedComponent = higherOrderComponent(WrappedComponent); 可以采用此模式来对原始组件进行增强、拦截等各种操作。示例如下

import { useState, useEffect } from 'react'

// Higher Order Component
const withLoading = (WrappedComponent, fetchData) => {
  return function WithLoadingComponent (props) {
    const [data, setData] = useState(null)
    const [loading, setLoading] = useState(true)
    const [error, setError] = useState(null)

    useEffect(() => {
      const loadData = async () => {
        try {
          setLoading(true)
          const result = await fetchData()
          setData(result)
        } catch (err) {
          setError(err.message)
        } finally {
          setLoading(false)
        }
      }

      loadData()
    }, [])

    if (loading) return <div>Loading...</div>
    if (error) return <div>Error: {error}</div>
    return <WrappedComponent data={data} {...props} />
  }
}

// Example component that displays user data
const UserList = ({ data }) => {
  return (
      <ul>
         {data.map(user => (
            <li key={user.id}>{user.name}</li>
         ))}
      </ul>
  )
}

// Mock API call
const fetchUsers = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve([
        { id: 1, name: 'John Doe' },
        { id: 2, name: 'Jane Smith' },
        { id: 3, name: 'Bob Johnson' }
      ])
    }, 1000)
  })
}

const UserListWithLoading = withLoading(UserList, fetchUsers)

const App = () => {
  return (
      <div>
         <h1>Users</h1>
         <UserListWithLoading />
      </div>
  )
}

export default App

消费高阶组件时需要注意如下问题

  1. 不要改变原始组件 高阶组件不应该修改传入的组件,而是使用组合的方式将其包裹起来。详见 不要修改原始组件
  2. 不要在 render 中使用高阶组件 高阶组件不应该在 render 方法中创建,render 需要保持为纯函数。详见 不要在 render 中使用高阶组件
  3. 注意 ref 和 static 丢失的问题 高阶组件可能会导致 ref 和 static 丢失,详见 ref 和 static 丢失

延伸阅读

state 和 props 区别是什么

答案

核心区别

state 和 props 是 React 组件数据流的两个核心概念,它们在数据来源、可变性和作用范围上有本质区别。

定义对比

  • state: 组件内部的私有状态,由组件自身管理和更新
  • props: 组件外部传入的属性,由父组件传递给子组件

主要区别

维度stateprops说明
数据来源组件内部父组件传递state 自管理,props 外部控制
可变性可变不可变只有 state 可通过 setState 更新
作用范围当前组件父子组件间state 私有,props 用于通信
初始化constructor 或 useState父组件传入state 需要初始化,props 接收即可

代码示例

// 父组件
function Parent() {
const [count, setCount] = useState(0) // state: 组件内部状态

return (
<Child
title="计数器" // props: 传递给子组件
count={count} // props: 传递状态值
onIncrement={() => setCount(count + 1)} // props: 传递回调函数
/>
)
}

// 子组件
function Child({ title, count, onIncrement }) { // props: 接收父组件传递的数据
const [isVisible, setIsVisible] = useState(true) // state: 子组件内部状态

return (
<div>
<h3>{title}</h3> {/* 使用 props */}
<p>计数: {count}</p> {/* 使用 props */}
<button onClick={onIncrement}>增加</button> {/* 使用 props */}

{isVisible && <p>显示状态</p>} {/* 使用 state */}
<button onClick={() => setIsVisible(!isVisible)}>
切换显示 {/* 更新 state */}
</button>
</div>
)
}

使用场景

  • state: 表单输入、开关状态、计数器等组件内部数据
  • props: 组件配置、数据传递、事件回调等父子组件通信

常见误区

  • 不要直接修改 state,必须通过 setState 或 useState 的更新函数
  • 不要尝试修改 props,它们是只读的
  • 避免将 props 直接赋值给 state,应该通过计算属性或 useEffect 处理
提示

遵循"单向数据流"原则:数据从父组件流向子组件(props),状态变化通过回调函数向上传递。

延伸阅读

什么是 Context 如何使用?

答案

Context 概念

Context 是 React 提供的一种跨组件传递数据的机制,解决了组件间的"props drilling"问题。它允许数据在组件树中向下传递,而不需要逐层手动传递 props。

核心API

  • createContext: 创建 Context 对象,包含 Provider 和 Consumer 组件
  • useContext: 在函数组件中访问 Context 值的 Hook
  • Provider: 提供 Context 值的组件
  • Consumer: 消费 Context 值的组件(较少使用)

基本使用

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

// 1. 创建 Context
const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} })

// 2. 消费 Context
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext)
return (
<button
style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff'
}}
onClick={toggleTheme}
>
切换主题
</button>
)
}

// 3. 提供 Context 值
function App() {
const [theme, setTheme] = useState('light')
const toggleTheme = () => setTheme(theme === 'light' ? 'dark' : 'light')

return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<ThemedButton />
</ThemeContext.Provider>
)
}

使用场景

场景说明示例
全局状态应用级别的共享数据主题、语言、用户信息
组件通信跨多级组件传递数据表单数据、配置项
功能模块特定功能的上下文购物车、权限管理

性能优化策略

  • Context 分割: 将不同功能的数据分到独立的 Context 中
  • useMemo 缓存值: 避免不必要的对象引用变化
  • React.memo 包裹: 对消费组件进行浅比较优化
// 分割 Context
const ThemeContext = createContext()
const UserContext = createContext()

function App() {
// 使用 useMemo 缓存 Context 值
const themeValue = useMemo(() => ({ theme: 'dark' }), [])
const userValue = useMemo(() => ({ name: 'John' }), [])

return (
<ThemeContext.Provider value={themeValue}>
<UserContext.Provider value={userValue}>
<ComponentTree />
</UserContext.Provider>
</ThemeContext.Provider>
)
}

常见误区

  • 过度使用 Context 替代简单的 props 传递
  • 在 Context 中存储频繁变化的数据
  • 不注意 Provider 值的引用稳定性
提示

Context 适合共享相对稳定的数据,频繁变化的数据建议使用专门的状态管理库如 Redux 或 Zustand。

延伸阅读

什么是渲染属性(render props) ?

答案

渲染属性概念

渲染属性(Render Props)是 React 中一种基于组件组合的代码复用技术。它通过一个值为函数的 prop 来动态决定要渲染什么内容,这个函数返回一个 React 元素。

基本模式

渲染属性的核心思想是将渲染逻辑作为函数传递给组件,让组件调用该函数来确定渲染内容:

// 基本渲染属性组件
function DataProvider({ render, children }) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)

useEffect(() => {
fetchData().then(result => {
setData(result)
setLoading(false)
})
}, [])

// 调用 render 函数传递状态
return render({ data, loading })
}

// 使用渲染属性
function App() {
return (
<DataProvider
render={({ data, loading }) => (
loading ? <div>加载中...</div> : <div>数据: {data}</div>
)}
/>
)
}

常见实现方式

方式语法说明
render prop<Component render={fn} />通过 render 属性传递函数
children as function<Component>{fn}</Component>将函数作为 children 传递
自定义 prop<Component customRender={fn} />使用自定义属性名

代码示例

// 1. 使用 render prop
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 })

useEffect(() => {
const handleMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY })
}
document.addEventListener('mousemove', handleMove)
return () => document.removeEventListener('mousemove', handleMove)
}, [])

return render(position)
}

// 2. 使用 children as function
function Toggle({ children }) {
const [isOn, setIsOn] = useState(false)

return children({
isOn,
toggle: () => setIsOn(!isOn)
})
}

// 使用示例
function App() {
return (
<div>
{/* render prop 方式 */}
<MouseTracker
render={({ x, y }) => (
<p>鼠标位置: ({x}, {y})</p>
)}
/>

{/* children as function 方式 */}
<Toggle>
{({ isOn, toggle }) => (
<button onClick={toggle}>
{isOn ? '开' : '关'}
</button>
)}
</Toggle>
</div>
)
}

应用场景

  • 状态逻辑复用: 在多个组件间共享状态管理逻辑
  • 条件渲染: 根据不同条件渲染不同内容
  • 数据获取: 封装数据请求逻辑,让使用者专注于渲染
  • 动画组件: 提供动画状态,让使用者控制渲染样式

与其他模式对比

  • vs HOC: 渲染属性更直观,避免了 HOC 的嵌套地狱和静态方法丢失问题
  • vs Hooks: Hooks 出现后,大部分渲染属性的使用场景被 Hooks 替代
  • vs 组件组合: 渲染属性更灵活,可以传递复杂的渲染逻辑

注意事项

  • 函数组件中使用 useCallback 优化性能,避免每次渲染创建新函数
  • 注意函数引用的稳定性,避免不必要的子组件重渲染
  • 现代 React 推荐使用自定义 Hooks 替代渲染属性模式
提示

虽然渲染属性是强大的模式,但在 React Hooks 出现后,大多数场景建议使用自定义 Hooks 来实现逻辑复用,代码更简洁易懂。

延伸阅读

什么是 React Hooks,它解决什么问题?

答案

React Hooks 是 React 16.8、React Native (0.59) 版本引入的一种新特性,它允许在函数组件中使用状态和钩子来执行副作用。hooks 解决如下的问题

  1. 通过自定义 hooks ,实现跨组件的状态逻辑复用,避免 HOC 和 render props 带来的嵌套复杂性和组件侵入式改动问题
  2. 通过自定义 hooks 组合实现复杂逻辑,避免类组件中逻辑分散到不同的生命周期中导致的难以维护的问题
  3. hooks 的函数式 API 为编译优化提供了更好的支持,避免了类组件中 this 的问题和复杂的生命周期管理

使用 Hooks 注意如下规则

  1. Hooks 只能出现在函数组件的顶层,不能在循环、条件或嵌套函数中调用,这样确保 hooks 的调用顺序在每次渲染中保持一致,避免状态错乱和副作用混乱, 进一步理解可以阅读 Why Do Hooks Rely on Call Order?
  2. Hooks 只能在 React 函数组件和自定义 hooks 中调用,不能在普通函数或类组件中使用
  3. Hooks 并不是完全兼容 Class 组件的生命周期,例如 getSnapshotBeforeUpdate, getDerivedStateFromError and componentDidCatch 详见 Do Hooks cover all use cases for classes?
  4. 可以利用 eslint-plugin-react-hooks 校验 1 和 2 的规则,避免错误使用 Hooks

延伸阅读

什么是 Reconciliation?

答案

协调(Reconciliation) 是 React 内部比对 VDOM 树差异刷新 UI 的算法。

一些核心的比对策略如下

  1. 对于根节点类型不一样的元素直接替换为新节点,避免树对比的开销
  2. 对于同类型节点,比对属性,子节点进行递归对比
  3. 对于列表节点,通过 key 属性来优化对比,尽可能实现节点复用和最小化更新

经过上述优化整体的树更新从 O(n^3) 降低到 O(n),大大提升了性能。

什么是 合成事件(synthetic event) ,它有什么作用?

答案

合成事件(Synthetic Event) 是 React 对浏览器原生事件的封装,提供了一致的事件处理接口,解决了浏览器间事件模型不一致的问题。

React 会在调用 ReactDOM.createRoot(container) 的时候,内部执行 listenToAllSupportedEvents 方法进行委托,支持的事件列表详见 DOMEventNames。合成事件的对象结构详见 SyntheticBaseEvent 可以通过 nativeEvent 属性访问原生事件对象。

注意

在 react16 及之前,由于存在合成事件, React 会采用事件池 进行管理,事件会在回调函数执行后被重置,导致无法在异步回调中访问事件对象。React 17 之后,合成事件不再使用事件池,事件对象会在回调函数执行后保留,可以直接访问。示例如下

function handleChange(e) {
setTimeout(() => {
// 无法访问到合成事件对象,因为在事件池中会被销毁
console.log(e.target.value); // Too late!
}, 100);
}

function handleChange(e) {
// 利用 persist() 方法保留合成事件对象
e.persist();

setTimeout(() => {
console.log(e.target.value); // 生效
}, 100);
}

延伸阅读

React Fiber 是什么,解决什么问题?

答案

React Fiber 是 React 16 引入的全新协调(Reconciliation)算法和架构,通过增量渲染,解决 React 15 中的性能瓶颈和渲染阻塞问题。

Fiber 本质是一个对象,和 React Element 一一对应,每一个 Fiber 本质就是一个渲染工作单元( Unit Work)。React 利用 Fiber 节点实现对渲染任务的调度。

延伸阅读

React 中纯函数的概念是什么?

答案

纯函数的在 React 语境下可以简单理解为幂等,及对于相同的输入总是返回相同的输出,并且没有副作用。在函数和类组件中一些典型的规则如下

组件类型方法规则原因
类组件constructor纯函数确保初始化一致,避免修改状态
getDerivedStateFromProps纯函数渲染前触发,规整 state 返回最终渲染的状态
shouldComponentUpdate纯函数判断是否需要更新,避免不必要的渲染
render纯函数返回 UI 结构,避免副作用
setState(() => state)纯函数更新组件状态,可能触发重新渲染
componentDidMount非纯函数可以执行副作用,如数据获取、订阅等
componentDidUpdate非纯函数可以执行副作用
componentWillUnmount非纯函数可以执行清理工作,如取消订阅、清除定时器等
自定义方法非纯函数可以执行副作用,如事件处理、状态更新等
setState(state, callBack) 的 callBack非纯函数可以执行副作用
函数组件无 hooks纯函数返回状态和更新函数,避免副作用
useState()纯函数返回状态和更新函数,避免副作用
[state,setState] = useState() 的 setState非纯函数用来变更状态

此外注意 propsstate 及组件中定义的变量的不可变性,禁止直接操作

延伸阅读

React 和 Vue 有什么区别?

答案
维度ReactVue
核心思想函数式、声明式 UI,组件即函数响应式、声明式 UI,数据驱动视图
视图层实现JSX 语法,全部用 JS 描述 UI单文件组件(SFC),模板+JS+CSS 分离
数据响应手动 setState,单向数据流响应式系统,数据变更自动驱动视图
组件通信props、context、状态提升props、$emit、provide/inject
状态管理官方无内置,常用 Redux/MobX/Recoil官方内置 Vuex/Pinia
生命周期useEffect/useLayoutEffect 等 Hook生命周期钩子(onMounted、onUpdated)
指令系统无,全部用 JS 实现丰富的模板指令(v-if、v-for 等)
生态与扩展依赖社区生态,灵活但需手动集成官方工具链完善,开箱即用
性能优化虚拟 DOM、Fiber、合成事件虚拟 DOM、响应式依赖收集、编译优化
提示

从使用角度看,React 更倾向于函数式编程,强调组件的纯函数特性和不可变数据流;而 Vue 则更接近于传统的 MVVM 模式,强调响应式数据绑定和模板语法。学习曲线上 Vue 更容易上手、心智负担更小。React 则更灵活但需要更多的配置和生态集成。开发者可以根据项目需求和团队技术栈选择合适的框架。

  • react vs vue Vue 官方文档对 React 和 Vue 的对比
  • React vs Vue 社区内核心开发对 react 和 Vue 的对比