你必须知道的 React 问题
React 是 Meta(原 Facebook)开源、用于构建用户界面的 JavaScript 库。
React 起源于 Facebook 内部项目,因对市面上已有的 MVC 框架不满意,团队自研用于 Instagram 业务,2013 年 5 月正式对外开源。
React 重点解决大规模数据集渲染难题。依靠虚拟 DOM + Diff 算法,数据变更时只更新 DOM 差异部分,获得优秀渲染性能。同时顺应前端组件化趋势,允许开发者将独立、简短的代码片段封装为组件,通过组合组件构建复杂交互式 UI。
备注:React 不属于完整 MVVM 框架;多数观点认为它并不是完整 MVC,仅仅承担 MVC 模式中的 View(视图层),是一套以状态驱动视图、单向数据流的视图库。
React 核心特点:
- 声明式设计:声明期望的 UI 状态,代码可读性高,便于调试;
- 高性能:通过虚拟 DOM,最大限度降低直接操作真实 DOM 的频次;
- 灵活性:可以和其他库、框架混合使用,渐进接入现有项目;
- JSX 语法:JSX 是 JS 语法扩展,非强制,但官方推荐使用;
- 组件化:组件可复用,适合大型项目协作开发;
- 单向响应数据流:数据流向清晰,减少重复代码,相比双向绑定逻辑更简单。
如何创建一个新的 React 应用?
create-react-app
Create React App 曾是官方推荐的创建 React 单页应用方式,提供零配置现代化构建设置。
备注:Create React App 官方已停止维护,新项目更常见的选择是 Vite 或 Next.js。
npx create-react-app my-app
cd my-app
npm start
create-react-app 是基于 webpack 的上层打包方案,内置 build、dev、lint 等能力,在打包层优化开发体验;它不是完整框架,不包含路由,也不支持直接修改 webpack 配置。
umi
umi,中文读作乌米,是可扩展企业级前端应用框架。
umi 以路由为核心,同时支持配置式路由与约定式路由,配套完整生命周期插件体系,覆盖源码到构建产物全流程,可实现各类业务扩展需求。
umi 是蚂蚁集团底层前端框架,直接、间接支撑超过 3000 个业务应用,涵盖 Java 后台管理、Node、H5、离线 Hybrid、纯前端静态资源、CMS 等多种业务场景。
mkdir myapp
cd myapp
npx @umijs/create-umi-app
npm i
npm start
create-umi 脚手架提供多种模板能力:
- project:通用项目脚手架,可选 TypeScript,集成 umi-plugin-react 常用能力
- ant-design-pro:仅提供 pro 布局骨架,业务页面可通过 umi block 快速添加
- block:区块脚手架
- plugin:插件开发脚手架
- library:组件库脚手架,基于 umi-plugin-library
npm create umi
Next.js
Next.js 是主流 React 上层框架,用于构建静态站点与服务端渲染应用,开箱即支持路由、样式方案,默认基于 Node.js 服务环境。
Gatsby
Gatsby 是使用 React 构建静态网站的优选方案。基于 React 组件开发,输出预渲染 HTML + CSS,保障页面加载性能。
从零开始创建 React 应用
完整 JS 构建工具链一般由三部分组成:
- 包管理器:npm、Yarn,管理海量第三方依赖,便捷安装、升级包;
- 打包器:webpack、Parcel,把模块化源码打包压缩,优化加载性能;
- 编译器:Babel,编译新版 JS、JSX,兼容低版本浏览器。
如果需要完全自研工具链,可以参考官方指南,手动复刻 create-react-app 的基础能力。
其他工具链
- Neutrino:融合 webpack 能力,提供预设配置,内置 React 应用、组件开发预设。
- Nx:面向全栈 monorepo 的开发工具集,内置 React、Next.js、Express 等支持。
- Parcel:零配置快速打包工具,可直接搭配 React 使用。
- Razzle:零配置 SSR 框架,相比 Next.js 提供更高自由度。
应用程序中的三种架构模式
MV 架构模式最早大量应用于桌面、后端服务,近几年广泛应用在 JavaScript 前端领域。MV 模式核心目标是分离关注点。三种最重要的模式:MVC、MVP、MVVM。
什么是 MVC?
MVC(Model-View-Controller),将应用拆分为三部分:
- Model(模型):业务数据,以及操作数据的业务方法;
- View(视图):展示数据的 UI 界面,本身尽量不包含业务逻辑;
- Controller(控制器):连接模型与视图,处理事件、调度整个业务流程。

交互流程:
- View 接收用户交互(鼠标、键盘、路由等),将事件交给 Controller;
- Controller 执行业务逻辑,调用 Model 更新数据;
- Model 数据变更后,通知 View 刷新界面。
还有另一条路径:不经过用户界面交互,外部指令直接通过 Controller 修改 Model,进而更新 View。典型场景:hashchange 修改 URL。
MVC 实现模块与展示分离,提升可维护性、扩展性、可移植性、组件复用能力。
MVC 的缺点:Model 和 View 解耦不够彻底,二者关联较强,难以完全独立复用;数据流方向不唯一,View 既可以被 Model 更新,也可以被 Controller 修改,追踪数据流比较麻烦。
典型框架: Backbone.js。
什么是 MVP?
MVP(Model-View-Presenter),是 MVC 的衍生模式,重点优化视图层逻辑。
MVP 分为两类实现:Passive View(被动视图)、Supervising Controller(监督控制器)。
Passive View(被动视图)
Presenter 作为核心控制层,Model 与 View 之间完全没有直接联系。
- Model(业务模型):业务数据、数据处理方法;
- View(视图界面):展示界面,只负责转发用户事件,对外提供操作接口;
- Presenter(派发器):中间层,统一控制 Model 修改与 View 的刷新。

交互流程:
- View 捕获用户交互,转发事件到 Presenter;
- Presenter 执行业务逻辑,调用 Model 修改数据;
- Model 更新完成,通知 Presenter;
- Presenter 收到通知,调用 View 提供的接口刷新界面。
Supervising Controller(监督控制器)
视图可以自己处理简单的 Model-View 状态同步;复杂业务逻辑、复杂状态同步交给 Presenter 处理。
MVP 模式做到 View 与 Model 完全解耦,互相不知道对方存在,只通过 Presenter 通信;View 更容易抽离为可复用组件。
MVP 的缺点:业务复杂时 Presenter 会变得十分庞大臃肿;数据同步全部需要手动编码,会产生大量重复模板代码。
典型框架:Web 端框架较少,Android 原生开发大量使用该模式。
什么是 MVVM?
MVVM(Model-View-ViewModel),MVC、MVP 的改良版本:
- Model(业务模型):业务数据与数据处理逻辑;
- View(视图界面):UI 展示,使用模板语法将数据声明式渲染到 DOM;
- ViewModel(视图模型):核心能力为双向绑定;View、Model 的修改可以自动同步到对方。

MVVM 通过双向绑定减少模板代码,提升开发效率。
MVVM 的缺点:大型项目中大量绑定会创建很多监听器,带来隐性性能开销,难以定位数据变化来源。
典型框架: knockoutjs,Vue 的设计思想大量借鉴 MVVM。
双向绑定
MVVM 的数据绑定常见三种实现手段:
- 数据劫持
- 发布-订阅模式
- 脏值检查
Vue 使用数据劫持 + 发布-订阅结合的方案。三个核心概念:Observer(数据监听器)、Compiler(模板解析器)、Watcher(订阅者)。
- Observer 监听对象数据变化;不管是 View 修改还是 Model 修改,Observer 捕获变更,通知 Watcher;
- Compiler 解析模板指令,绑定事件,负责更新真实视图;
- Watcher 接收 Observer 通知,调度 Compiler 完成视图更新。
现在很多框架都具备双向绑定能力。AngularJS 需要绑定在 $scope;React 没有内置双向绑定,需要手动调用 setState 实现。
MVC、MVP、MVVM 的区别
MVC 和 MVP(Passive View)区别:
二者 Controller、Presenter 负责业务,Model 提供数据,View 负责展示。
MVC 中 View 可以直接访问 Model;
MVP(Passive View)View 和 Model 完全解耦,View 不能直接访问 Model,全部操作必须经过 Presenter。
MVP 和 MVVM 区别:
结构非常相似。
MVP:Presenter 需要手动调用方法完成 View、Model 的同步;
MVVM:ViewModel 内置双向绑定,Model、View 修改会自动同步对方,不需要手动写大量更新逻辑。
总结(个人理解)
架构模式属于概念层面的抽象,不可以拿某个框架去硬套某一个模式;MV* 模式最早来源于桌面、后端,Web 框架只是借鉴思想,实现上会做改造。
MV* 中 Model 负责存数据,View 负责展示数据;变化主要集中在中间层(C、P、VM),核心是如何实现 Model 和 View 的同步:
- MVC:Controller 控制 Model,Model 通知 View 更新;
- MVP:Presenter 作为中间层管控 Model 和 View,二者交互全部走 Presenter;
- MVVM:ViewModel 通过双向绑定,自动完成 Model 与 View 的同步。
框架实现具有多样性,并不严格教条遵守模式定义;哪些逻辑放在哪一层没有强制规定,由开发者结合业务自行选择。
React 官方定位它只是 MVC 里的 V(视图层);Vue 官方文档也说明,它没有完全实现标准 MVVM,只是受 MVVM 思想启发。
一句话概括:
- MVC:核心是 M、V、C 分层;
- MVP:核心是 M 和 V 彻底解耦;
- MVVM:核心是 M 和 V 的双向自动绑定。
React 和 Vue 有哪些差异?
React 与 Vue 是当下最主流两大前端视图库,有大量共同点:
- 都使用 Virtual DOM;
- 都支持响应式、组件化开发;
- 核心库只聚焦视图层,路由、全局状态管理交给第三方生态。
诞生
React:起源于 Facebook 内部项目,对市面上 MVC 框架不满意,自研用于 Instagram;2013-05 正式开源。
Vue:作者尤雨溪,曾就职于 Google。
- 2013:受 Angular 启发开发轻量版本,项目名 Seed;
- 2013-12-08:0.6.0,改名为 Vue;
- 2014-01-27:0.8.0,Vue 正式对外发布;
- 2014-02-25:0.9.0(代号 Animatrix),后续重要版本都会设置代号;
- 2015-06-13:0.12.0(代号 Dragon Ball),Laravel 社区开始大规模使用 Vue;
- 2015-10-27:1.0.0(代号 Evangelion)第一个里程碑;vue-router、vuex、vue-cli 陆续发布,从视图库进化成渐进式框架;
- 2016-10-01:2.0.0,第二个里程碑;吸收 React 虚拟 DOM,支持 SSR;Vue 正式跻身主流;
- 2019-02-05:2.6.0(代号 Macross)承上启下版本;
- 2019-12-05:Vue 3 alpha 源码公开;
- 2020-09-18:Vue 3.0 正式发布。
基本原理
Vue:把普通 JS 对象传入 Vue 实例的 data,Vue 遍历对象所有属性,把属性转为 getter、setter。Vue 2 使用 Object.defineProperty,Vue 3 改为 Proxy。每个组件实例对应一个 watcher;组件渲染过程中会收集用到的数据作为依赖;当 setter 触发变更,通知 watcher,触发组件重新渲染。
React:将 DOM 抽象为 JavaScript 对象,也就是虚拟 DOM。开发层面操作虚拟 DOM,通过 Diff 算法对比新旧虚拟 DOM 的差异,把差异转化为真实 DOM 的操作更新页面。
性能对比
React:组件 state 变更,会以当前组件为根,重新渲染整棵子组件树。state、props 改变,生成全新虚拟 DOM 树,Diff 两棵树,只更新差异部分。shouldComponentUpdate 生命周期用来手动控制是否执行渲染,返回 true 执行 render,false 跳过渲染,做性能优化。
Vue:Vue 在渲染时自动收集组件依赖,可以精确定位哪部分组件需要重新渲染。data 属性被劫持为 getter、setter,属性修改触发对应 watcher,只驱动依赖该数据的组件重新渲染。
注意:数据量巨大场景,Vue 会产生大量 watcher,也会造成性能压力。Vue 2 对比文档曾据此认为,超大规模复杂应用更偏向 React 的手动优化模型。
状态管理
React: 单向数据流。props 父传子,子组件禁止修改 props;state 属于组件内部,仅组件自身可修改;通过 setState 修改状态。
Vue: 支持双向绑定。View 修改同步 Model,Model 修改同步 View;data 内的数据被劫持,任意方式修改 data,都会触发组件更新。
推荐写法
React: All-in-JS,HTML、CSS 都融入 JavaScript。推荐使用 JSX,JSX 是 React.createElement 的语法糖。
/* component.jsx */
const contentColor = { color: 'red' }
class MyComponent extends React.Component {
state = {
content: '这是一个简单的JSX文件'
}
render() {
return <div style={contentColor}> {this.state.content} </div>
}
}
export default MyComponent
Vue: 拥抱传统 Web 开发习惯,推荐 .vue 单文件组件,HTML、JS、CSS 分离,模板是合法 HTML。Vue 也支持 render 函数、JSX,但不作为首选。
<!-- component.vue -->
<template>
<div class="contentColor"> {{content}} </div>
</template>
<script>
export default {
name: 'MyComponent',
data () {
return {
content: '这是一个简单的.vue模板'
}
}
}
</script>
<style>
.contentColor { color: red; }
</style>
对比小结:JSX 偏向 JavaScript 逻辑;.vue 模板更贴近 HTML 视图。
原生渲染 Native
Vue: 和阿里 Weex 合作;一套 Vue 语法代码,编译后运行 Web、iOS、Android。Weex 抹平平台差异,降低上手门槛,但是社区生态相对薄弱。
React: React Native;承认各平台差异,框架层抽象统一 API,平台可编写差异化代码;社区生态非常繁荣,第三方组件库丰富。
Native:使用平台原生 API 开发 App,iOS 用 OC、Swift,Android 用 Java、Kotlin。
Weex:一套代码多端抹平差异;
React Native:同一套框架,允许平台差异化实现。
服务端渲染 SSR
SSR 核心价值:利于 SEO,服务端组装完整 HTML,返回浏览器,首屏渲染更快。
React: Next.js,React 生态 SSR、静态站点框架。
Vue: Nuxt.js,对标 Next.js,Vue 生态 SSR 框架。
构建工具
React: 官方脚手架 create-react-app;默认配置封闭,修改 webpack 配置需要借助第三方方案;模板场景单一。
Vue: Vue CLI,内置多种模板,支持 webpack、browserify 甚至无构建模式;预处理、自定义配置需要自行配置。
总结
Vue 和 React 都只聚焦视图层,核心库功能精简,复杂能力依靠庞大生态(路由、状态管理等)按需引入。
框架只是工具,简单页面引入重型框架会增加包体积、开发成本;大型复杂业务场景,合适的框架可以显著提升开发效率。
React 中元素与组件有什么区别?
元素
React 最小基础单元。可以通过 JSX、React.createElement()、React.cloneElement() 创建。
const rElement = <div className="element">I'm element</div>
React.createElement(type, [props], [...children])
React.cloneElement(element, [props], [...children])
React Element 就是普通 JavaScript 对象,用来描述 UI 长什么样。
组件
组件是封装独立 UI 功能的可复用部件,概念类似 JS 函数,接收 props,返回 React Element。
三种组件定义方式:React.createClass()(已废弃)、ES6 class 组件、函数组件。
const ReactComponent = React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>
}
})
class ReactComponent extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>
}
}
function ReactComponent (props) {
return <h1>Hello, {props.name}</h1>
}
简单区分:组件是函数、class;元素是普通 JS 对象;组件执行之后返回 React 元素。
什么是渐进式框架?
渐进式:框架只提供核心最小能力,其余能力按需、逐步引入,适配从简单到复杂各类业务场景。
框架会带有自己的主张,主张强弱决定侵入性。
- Angular 是强主张框架,使用它就要接受它的模块、依赖注入、组件定义规则;很难和老项目做局部集成。
- React 主张偏向函数式编程;需要开发者理解副作用、纯函数等概念;属于软性侵入,可以局部嵌入已有项目。
渐进式框架特点:可以只使用它部分能力,不需要全盘改造整个系统;小项目简单使用,大型项目再追加路由、状态管理等方案。
什么时候使用 useMemo 和 useCallback?
useMemo、useCallback 目的:避免不必要的重复渲染。
默认规则:父组件重渲染,其所有子组件全部跟着重渲染,无论子组件的数据有没有发生变化。
function CountButton({onClick, count}) {
return <button onClick={onClick}>{count}</button>
}
function DualCounter() {
const [count1, setCount1] = React.useState(0)
const increment1 = () => setCount1(c => c + 1)
const [count2, setCount2] = React.useState(0)
const increment2 = () => setCount2(c => c + 1)
return (
<>
<CountButton count={count1} onClick={increment1} />
<CountButton count={count2} onClick={increment2} />
</>
)
}
点击任意按钮,父组件 DualCounter 更新,两个 CountButton 全部重渲染;其中一个按钮状态没变,依然执行渲染,就是不必要重渲染。父组件重渲染会重新创建函数、变量;若子组件做了浅比较(如 React.memo),新引用就会触发更新。
绝大多数场景不需要手动优化,React Diff 速度很快。只有渲染成本很高(复杂图表、动画组件),并且 props 值逻辑上没有改变,但引用变化造成子组件重复渲染,才需要使用。
注意:useCallback、useMemo 本身不会阻止子组件重渲染;要跳过子组件更新,需要再配合 React.memo(或 PureComponent)。前者稳定 props 引用,后者根据浅比较决定是否跳过渲染。
// useCallback(fn, deps) 等价于 useMemo(() => fn, deps)
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
区别:useMemo 返回函数执行结果;useCallback 返回函数本身;依赖数组不变,就返回上一次缓存结果。
其他问题
为什么 React 不同步地更新 this.state?
在同一个事件处理函数内多次调用 setState,React 会做批量合并,等待全部 setState 执行完成之后,再统一触发重渲染,减少重复渲染,提升性能。
不直接同步修改 this.state 有两点关键原因:
- 会破坏 props 和 state 的一致性,产生难以复现、难以调试的 bug;state 可以同步改,但 props 必须等到父组件重渲染才能拿到最新值;
- 同步修改 state 会造成多次渲染,页面闪烁。
注意:this.state 不会立刻变成最新值;更新后的状态,要在下一轮渲染周期才可以读取。
如何获取上一轮的 props 或 state?
使用 useRef,ref 的 .current 属性是可变容器,可以保存任意变量,模拟实例属性。
function MyComponent() {
const [count, setCount] = useState(0);
const prevStateRef = useRef()
useEffect(() => {
prevStateRef.current = count
});
const prevState = prevStateRef.current;
return <h1>Now: {count}, before: {prevState}</h1>
}
单向数据流和双向绑定各有什么优缺点?
单向数据流
优点:数据流向清晰可追踪,排错简单;
缺点:开发繁琐;修改 UI 状态,必须编写 action 修改 state。
双向绑定
优点:开发写代码简洁;
缺点:数据互相依赖;数据变更来源不明确,排错困难。
React.Component 和 React.PureComponent 的区别?
React.Component 没有内置 shouldComponentUpdate。
React.PureComponent 实现了 shouldComponentUpdate,做浅层比较 props 和 state;浅层相等则跳过渲染。
注意:当组件 props、state 是引用类型,浅层比较只会对比引用地址,不会深度对比对象内容,注意由此带来的坑。
自定义 Hook 必须以 “use” 开头吗?
必须以 use 开头。
这是 React 的强制约定。React 通过函数名前缀识别自定义 Hook;如果不使用 use 开头,React 的 lint 规则无法校验 Hook 调用规则(只能顶层调用、不能条件调用),容易产生隐藏 bug。