你必须知道的 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(控制器):连接模型与视图,处理事件、调度整个业务流程。

mvc

交互流程:

  • 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 的刷新。

MVP

交互流程:

  • 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 通过双向绑定减少模板代码,提升开发效率。

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?

useMemouseCallback 目的:避免不必要的重复渲染

默认规则:父组件重渲染,其所有子组件全部跟着重渲染,无论子组件的数据有没有发生变化

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 值逻辑上没有改变,但引用变化造成子组件重复渲染,才需要使用。

注意useCallbackuseMemo 本身不会阻止子组件重渲染;要跳过子组件更新,需要再配合 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。

参考资料

React 中文文档

Vue.js 对比其他框架

© lizhao all right reserved,powered by Gitbook文件修订时间: 2026-08-24 21:29:07

results matching ""

    No results matching ""