核心概念✅
什么是 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> ); }
为了避免和 HTML 混淆,JSX 默认有如下规则。
- 大写字母开头的标签被视为 React 组件,小写字母开头的标签被视为 HTML 元素。
- 属性名采用小驼峰命名法,如
className
、onClick
等。 - 可以通过
{}
包裹 JavaScript 表达式,如变量、函数调用等。
延伸阅读
- Writing Markup with JSX 官方对 JSX 的介绍
- Introducing JSX React 官方文档对 JSX 的介绍
JSX 支持哪些功能?
答案
功能 | 描述 |
---|---|
{} 表达式插值 | 可以在 {} 中嵌入 JavaScript 表达 嵌入的内容会自动转移,避免 XSS 攻击 |
空渲染 | 可以返回 null 或 false 来表示不渲染任何内容,如 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 /></> ,也支持直接返回数组 |
延伸阅读
- JSX JSX 的语法规范
- JSX In Depth
什么是 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 核心意义如下
- 渲染优化:VDOM 通过在内存中维护一个轻量级的 DOM 树,通过对比差异,增量更新,提升渲染效率
- 跨平台渲染:VDOM 使得 React 可以在不同的平台(如 Web、Native 等)上使用相同的渲染逻辑。通过将 VDOM 转换为特定平台的渲染指令,React 可以实现跨平台开发。
- 声明式编程: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>;
}
}
延伸阅读
- React Components, Elements, and Instances 讲解这几个概念的差别
state 和 props 区别
什么是 Reconciliation?
答案
协调(Reconciliation) 是 React 内部比对 VDOM 树差异刷新 UI 的算法。
一些核心的比对策略如下
- 对于根节点类型不一样的元素直接替换为新节点,避免树对比的开销
- 对于同类型节点,比对属性,子节点进行递归对比
- 对于列表节点,通过
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 | 非纯函数 | 用来变更状态 |
此外注意 props
和 state
及组件中定义的变量的不可变性,禁止直接操作
延伸阅读
React 和 Vue 有什么区别?
答案
维度 | React | Vue |
---|---|---|
核心思想 | 函数式、声明式 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 的对比