news 2026/9/8 11:10:04

React零基础入门:从JSX到组件与Hooks核心知识

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
React零基础入门:从JSX到组件与Hooks核心知识

1. 项目概述:为什么我建议你这样入门 React

在正式开始之前,先明确一个事实:React 不是一个框架,它是一个用于构建用户界面的 JavaScript 库。这句话听起来简单,但无数新人恰恰是因为没理解这一点,才会在上手阶段反复碰壁——你遇到的很多问题,其实是 JavaScript 本身的问题,而不是 React 的问题。零基础小白最容易犯的错,就是跳过 JS 基础直接冲进 React,结果连mapfilter、解构赋值、展开运算符都还没摸透,就跑去写组件,最后被一堆报错折磨得怀疑人生。

这篇入门知识点覆盖的范围,是从安装环境开始,到 JSX、组件、Props、State、事件处理、表单、生命周期、Hooks、路由、状态管理,再到和 Vue 的差异对比、React Native 延伸方向,以及面试中真正会被问到的底层问题。我会用自己踩过的坑、在真实项目里积累的经验,把这条学习路径完整串起来。

适合谁看?零基础想系统学 React 的新人、被公司要求从 Vue 转 React 但一直没理的开发者、准备前端面试想快速梳理知识体系的人。看完这篇,不敢说你直接进大厂,但至少对着 React 项目不会两眼一抹黑,面试常规题也有底气答了。

2. 环境准备与工具选型:先把地基打牢

2.1 Node.js 版本怎么选,别在最开始就卡住

React 开发完全跑在 Node.js 生态上。你写的组件代码最终要靠构建工具打包,才能被浏览器识别,而构建工具本身就依赖 Node.js 环境。我的建议是:装 Node.js 最新的 LTS 版本,别追最新版,也别死守老版本。LTS 版本稳定、生态兼容性好,遇到问题搜解决方案时成功率最高。

安装完成后,打开终端输入node -vnpm -v,能看到版本号说明环境就绪了。这里有 Windows 用户经常踩的一个坑:安装包装完后,终端还是提示“node 不是内部或外部命令”,原因多半是环境变量没生效,重启终端甚至重启电脑就能解决,不用急着折腾 PATH。

国内开发者建议顺手把 npm 镜像切到淘宝源:

npm config set registry https://registry.npmmirror.com

用的是 npx create-react-app 或 vite,国内源下载依赖的速度差距非常明显,实测能差出好几分钟。

2.2 用 Vite 还是 Create React App,我推荐你直接用 Vite

传统教程还在带新人用create-react-app(简称 CRA),但对 2024 年的新项目,我强烈建议直接用Vite。原因很直接:CRA 底层是 webpack,开发服务器启动要 20-60 秒,热更新也慢,你改一行代码可能要等好几秒才刷新;Vite 基于 esbuild,启动速度基本在 1-2 秒内,热更新几乎是毫秒级响应。对新手来说,越快的反馈意味着越高的学习效率。

创建项目只需要一行命令:

npm create vite@latest my-first-react-app -- --template react

然后:

cd my-first-react-app npm install npm run dev

浏览器打开终端提示的地址(一般是http://localhost:5173),就能看到你的第一个 React 页面。打开项目目录,你会看到src/App.jsxsrc/main.jsx这几个关键文件,App.jsx是你之后主要折腾的地方,main.jsx负责把根组件挂载到 HTML 页面上。

2.3 编辑器配置:VS Code 必装插件清单

VS Code 是 React 开发事实上的标准编辑器。必装插件三个:ES7+ React/Redux/React-Native snippets(输入rfc可以秒生成函数组件模板)、ESLint(规范代码、报错提示)、Prettier(自动格式化)。装完后新建一个.prettierrc配置文件:

{ "semi": false, "singleQuote": true, "trailingComma": "all" }

这三行配置的意思是结尾不加分号、字符串用单引号、多行结构最后加逗号,这是目前 React 项目最主流的代码风格。设置好后,保存文件时代码会自动整理,你只管写逻辑,格式交给工具处理。

3. JSX 语法:用写 HTML 的方式写 JavaScript

3.1 JSX 到底是什么,它和模板引擎的区别在哪

JSX 是 React 设计上最聪明也最容易让人误会的点。它看起来像 HTML,但本质上它就是一个 JavaScript 表达式。你写<div className="box">Hello</div>,经过 Babel 或 esbuild 编译后,会变成React.createElement("div", { className: "box" }, "Hello")这样一个函数调用。

很多从 Vue 转来的人习惯用v-ifv-for、插值表达式那套模板语法,在 JSX 里全都不是这样搞。JSX 里没有指令,你不是在“模板渲染”,你是在用函数组合的方式构建 UI 结构。理解这个转变至关重要,它会直接影响你写 React 的思路。

JSX 有三大纪律:

  • 只能有一个根元素。多个兄弟元素必须包在一个<div><>(Fragment 碎片)里。
  • 所有标签必须闭合。<img>要写成<img /><input>要写成<input />
  • class不能用,要用classNamefor不能用,要用htmlFor。原因很简单:classfor在 JavaScript 里是保留字,JSX 是 JS 的语法扩展,命名冲突避不开。

3.2 在 JSX 里写表达式:花括号的四边形法则

JSX 里想插入变量或表达式,必须用一对花括号{}包起来。比如写个动态标题:

const username = '张三' const isVIP = true function Greeting() { return ( <div> <h1>你好,{username}</h1> <span>{isVIP ? '尊贵的VIP用户' : '普通用户'}</span> </div> ) }

注意几个规律:{}里可以是变量、算术运算、三元表达式、函数调用,但不可以放 if 语句、for 循环,因为它们是语句,不是表达式。想条件判断,用三元运算符或者&&短路运算;想循环渲染,用数组的map方法。

我在真实项目中经常看到新同事写类似{isVIP && <span>VIP标识</span>}这样的代码,这就是 React 社区最经典的&&短路条件渲染写法。当isVIPtrue时,&&返回后面的 JSX 元素;为false时,返回false,React 会忽略不渲染它。

3.3 样式绑定与图片资源引入

JSX 里写行内样式,和 HTML 里完全不是一回事:

<div style={{ color: '#333', fontSize: '16px', backgroundColor: '#f5f5f5' }}> 样式对象 </div>

外层大括号表示 JSX 表达式,内层大括号是对象字面量。注意属性名要改成驼峰命名法:background-color写成backgroundColorfont-size写成fontSize

图片资源的引入也有讲究。public目录下的资源直接用绝对路径引用,src目录下的图片要import进来再使用,否则打包后路径会出错。推荐把组件相关资源放在组件目录下用import引入,这样 Webpack 或 Vite 会对它做哈希处理,缓存策略更好。

4. 组件体系:函数组件是主角,类组件是配角

4.1 函数组件与类组件:为什么新时代只推荐函数组件

React 组件有两个时代的产品:类组件和函数组件。类组件基于 ES6 的 class 语法:

import React, { Component } from 'react' class Counter extends Component { constructor(props) { super(props) this.state = { count: 0 } } increment = () => { this.setState({ count: this.state.count + 1 }) } render() { return ( <div> <p>当前计数:{this.state.count}</p> <button onClick={this.increment}>+1</button> </div> ) } } export default Counter

函数组件则简洁得多:

import { useState } from 'react' function Counter() { const [count, setCount] = useState(0) return ( <div> <p>当前计数:{count}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div> ) } export default Counter

结论毫无悬念:新项目一律写函数组件 + Hooks。函数组件代码量更少、逻辑更集中、没有this指向问题、Hooks 机制比生命周期函数更易理解。类组件你只需要能看懂老项目的代码就够了,面试被问到了不至于懵。

4.2 Props 与 State:组件数据流的两个核心角色

Props 是父组件传给子组件的数据,子组件不能修改 props,它只读。State 是组件自己内部的数据,通过useState定义,通过setState函数修改。

当一个组件接收 props 时,函数组件的第一个参数就是 props 对象:

function UserCard({ name, age, avatar }) { return ( <div className="user-card"> <img src={avatar} alt={name} /> <p>{name},{age}岁</p> </div> ) } // 使用 <UserCard name="李四" age="25" avatar="/images/default.png" />

props 的默认值和类型校验,用defaultPropsprop-types来定义:

import PropTypes from 'prop-types' function UserCard({ name, age }) { // ... } UserCard.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number } UserCard.defaultProps = { age: 18 }

我给新人的建议是:项目里一定要写 prop-types。虽然 TypeScript 更严格更彻底,但零基础阶段引入 TS 会显著拉高学习曲线,先用 prop-types 养成类型意识,之后再平滑过渡到 TS。

4.3 组件拆分的原则:一个组件只干一件事

新手阶段最常见的病就是把所有 UI 塞进一个巨大的组件里,一个render里写了十几个区块的 JSX。这里我强烈建议你记住这条原则:当组件超过 200 行或 JSX 超过 50 行时,就应该拆分子组件

一个拆分方案参考:

  • 页面容器组件(负责数据请求和业务逻辑)
  • 纯展示组件(只接收 props 渲染 UI,不关心数据从哪来)
  • 公共基础组件(按钮、输入框、弹窗这类可复用的 UI)

拆分的实际收益在于:代码可读性提升、组件可复用、出问题时定位更快。在真实项目里,我见过 500 多行一个组件最后三天没人愿意改的情况——拆组件的功夫根本比不上维护的代价。

5. 事件处理与状态更新:React 运行机制的核心

5.1 事件绑定怎么用,参数怎么传

React 事件有一套自己的命名系统,都用驼峰写法并指向合成事件:onClickonChangeonSubmitonMouseEnter。事件绑定依旧是你熟悉的函数处理方式:

function Button() { const handleClick = (e) => { e.preventDefault() console.log('点击了按钮,事件对象是:', e) } return <button onClick={handleClick}>点我</button> }

如果函数体只有一行,可以简写成箭头函数的方式。传参的话,直接套一层箭头函数是最通俗的做法:

<button onClick={() => handleDelete(user.id)}>删除</button>

这里有个很多新人容易犯的错误:onClick={handleDelete(user.id)},在赋值时就先执行了函数,点击操作反而不会触发任何东西。正确做法永远是用箭头函数包一层。

5.2 setState 是异步的,别急着读最新值

setState是一个异步动作,调用后 state 不是立刻更新的。React 内部会对多个setState进行批处理,然后一次性聚合成一次渲染。经典案例:

const [count, setCount] = useState(0) const handleClick = () => { setCount(count + 1) setCount(count + 1) setCount(count + 1) console.log(count) // 还是 0,而且 count 最终只加了 1 }

连续调用三次setCount(count + 1),count 最后只加了一次,因为每次拿到的都是旧值。如果想基于最新状态更新,必须用函数式更新:

setCount(prevCount => prevCount + 1) setCount(prevCount => prevCount + 1) setCount(prevCount => prevCount + 1)

这样每次都在上一次更新结果的基础上继续叠加,最终结果就正确了。这是 React 面试高频题,也是实际开发中会遇到的坑。我在团队里见到过因为没搞懂这点,导致购物车数量重复相加只加一次的 bug。

5.3 状态更新后页面没变?大概率是引用没变

React 判断状态是否变化,用的是Object.is比较,简单理解就是比较引用地址。因此,修改对象和数组时,绝对不能直接修改原值,必须创建新的引用

// 错误示范 const [user, setUser] = useState({ name: '张三', age: 20 }) user.age = 21 setUser(user) // 渲染不会更新,因为引用地址没变 // 正确写法 setUser({ ...user, age: 21 })

数组也是同理:

// 错误示范 const [list, setList] = useState([1, 2, 3]) list.push(4) setList(list) // 不生效 // 正确写法:展开创建新数组 setList([...list, 4]) // 或者用 concat setList(list.concat(4))

不可变数据是 React 社区最核心的开发哲学,所有状态更新都要以创建新副本为前提。这个理念刚开始会觉得麻烦,但习惯后会极大减少 bug 的产生,也为后面学习 Redux 打下了基础。

6. 条件渲染与列表渲染:业务开发中最高频的两种模式

6.1 四种条件渲染方式,哪一种该用于什么场景

React 没有像 Vue 那样的v-if,条件渲染靠 JavaScript 本身的能力完成。常见有四种写法:

  • 三元运算符:{isLogin ? <HomePage /> : <LoginPage />},适合两个分支的选择。
  • 逻辑与&&{isVIP && <VipBadge />},适合只渲染一个分支、没有 else 需求时。
  • 逻辑或||{data || <Loading />},数据没拿到时显示加载组件,适合做兜底。
  • 立即调用函数:{(() => { if (...) return ... })()},适合复杂多分条件逻辑,但代码可读性差,业务里一般会抽成函数组件。

在实际项目中,优先避免把太多条件嵌套在 JSX 里,可以提前定义变量:

function Dashboard({ user }) { let content if (!user) { content = <LoginPrompt /> } else if (user.isAdmin) { content = <AdminPanel /> } else { content = <UserPanel /> } return <div>{content}</div> }

这样的代码线性可读,别人才看得懂,自己也看得懂。

6.2 map 渲染列表,key 是什么,为什么不能只用索引

列表渲染是前端业务的重头戏。React 里用map函数把数组转换成 JSX 数组:

const fruits = ['苹果', '香蕉', '橘子'] function FruitList() { return ( <ul> {fruits.map((fruit, index) => ( <li key={index}>{fruit}</li> ))} </ul> ) }

这里 key 是 React 的 diff 算法比对新旧列表时的标识,直白说就是 React 靠 key 判断哪些列表项是新增、修改、删除的。用数组索引当 key 在大多数情况下是有隐患的。假设列表可以排序、删除、新增,索引会变化,React 可能把原本的 DOM 节点复用到错误的组件上,导致状态错乱。

我用一个经典例子解释:如果你有一个可删除的输入框列表,第一行输入了“hello”,删除第二行后,第一行的输入框会被复用给原来第二行的位置,但它的状态还是“hello”,界面就出 bug 了。

正确做法是使用唯一标识:

const items = [ { id: 'a1', text: '苹果' }, { id: 'b2', text: '香蕉' } ] {items.map(item => ( <Item key={item.id} text={item.text} /> ))}

后端返回的数据如果有 id 字段,直接用;没有的话就考虑数据是否真的会变化。如果列表是纯静态的,用索引也无伤大雅,但最佳实践仍然是给每条数据维护一个唯一标识。

6.3 列表渲染中 key 和 diff 算法的关系,面试必问

React 的 diff 算法基于同级比较,逐层对比两个虚拟 DOM 树的差异,然后只更新变化的部分。List 里 key 的作用就是给 diff 一个“稳定的锚点”。

我给你一个面试时可以用的解释框架:React 在做列表 diff 时,会先对比 key 是否一致。key 相同就认为元素不变,直接复用;key 变了就卸载旧元素,创建新元素。如果没有唯一的 key,diff 就只能按位置对比,一旦数组顺序变化,React 会误判为所有元素都变了,要么多创建多余 DOM,要么把子组件内部状态复用到错误对象上。

7. 表单处理:受控组件与非受控组件

7.1 受控组件模式:React 管住输入框

在 React 中就两种表单模式,受控和非受控。受控组件的核心是:表单元素的值由 state 控制,用户在输入框里输入时,通过 onChange 事件更新 state,state 变化再反映回表单元素的 value。数据双向闭环,组件状态永远与页面显示保持一致。

import { useState } from 'react' function LoginForm() { const [form, setForm] = useState({ email: '', password: '' }) const handleChange = (e) => { const { name, value } = e.target setForm(prev => ({ ...prev, [name]: value })) } const handleSubmit = (e) => { e.preventDefault() console.log('提交的数据是:', form) // 这里写发送请求的逻辑 } return ( <form onSubmit={handleSubmit}> <input name="email" type="email" value={form.email} onChange={handleChange} placeholder="邮箱" /> <input name="password" type="password" value={form.password} onChange={handleChange} placeholder="密码" /> <button type="submit">登录</button> </form> ) }

上面用name属性 + 计算属性名的方式,一套handleChange处理所有输入框。这是真实项目里最常用的表单处理方式,代码复用度高,不用每个输入框写一个独立的 onChange。

受控组件的好处是:数据实时掌握在 React 手里,方便做实时校验、联动逻辑(比如一个下拉选择影响另一个区域的内容显示)、拦截输入格式。代价是每次击键都会触发一次 setState 和重新渲染,性能上有轻微开销,但对于绝大多数业务场景,这种开销可以忽略。

7.2 非受控组件:用 ref 直接拿 DOM 值

非受控组件的思路是:表单元素自己维护内部状态,需要的时候用 ref 直接读取 DOM 的 value。

import { useRef } from 'react' function SimpleForm() { const inputRef = useRef(null) const handleSubmit = (e) => { e.preventDefault() console.log('输入的值是:', inputRef.current.value) } return ( <form onSubmit={handleSubmit}> <input ref={inputRef} type="text" placeholder="说点什么" /> <button type="submit">提交</button> </form> ) }

什么时候用非受控组件?文件上传控件input type="file"的值是只读的,没法走受控模式;一些第三方库(比如富文本编辑器)内置了自己的状态管理,你强行控制 value 反而会出问题。除此之外,我绝大多数场景都建议用受控模式,那样数据链路清晰、调试方便。

7.3 表单验证的两个实操细节

一是不要只依赖 onChange 做必填校验,那会导致用户啥都没做就开始标红,体验很差;正确的交互节奏是:首次提交时把所有错误一次性显示出来,之后每次输入变更都重新校验,输入正确后错误提示自动消失。

二是提交后要重置表单。受控组件下重置很简单:

setForm({ email: '', password: '' })

二是不用单独调用 DOM 的 reset 方法,因为 value 完全由 state 控制,state 清空了,输入框里的内容自然就清空了。

8. 生命周期与 Hooks:从“何时触发”到“想触发就触发”

8.1 函数组件没有生命周期,但有 useEffect

类组件的生命周期有componentDidMountcomponentDidUpdatecomponentWillUnmount三个阶段。函数组件里,这些全部由useEffect一个 Hook 接管。你不再需要记忆生命周期方法的名称,只需要理解:“当某个依赖变化时,我想做什么”

useEffect 的四个使用场景:

// 1. 组件挂载后执行一次(模拟 componentDidMount) useEffect(() => { fetchData() }, []) // 2. 依赖变化后执行(模拟 componentDidUpdate 的部分场景) useEffect(() => { document.title = `你有 ${count} 条新消息` }, [count]) // 3. 组件卸载时清理(模拟 componentWillUnmount) useEffect(() => { const timer = setInterval(() => { console.log('定时任务执行中') }, 1000) return () => { clearInterval(timer) console.log('组件卸载,清理定时器') } }, []) // 4. 每次渲染都执行(不传依赖数组) useEffect(() => { console.log('组件每次渲染后都会执行') })

第二和第三个场景是实际开发中用途最频繁的:发送网络请求、订阅事件、操作浏览器 API、启动定时器、设置监听器、清理定时器和取消订阅。

8.2 useEffect 的依赖数组:最重要的一个配置项

依赖数组是 useEffect 的第二个参数,它决定了 effect 在什么时候执行。三种写法对应三种行为:

  • 不传依赖数组:每次渲染后都执行。
  • 传空数组[]:只在组件第一次挂载后执行一次。
  • 传入变量数组[count, user]:任一依赖变化后执行。

翻阅真实项目代码时,最多的 bug 往往出在依赖数组上。要么漏了依赖导致闭包捕获旧值,要么依赖过多导致函数频繁触发。这里我给你三条实操经验:

第一,网络请求类的 effect 依赖数组选空数组,意思就是只在挂载时请求一次。

第二,useEffect 里如果用了外部变量,尽量把它加进依赖数组,否则前端工具的开发模式下也会警告提示你闭包过期值问题。

第三,获取最新值时不要试图绕过依赖数组,用useRef保存最新值,或者把逻辑拆分到多个 effect 里。

8.3 必学的 Hooks 全家桶:useState、useEffect、useRef、useMemo、useCallback

函数组件的动力系统就是 Hooks,掌握下面这几个就足够应付至少九成业务需求。

useState:给函数组件添加局部状态,前面已经用过多次,不赘述。

useRef:保存一个可变值,改变它不会触发重新渲染,同时可以访问 DOM 节点。

const timerRef = useRef(null) const startTimer = () => { timerRef.current = setInterval(() => { console.log('运行中') }, 1000) } const stopTimer = () => { clearInterval(timerRef.current) }

useMemo:缓存计算结果。当依赖不变时,多次渲染直接从缓存读取,而不是重新计算。适合复杂数据处理场景。

const totalPrice = useMemo(() => { return products.reduce((sum, p) => sum + p.price * p.count, 0) }, [products])

useCallback:缓存函数本身。当函数被传给子组件时,父组件重新渲染不会导致子组件重复接收新的函数引用,避免不必要的子组件重渲染。

const handleSave = useCallback(() => { saveData(form) }, [form])

我给新人的建议是:不要一开始就滥用性能优化 Hooks。先写清晰的普通函数,出现性能问题时再针对性地用 useMemo 和 useCallback,否则只会把代码变得复杂难读。

8.4 自定义 Hook:把逻辑抽出来复用

Hook 可以封装到独立函数里供多个组件复用,这就是自定义 Hook。命名必须以use开头,这样 React 才能识别它是一个 Hook,允许在内部调用其他 Hook。

举个例子,封装一个窗口宽度监听的 Hook:

import { useState, useEffect } from 'react' function useWindowWidth() { const [width, setWidth] = useState(window.innerWidth) useEffect(() => { const handleResize = () => setWidth(window.innerWidth) window.addEventListener('resize', handleResize) return () => window.removeEventListener('resize', handleResize) }, []) return width } // 组件中使用 function Layout() { const width = useWindowWidth() const isMobile = width < 768 return ( <div> {isMobile ? <MobileMenu /> : <DesktopSidebar />} </div> ) }

自定义 Hook 让逻辑复用变得异常优雅,组件间的公共逻辑(请求数据、表单状态、鉴权、主题切换)全都可以封装成独立 Hook。这也是面试中区分初级和中级工程师的一个考察点。

9. 组件通信与状态管理:当数据不再局限于一个组件内

9.1 单向数据流与状态提升

React 的数据流是自顶向下的单向数据流。父组件通过 props 把数据传给子组件,子组件通过回调函数把事件和数据传给父组件。这条链路看起来简单,但新手经常会滥用。

当多个兄弟组件需要共享同一个状态时,最简单的方案是状态提升:把公共状态放到最近的祖先组件中,由祖先通过 props 把这个状态分发给各个子组件,子组件修改数据的请求也通过回调传递回祖先组件。

function Parent() { const [activeTab, setActiveTab] = useState('home') return ( <div> <TabNav activeTab={activeTab} onTabChange={setActiveTab} /> <TabContent activeTab={activeTab} /> </div> ) }

这种模式的优点是数据来源单一、可预测性强、调试方便;缺点是组件层级深时,“prop drilling”问题会出现——中间层组件不需要这个数据,但被迫接过 props 再传给更下层。

9.2 跨层级通信:Context 一把梭

跨越多层组件传递 props,层级一深就变得很折磨。React 的 Context API 能直接解决这个问题,它可以在组件树中“注入”数据,任何层级的子组件都能直接读取,不需要逐层透传。

import { createContext, useContext, useState } from 'react' const ThemeContext = createContext('light') function App() { const [theme, setTheme] = useState('light') return ( <ThemeContext.Provider value={{ theme, setTheme }}> <Header /> <Content /> <Footer /> </ThemeContext.Provider> ) } function Header() { const { theme, setTheme } = useContext(ThemeContext) return ( <div className={`header ${theme}`}> <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> 切换主题 </button> </div> ) }

Context 适合全局性、低频变化的数据:主题、用户登录信息、语言偏好、权限配置。但它也有缺点——Context 变化时,所有消费这个 context 的组件都会重新渲染,如果数据结构很大、更新太频繁,会有性能隐患。所以大的全局状态管理,我更推荐下面要说的状态管理库。

9.3 状态管理库:Redux 还在用吗?要不要学 zustand

React 状态管理的演进速度很快。老牌王者Redux依然是大厂项目的标配,但 Redux Toolkit 的引入已经大大简化了原来的模板代码。红遍社区的Zustand则以极简的 API 和天然的 hook 集成为特征,受到了新兴项目的偏爱。

我结合实际使用感受给你做个对比:

对比维度Redux ToolkitZustand
学习曲线较陡,需要理解 store、reducer、action、dispatch平缓,接近 React state 的使用方式
模板代码量较多,需要约定的文件结构极少,几乎零模板
调试工具Redux DevTools 非常成熟有,但生态相对逊色
适合场景大型应用、复杂业务逻辑、团队协作中小型项目、追求快速开发

Zustand 的用法简单到看一眼就会:

import { create } from 'zustand' const useCounterStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })) })) function Counter() { const { count, increment, decrement } = useCounterStore() return ( <div> <button onClick={decrement}>-</button> <span>{count}</span> <button onClick={increment}>+</button> </div> ) }

我给新人的学习建议:先熟练管理组件局部 state,再掌握 Context,最后按需引入状态管理库。不要一上来就套 Redux,绝大多数场景根本用不到。

10. 路由、网络请求与工程化:React 应用的完整拼图

10.1 React Router 的路由体系,与 Vue Router 差异在哪

单页应用(SPA)无法离开路由而存在。React 生态主流的路由库就是React Router(当前最新为 v6)。基本用法:

import { BrowserRouter, Routes, Route, Link, useNavigate } from 'react-router-dom' function App() { return ( <BrowserRouter> <nav> <Link to="/">首页</Link> <Link to="/about">关于</Link> <Link to="/users/123">用户详情</Link> </nav> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/about" element={<AboutPage />} /> <Route path="/users/:id" element={<UserDetail />} /> </Routes> </BrowserRouter> ) } // 在组件中跳转 function HomePage() { const navigate = useNavigate() const goToAbout = () => navigate('/about') return <button onClick={goToAbout}>去关于页</button> }

对比 Vue Router,React Router v6 的核心差异有三点:第一,Vue Router 的路由配置是配置文件内置的<router-view>,React Router 则是组件化路由,路由就是 JSX 的一部分;第二,Vue Router 的导航守卫是内置功能,React Router 里要通过useEffect或封装高阶组件实现类似的用户鉴权逻辑;第三,动态路由参数的获取方式不同,Vue 里是route.params.id,React 里是useParams()函数解构出来。这几点在面试中被问的概率相当高。

在业务场景下,如果项目本身就是 React 技术栈,我建议直接使用 React Router,没必要为了“类比熟悉”强行切换框架。

10.2 网络请求:fetch 还是 axios

React 本身不含 HTTP 请求库,需要自己接,常用选择有两个。浏览器的原生fetchAPI 够用但封装程度低:

const loadData = async () => { try { const response = await fetch('/api/users') if (!response.ok) { throw new Error(`请求失败:${response.status}`) } const data = await response.json() setList(data) } catch (err) { console.error('请求出错:', err) } }

axios则是社区最主流的解决方案,它具有浏览器兼容性好、支持请求拦截器、响应拦截器、自动 JSON 转换、取消请求等特性。配合请求/响应拦截器,可以在一个地方统一处理 token 携带、错误提示、统一 loading 控制。

import axios from 'axios' const api = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:自动加 token api.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) // 响应拦截器:统一处理错误 api.interceptors.response.use( response => response.data, error => { if (error.response?.status === 401) { // 跳转登录页 } return Promise.reject(error) } )

我给新人的建议:先学会用 fetch 理解原理,项目开发时直接用 axios,效率更高。

10.3 从零到一:一个真实 React 项目的完整搭建流程

把前面所有内容串起来,新手完成一个简单管理后台的流程是:

  1. 使用 Vite 初始化项目,安装依赖。
  2. 安装react-router-domaxioszustand(如果业务复杂度需要)。
  3. 按路由划分页面组件,建立pagescomponentshooksutilsservices等目录结构。
  4. services目录封装 API 请求函数,统一管理接口。
  5. 开发公共组件(按钮、表格、弹窗、表单),再开发页面组件。
  6. 在页面组件中使用自定义 Hook 封装数据获取、loading、错误处理逻辑。
  7. 用路由配置串联页面,配合懒加载React.lazy(() => import(...))做代码分割。
  8. 开发阶段用 mock 数据,联调时切换 API 地址。

一条实用经验:在前端项目里,目录结构即是团队的规范。新人接手项目时,先看目录结构就能猜出这个团队的水平和代码组织风格。我习惯的最小目录结构是:

src/ ├── api/ # 接口请求 ├── components/ # 全局公共组件 ├── hooks/ # 公共自定义 Hook ├── pages/ # 页面级组件 ├── store/ # 全局状态 ├── utils/ # 公共工具方法 ├── App.jsx # 路由配置入口 └── main.jsx # 应用启动入口

11. React 与 Vue 的选型对比:别再纠结了

新手问得最多的问题就是“React 和 Vue 到底学哪个”,许多人在选择阶段就卡住了。我从技术思想和业务场景两个维度给一个务实的答案。

技术上,Vue 的核心理念是“渐进式”和“模板语法”,模板更贴近传统 HTML,适合新人快速上手做出界面;React 的核心理念是“一切皆 JavaScript”,UI 只是数据映射出来的函数结果。React 写法的 JavaScript 特性更强,但要求开发者本身对 JS 基础有更扎实的掌握。从状态管理、组件拆分到路由实现,React 给出的“自由度”更高,这意味着你有了更多选择权,也意味着你要自己做出更多决策。

业务上,如果是中小型项目、展示型页面、后台可视化,Vue 开发效率确实更高;如果是大型团队、长期维护的复杂应用、对组件抽象和逻辑复用要求高,React 的生态优势和工程化成熟度更明显。

我个人的建议很简单:你接下来要去投递的公司主要用什么技术栈,就主攻什么。都还是学生或者没目标,那就选 React。原因是 React 的生态体系更大、就业市场更广、周边工具的学习路径也自然延伸到了 React Native 等方向,一门语言的触角更宽。

12. React Native:同一套思想,延伸到移动端

React 的知识并不仅限于 Web 端。React Native允许你用 JavaScript 和 React 的组件模型,开发 iOS 和 Android 原生应用。它的价值在于:Web 前端工程师可以复用 React 的知识和技能,直接进入移动端开发领域。

React Native 的核心是桥接机制:JavaScript 层运行着 React 逻辑,通过桥层与原生 UI 组件通信。你在 JS 里写<View><Text>,最终会映射成原生平台上的 UIView 或 Android View,因此 App 的外观和交互感受接近原生应用,而不是一个网页套壳。

对零基础小白的建议:先问 Web 端 React 是否理解透彻了,再考虑学习 React Native。否则两组概念交叉在一起——Web 的 DOM 模型和 React Native 的原生组件模型完全不是一回事,容易崩溃。常用的启动白屏问题也与此有关:原生层面首先要加载 JavaScriptBundle,这段时间 UI 是空白的,需要做启动屏优化。

13. 面试高频题与新手常见坑,一次补齐

React 面试几乎绕不开以下核心问题,我按类别整理出来并附上答题思路。

13.1 面试题速查表

问题答题要点
什么是虚拟 DOM用 JavaScript 对象描述真实 DOM 结构,操作时先改虚拟 DOM,通过 diff 更新最小变更部分,减少不必要的真实 DOM 操作
key 的作用给 diff 提供对比锚点,让 React 知道列表项的身份。用稳定唯一 id,避免用随机数和索引
函数组件和类组件区别代码形式不同,类组件有 this 和生命周期,函数组件用 Hooks 实现同等能力、代码更简洁
setState 是异步还是同步React 18 自动批处理下多为异步,函数式更新可基于最新状态
useEffect 与生命周期映射空依赖对应 DidMount,有依赖对应 DidUpdate,返回函数对应 WillUnmount
组件间通信方式父子用 props 和回调,跨多层级用 Context,复杂全局状态用状态管理库
React 路由鉴权实现封装守卫组件,没登录时用<Navigate to="/login" />重定向
如何性能优化React.memo 包裹组件、useMemo 缓存计算、useCallback 缓存函数、列表虚拟化、代码分割懒加载

13.2 新手最容易踩的坑

坑一:在 map 渲染的循环体内写业务逻辑。比如在 JSX 里直接写if/else、复杂的计算式,代码可读性极差。正确做法是基于数据先计算好渲染内容,再在 JSX 内做展示即可。

坑二:滥用index当 key 导致样式错乱。前文已详述,列表一旦涉及顺序变动,务必用唯一 id。

坑三:组件卸载后还在 setState。常见于异步请求已经发出但组件被销毁,回调里仍然执行了 setState,控制台直接报错“Can't perform a React state update on an unmounted component”。处理方案:用useRef记录组件是否已卸载,或者在 useEffect 的清理函数里设置标志位。

function useSafeSetState() { const isMounted = useRef(true) useEffect(() => { return () => { isMounted.current = false } }, []) const safeSetState = (setter, value) => { if (isMounted.current) { setter(value) } } return safeSetState }

坑四:直接修改 props 或 state 导致无渲染更新。引用不变问题前面已讲,检查一下你是不是直接改了数组或者对象原值。

坑五:在 render 中定义内联对象导致子组件 mome 失效。用 React.memo 优化的子组件,如果父组件每次渲染都创建新的对象 props,memo 完全失去效果,因为引用每一次都不同。

// 错误示范 <Child config={{ type: 'primary', size: 'large' }} /> // 正确做法:用 useMemo 保持引用稳定 const config = useMemo(() => ({ type: 'primary', size: 'large' }), []) <Child config={config} />

14. 我的学习路径建议与最后几句掏心话

如果你是完全零基础,我私心建议的学习路径应该是这样的:先花两周把 JavaScript 的 ES6 语法过一遍,重点掌握let/const、箭头函数、解构赋值、模板字符串、展开运算符、数组的map/filter/reducePromiseasync/await;然后开始学 React 基础,从 JSX 语法、组件、State、Props 一点点写过来,每学一个知识点就写一个小 demo 练手;接着掌握 Hooks、React Router、网络请求,尝试做一个完整的 To-Do 应用或简易博客;最后再接触状态管理库和工程化工具,了解 Redux 或 Zustand 解决了什么问题。

不要一上来就扑到源码里。我看过太多新人把 React 源码当作第一学习素材,啃了三天什么也没记住,深受打击后放弃。源码是进阶的范畴,而入门更重要的从来都是“能跑起来、能写出来、能 debug 出来”。

写 React 这两年半,最大的成长感悟就是:React 告诉你 UI = f(state),它会逼着你用 JavaScript 的思维去思考界面。你写的每一行 JSX 都只是一个函数的返回值,你定义的数据才是整个应用的灵魂。数据设计好了、状态拆分合理了,组件自然而然就好了。

最后分享一个实战技巧:遇到报错不要怕,记住三个步骤——先读报错信息里的组件栈定位问题文件,再看代码里最近的几行有没有引用错误或空值判断问题,最后用控制台的console.log打印关键变量的值确认数据是否符合预期。

React 是一条越到后面越呈现魅力的技术路线。开头会有点磕磕绊绊,但把这篇基础版的每个知识点都动手实跑一遍,再回去看你公司的老项目,一定能看懂七八成了。到那时候,你就正式从“零基础小白”毕业了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 11:09:28

【计算机毕业设计单片机案例】基于 STM32 的水产养殖定时任务执行与环境监测系统设计 基于 STM32 单片机的 JDY‑31 蓝牙养殖监控终端设计与实现(012307)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/9/8 11:08:56

Python print()函数完全指南:从基础参数到高阶玩法

每个学Python的人&#xff0c;跟print()函数的第一次见面几乎都发生在同一天——你在终端敲下print("Hello, world")&#xff0c;屏幕应声吐出那行字&#xff0c;然后你觉得自己已经会编程了。但我要泼一盆冷水&#xff1a;print()是Python里最容易被低估的内建函数。…

作者头像 李华
网站建设 2026/9/8 11:08:16

深度学习+TensorFlow+Visual Studio人脸识别实战:从环境部署到模型调用

简介&#xff1a;这是一份基于TensorFlow与Visual Studio环境实现的人脸识别项目代码&#xff0c;适合具备Python/C基础、希望深入理解深度学习在计算机视觉中落地流程的开发者。项目以CNN卷积神经网络为核心&#xff0c;兼顾MTCNN人脸检测、特征提取与身份识别&#xff0c;覆盖…

作者头像 李华
网站建设 2026/9/8 11:07:53

7代酷睿核显驱动WIN7安装实战:HD630 INF修改全攻略

简介&#xff1a;一份面向英特尔七代CPU&#xff08;如i5-7500、i7-7700&#xff09;的Win7集成显卡驱动资源包&#xff0c;专门解决HD Graphics 630等核显在Windows 7下无法原生安装、微软不再提供新硬件官方支持的问题&#xff0c;适合装机维护人员、老系统偏好用户以及遇到兼…

作者头像 李华
网站建设 2026/9/8 11:04:20

软件测试用例设计实战:从等价类到场景法,打造高质量用例体系

1. 为什么测试用例设计常常做不好 做软件测试这些年&#xff0c;我面试过不少候选人&#xff0c;也带过十几个新人。有一个现象特别有意思&#xff1a;几乎所有简历上都写着“熟悉测试用例设计方法”&#xff0c;但真到了实际项目里&#xff0c;能把用例写到位的&#xff0c;十…

作者头像 李华
网站建设 2026/9/8 11:03:51

Go服务性能调优实战:从Profiling到压测,QPS提升3倍

性能调优这块&#xff0c;我一直觉得是系统编程和普通Web开发之间的一条分界线。前面写了9篇Go语言系统编程和云原生开发的内容&#xff0c;从网络模型讲到容器编排&#xff0c;算是把“能跑”到“能扛”的路走了一半。这一篇我打算专门聊聊性能调优&#xff0c;也就是把服务从…

作者头像 李华