作为一名有原生 JavaScript 基础的前端工程师,学习 React 时最容易困惑的问题是:React 和 JavaScript 到底是什么关系?为什么同样是写页面,React 的写法和原生 JS 差别这么大?本文将从核心逻辑入手,通过对比的方式帮你理清两者的本质差异,为学习 React 打下扎实基础。
一、核心定位与本质差异
1. JavaScript:前端世界的“基础工具”
JavaScript 是一门编程语言,它的核心作用是为网页添加交互逻辑。简单来说,浏览器加载 HTML 和 CSS 后,JavaScript 可以:
- 操作页面元素(比如修改文字、隐藏图片)
- 处理用户行为(比如点击按钮、输入文字)
- 发送网络请求(比如加载后台数据)
- 处理数据(比如计算、筛选列表)
你可以把 JavaScript 理解为“建筑工人的扳手和锤子”,是实现各种功能的基础工具,没有固定的使用套路,灵活但需要手动设计实现逻辑。
2. React:基于 JavaScript 的“UI 开发框架”
React 是一个UI 库(也常被称为框架),它基于 JavaScript 语法,但专门用于解决“复杂页面的高效开发”问题。它的核心作用是:
- 把页面拆分成可复用的“组件”(比如导航栏、商品卡片)
- 通过“数据驱动”的方式自动更新页面(数据变了,页面自动跟着变)
- 简化复杂交互场景的开发(比如表单、列表、弹窗)
你可以把 React 理解为“建筑工人的预制板和施工图纸”——它基于基础工具(JavaScript),但提供了更高效的标准化开发模式。
3. 两者的核心关系
- React 不是替代 JavaScript 的新语言,而是用 JavaScript 写出来的工具库
- 学习 React 本质上是学习“用 React 的方式写 JavaScript”
- 所有 React 代码最终都会被转换成原生 JavaScript 代码在浏览器运行
举例理解:就像用乐高积木(React)和用原始塑料(JavaScript)造房子——乐高本质是塑料,但提前做好了标准化模块,组装效率更高。
二、编程范式对比
编程范式是指“写代码的思考方式”,这是 React 和原生 JavaScript 最核心的差异之一。
1. JavaScript:命令式编程为主
原生 JavaScript 操作页面时,通常用命令式编程:即详细描述“每一步该怎么做”。
比如要实现“点击按钮后,把列表里的偶数变成红色”,原生 JS 的思路是:
- 获取按钮元素
- 给按钮绑定点击事件
- 点击时获取列表所有元素
- 遍历每个元素,判断是否为偶数
- 对偶数元素设置红色样式
代码示例:
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<button id="btn">标红偶数</button>
<script>
// 1. 获取元素
const btn = document.getElementById('btn');
const list = document.getElementById('list');
// 2. 命令式描述步骤
btn.addEventListener('click', () => {
// 3. 遍历列表
const items = list.getElementsByTagName('li');
for (let i = 0; i < items.length; i++) {
const item = items[i];
const num = Number(item.innerText);
// 4. 判断并修改样式(命令式操作)
if (num % 2 === 0) {
item.style.color = 'red'; // 直接命令DOM修改
} else {
item.style.color = 'black';
}
}
});
</script>
这种方式的特点是:你必须清楚每一步操作的细节,并且手动控制 DOM 的变化。
2. React:声明式编程为主
React 采用声明式编程:只需要描述“最终要呈现的样子”,不用关心“具体怎么做”。
同样实现“点击按钮标红偶数”,React 的思路是:
- 定义一个“列表数据”(比如 [1,2,3])
- 定义一个“是否标红”的状态(比如 isRed)
- 描述 UI 结构:列表项的颜色由“是否为偶数且 isRed 为 true”决定
- 点击按钮时,只需要修改 isRed 状态(不用手动操作 DOM)
代码示例(React):
import { useState } from 'react';
function NumberList() {
// 1. 声明数据和状态(描述“是什么”)
const numbers = [1, 2, 3];
const [isRed, setIsRed] = useState(false);
// 2. 声明式描述UI(只关心最终样子)
return (
<div>
<ul>
{numbers.map((num) => (
<li
key={num}
style={{
color: isRed && num % 2 === 0 ? 'red' : 'black'
}}
>
{num}
</li>
))}
</ul>
<button onClick={() => setIsRed(true)}>
标红偶数
</button>
</div>
);
}
这里的核心差异:
- 你不用写“遍历 DOM 元素”“修改 style”这些步骤
- 只需要告诉 React:“当 isRed 为 true 时,偶数项是红色”
- React 会自动处理 DOM 更新的细节
3. 函数式编程的体现
React 大量使用函数式编程思想,这也是和原生 JS 不同的点:
- 纯函数组件:React 组件通常是函数,输入相同的“参数(Props)”,一定返回相同的 UI
- 无副作用:组件内部尽量不直接操作外部变量或 DOM(这些交给 React 处理)
- 数据不可变:修改状态时不直接修改原数据,而是返回新数据(比如用 setIsRed 替换直接修改 isRed)
三、DOM 操作机制对比
DOM 操作是前端开发的核心,但 React 和原生 JS 的处理方式截然不同,这也是 React 性能优势的关键。
1. JavaScript:直接操作真实 DOM
原生 JS 操作页面时,直接修改浏览器中的真实 DOM(文档对象模型)。比如:
// 直接修改真实DOM
const div = document.createElement('div');
div.innerText = 'Hello';
document.body.appendChild(div); // 立即渲染到页面
这种方式的问题在于:
- 性能消耗大:真实 DOM 是浏览器渲染引擎的一部分,每次操作都会触发页面“回流/重绘”(比如重新计算布局、重新绘制像素),频繁操作会导致页面卡顿
- 代码繁琐:需要手动管理 DOM 的创建、修改、删除,逻辑复杂时容易出错
- 状态同步难:数据变化后,需要手动找到对应的 DOM 元素并更新,容易出现“数据和页面不一致”的问题
举例:如果要更新一个列表中的 10 个元素,原生 JS 可能需要删除旧列表,再创建新列表,导致 10 次 DOM 操作和多次回流。
2. React:虚拟 DOM + Diff 算法
React 引入了虚拟 DOM(Virtual DOM)的概念,彻底改变了 DOM 操作方式。
什么是虚拟 DOM?
虚拟 DOM 是用 JavaScript 对象模拟的“DOM 树”。比如一个真实的 DOM 节点:
<div class="box">
<p>Hello</p>
</div>
对应的虚拟 DOM 可能是这样的对象:
{
type: 'div',
props: { className: 'box' },
children: [
{ type: 'p', props: {}, children: ['Hello'] }
]
}
React 的 DOM 操作流程:
- 初始渲染:React 根据组件代码生成虚拟 DOM,再把虚拟 DOM 转换成真实 DOM 渲染到页面
- 数据变化:当状态(比如 isRed)改变时,React 会生成一个新的虚拟 DOM
- Diff 算法对比:React 用 Diff 算法对比新旧虚拟 DOM 的差异(比如“只有第二个 li 的颜色变了”)
- 批量更新真实 DOM:只把差异部分转换成真实 DOM 操作,一次性更新到页面
优势:
- 减少真实 DOM 操作:通过虚拟 DOM 批量处理差异,原本 10 次 DOM 操作可能变成 1 次
- 提升性能:避免不必要的回流/重绘,尤其在复杂页面中效果明显
- 简化逻辑:开发者不用关心 DOM 操作细节,只需要维护数据状态
JSX 与虚拟 DOM 的关系
React 中写的 JSX 语法(比如 <div>Hello</div>),本质是创建虚拟 DOM 的“语法糖”。比如:
// JSX 代码
<div>Hello {name}</div>
// 会被编译成(创建虚拟DOM的函数)
React.createElement('div', null, 'Hello ', name);
这种语法让开发者可以像写 HTML 一样描述虚拟 DOM,比直接写 JavaScript 对象更直观。
四、状态管理逻辑对比
“状态”(State)是指页面中会变化的数据(比如表单输入、开关状态、列表数据)。管理状态是前端开发的核心任务,两者的处理方式差异极大。
1. JavaScript:无内置状态管理方案
原生 JS 没有统一的状态管理规则,通常用全局变量、闭包或自定义对象存储状态,并且需要手动同步到 DOM。
举例:实现一个计数器(点击按钮加 1)
<button id="btn">0</button>
<script>
// 用全局变量存储状态
let count = 0;
const btn = document.getElementById('btn');
// 点击时修改状态,然后手动更新DOM
btn.addEventListener('click', () => {
count++; // 修改状态
btn.innerText = count; // 手动同步到DOM
});
</script>
这种方式的问题在复杂应用中会被放大:
- 状态分散:状态可能存放在全局变量、函数内部、DOM 属性中,难以追踪
- 同步繁琐:每次状态变化都要手动找到对应的 DOM 元素更新,容易遗漏
- 数据流向混乱:多个地方可能修改同一个状态,出问题时难以调试
比如一个电商页面,商品数量、选中状态、总价计算等状态如果用原生 JS 管理,很容易出现“数量变了但总价没更新”的 bug。
2. React:内置状态管理 + 数据驱动
React 提供了专门的状态管理机制,核心思想是数据驱动视图:状态变化时,视图自动更新,不需要手动操作 DOM。
基础状态管理:useState 钩子
React 函数组件中用 useState 定义状态,用法如下:
import { useState } from 'react';
function Counter() {
// 定义状态:count 是当前值,setCount 是修改状态的函数
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count} {/* 直接使用状态,状态变了这里自动更新 */}
</button>
);
}
核心特点:
- 状态与视图绑定:
{count}会自动显示最新的状态值,不用手动写innerText - 状态修改有规范:必须用
setCount函数修改状态(不能直接写count++),这样 React 才能感知到状态变化并更新视图 - 局部状态:
count只属于Counter组件,不会污染全局
复杂状态管理:状态共享与数据流
当多个组件需要共享状态时(比如购物车数量在导航栏和详情页都要显示),React 提供了:
- Props 传递:父组件通过 Props 把状态传给子组件(单向数据流:父→子)
- Context API:跨组件共享状态(避免多层 Props 传递)
- 状态管理库:如 Redux、Zustand 等,适合大型应用
单向数据流是 React 的重要原则:数据只能从父组件流向子组件,子组件不能直接修改父组件的状态(只能通过父组件传递的函数间接修改)。这种规则让数据变化可追踪,减少了 bug。
五、事件处理机制对比
处理用户交互(点击、输入等)是前端开发的基础,React 对原生事件系统做了封装,用法更统一。
1. JavaScript:原生事件绑定
原生 JS 绑定事件的方式有多种,且存在浏览器兼容性问题:
// 方式1:HTML属性绑定
<button onclick="handleClick()">点击</button>
// 方式2:DOM元素绑定
const btn = document.getElementById('btn');
btn.onclick = handleClick; // 覆盖式绑定
// 方式3:事件监听(推荐)
btn.addEventListener('click', handleClick); // 可绑定多个函数
// 移除事件需手动解绑,否则可能内存泄漏
btn.removeEventListener('click', handleClick);
原生事件的问题:
- 写法不统一:有多种绑定方式,容易混淆
- 兼容性处理:比如 IE 浏览器用
attachEvent而非addEventListener - 事件对象差异:不同浏览器的事件对象(event)属性可能不同
- 需手动解绑:在组件销毁时如果忘了移除事件监听,可能导致内存泄漏
2. React:合成事件系统
React 封装了一套合成事件系统(SyntheticEvent),解决了原生事件的痛点。
基本用法:
function Button() {
// 事件处理函数
const handleClick = (e) => {
e.preventDefault(); // 阻止默认行为(和原生类似)
console.log('点击了');
};
return (
// 1. 驼峰命名(onClick 而非 onclick)
// 2. 绑定函数(而非字符串)
<button onClick={handleClick}>点击</button>
);
}
合成事件的优势:
- 跨浏览器兼容:React 内部处理了浏览器差异,不用写
if (IE) { ... } - 事件委托优化:所有 React 事件都委托到根节点处理,减少内存占用(不用给每个元素绑定事件)
- 自动解绑:组件卸载时,React 会自动移除事件监听,避免内存泄漏
- 统一事件对象:合成事件对象(e)在所有浏览器中具有相同的属性和方法(如
e.target、e.preventDefault())
注意点:
- React 事件名用驼峰命名(
onClick、onChange而非onclick、onchange) - 绑定的是函数引用(
onClick={handleClick}),不是字符串(onClick="handleClick()"是错误的) - 如果需要访问原生事件对象,可以用
e.nativeEvent
六、生命周期与执行流程对比
“生命周期”指的是组件从创建到销毁的整个过程。原生 JS 没有统一的生命周期概念,而 React 组件的生命周期非常明确。
1. JavaScript:无统一生命周期
原生 JS 实现一个“组件”(比如弹窗)时,需要手动管理从创建到销毁的过程:
// 模拟一个弹窗组件
class Modal {
constructor() {
this.element = null;
}
// 初始化(创建DOM)
init() {
this.element = document.createElement('div');
this.element.className = 'modal';
this.element.innerText = '弹窗内容';
}
// 显示(挂载到页面)
show() {
document.body.appendChild(this.element);
// 可能需要绑定事件
this.element.addEventListener('click', this.hide.bind(this));
}
// 隐藏(从页面移除)
hide() {
document.body.removeChild(this.element);
// 必须手动解绑事件,否则内存泄漏
this.element.removeEventListener('click', this.hide.bind(this));
}
}
// 使用
const modal = new Modal();
modal.init();
modal.show(); // 显示弹窗
// 一段时间后隐藏
setTimeout(() => modal.hide(), 3000);
这里的问题是:
- 生命周期方法(init、show、hide)需要自己设计,没有标准
- 容易遗漏关键步骤(比如忘记解绑事件)
- 复杂组件的生命周期逻辑会非常混乱
2. React:明确的生命周期管理
React 组件有严格定义的生命周期阶段,函数组件中主要通过 useEffect 钩子管理。
函数组件的生命周期(useEffect)
useEffect 可以模拟“组件挂载后”“组件更新后”“组件卸载前”等生命周期:
import { useState, useEffect } from 'react';
function Modal() {
const [isShow, setIsShow] = useState(false);
// 模拟“组件挂载后”和“isShow变化后”执行
useEffect(() => {
console.log('弹窗显示状态变化了');
// 如果显示弹窗,绑定点击事件
if (isShow) {
const handleClick = () => setIsShow(false);
document.addEventListener('click', handleClick);
// 模拟“组件卸载前”或“依赖变化前”执行(清理函数)
return () => {
document.removeEventListener('click', handleClick); // 自动解绑
};
}
}, [isShow]); // 依赖数组:只有isShow变化时才执行
return (
<button onClick={() => setIsShow(true)}>
显示弹窗
</button>
{isShow && <div className="modal">弹窗内容</div>}
);
}
useEffect 的核心逻辑:
- 第一个参数是“副作用函数”(比如绑定事件、发送请求)
- 第二个参数是“依赖数组”:只有依赖项变化时,才会重新执行副作用函数
- 副作用函数返回的“清理函数”:会在组件卸载前或依赖变化前执行(用于解绑事件、取消请求等)
这种机制让生命周期逻辑更清晰,且自动处理了清理工作(比如事件解绑),减少了内存泄漏风险。
七、核心优势与局限性对比
通过以上对比,我们可以总结出两者的适用场景和优缺点:
1. JavaScript 的核心特点
- 优势:
- 灵活性极高:没有固定规则,可按需实现任何逻辑
- 轻量无依赖:不需要额外引入库,直接在浏览器运行
- 学习成本低:掌握基础语法和 DOM API 即可开发简单功能
- 局限性:
- 复杂应用难维护:代码容易冗余,状态管理混乱
- 开发效率低:重复编写 DOM 操作和状态同步逻辑
- 性能优化难:需要手动处理 DOM 操作带来的性能问题
适用场景:简单页面、静态网站、交互逻辑较少的项目(如企业官网、营销页)。
2. React 的核心特点
- 优势:
- 组件化复用:页面拆分成组件,减少重复代码,提高开发效率
- 数据驱动视图:状态变化自动更新 UI,避免手动操作 DOM
- 性能更优:虚拟 DOM 和 Diff 算法减少不必要的 DOM 操作
- 生态完善:有路由(React Router)、状态管理(Redux)等配套工具
- 适合团队协作:组件化和固定范式让代码风格更统一
- 局限性:
- 学习成本高:需要理解 JSX、虚拟 DOM、Hooks 等新概念
- 配置复杂:需要构建工具(如 Webpack)打包,入门门槛较高
- 过度封装:简单场景下显得冗余(比如一个按钮点击用 React 反而麻烦)
适用场景:中大型项目、交互复杂的单页应用(如后台管理系统、电商平台、社交应用)。
总结
React 不是对 JavaScript 的否定,而是对 JavaScript 在 UI 开发场景下的“增强”。它保留了 JavaScript 的核心语法,但通过组件化、声明式编程、虚拟 DOM 等机制,解决了原生 JS 在复杂应用开发中的痛点。
作为初学者,建议从“数据驱动”和“组件化”这两个核心思想入手,对比原生 JS 的命令式写法,逐步理解 React 的设计理念。后续学习中,你会发现 React 虽然有一定学习成本,但掌握后能极大提升复杂项目的开发效率和可维护性。
&spm=1001.2101.3001.5002&articleId=153696876&d=1&t=3&u=de47b95010084fa3a6fa8e87dec4d0e8)
2378

被折叠的 条评论
为什么被折叠?



