跳到主要内容

核心概念✅

什么是 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, // 调试任务信息
};

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 有什么区别?

答案

元素(Elements) 是 React 的基本构建块,可以嵌套,表示 UI 中的一个节点。它是不可变的,本质上 Elment 元素的会变转换成 VDOM 对象描述。元素可以是原生 HTML 元素、组件或文本。

const element = <h1><strong>Hello, world</strong></h1>;
提示

更确切的说过 Elements 应该叫做 React Elements,JSX 本质上是语法糖,最终会被转换成 React.createElement 调用。

组件(Components) 是构成页面的可复用部分,可以是函数或类。组件可以接收 props 并返回元素(Elements)。组件本质就是 Element 的工厂函数或类, 用来实现可复用的逻辑和 UI。

// 函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

// 类组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

延伸阅读

state 和 props 区别

什么是 Reconciliation?

答案

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

一些核心的比对策略如下

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

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

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 的对比
48%