命令式编程,
声明式编程
react核心思想:UI = f(State)。状态驱动视图更新
事件驱动:通过事件驱动(改变)视图内容的变化
搭建项目
开发环境:vite
安装pnpm:
c
npm install -g pnpm
创建项目的命令:
c
npm create vite@latest

常见的 "Variant" 选项解析
不同框架对应的变体选项不同,最常见的是关于语言(JavaScript 或 TypeScript)和编译工具(如 Babel 或 SWC)的选择。
以下是几种典型场景:
- react 框架:
React:使用 Babel 进行代码转译,生态成熟稳定。
使用场景:项目较小、团队熟悉 Babel 生态、或需要兼容性较好的场景。React + TypeScript:在 Babel 转译的基础上,加入 TypeScript 类型系统。
使用场景:希望使用 TypeScript 但偏好传统 Babel 工具链的 React 项目。React + SWC:使用 SWC (Speedy Web Compiler) 进行转译,构建和热重载速度极快。
使用场景:中大型项目、对构建性能有高要求、追求极致开发体验的新项目。React + TypeScript + SWC:结合 TypeScript 类型检查与 SWC 的高速编译能力。
使用场景:强烈推荐给大多数希望同时使用 TypeScript 和追求高性能的现代 React 项目。
- Vue 框架:
Vue:标准的 Vue 3 项目,使用 JavaScript。
使用场景:使用 JavaScript 的通用 Vue 项目。Vue + TypeScript在 Vue 项目的基础上集成了 TypeScript。
使用场景:需要使用 TypeScript 进行类型校验的 Vue 项目。
- 其他框架
JavaScript / TypeScript对于 Preact、Svelte、Solid 等框架,变体通常是在基础框架和集成了 TypeScript 的版本之间选择。
使用场景:根据项目是否需要静态类型检查来决定。
项目启动成功:

复制地址 http://localhost:5173/,在网页上打开

文件介绍

根目录关键文件
- package.json
项目配置文件,定义了项目名称、版本、依赖包、运行脚本(如 dev、build)等。 - vite.config.ts
Vite 构建工具的配置文件,用于优化开发服务器和打包流程。 - tsconfig.json & tsconfig...json*
TypeScript 编译配置,分别针对项目整体、应用代码和 Node 环境。
| 文件 | 用途 | 配置目标 | 包含文件 |
|---|---|---|---|
| tsconfig.json | 基础配置 | 提供通用配置,供其他文件继承 | 不直接编译任何文件 |
| tsconfig.app.json | 前端应用配置 | 浏览器环境 (React/Vue 等) | src/目录下的所有文件 |
| tsconfig.node.json | 构建工具配置 | Node.js 环境 (Vite/构建脚本) | vite.config.ts等配置文件和脚本 |
- .gitignore
指定哪些文件/文件夹不应被 Git 版本控制跟踪(如 node_modules)。 - eslint.config.js
ESLint 静态代码检查工具的配置文件,用于统一代码风格。 - pnpm-lock.yaml
锁定依赖版本,确保团队协作或部署时依赖一致性。
核心文件夹
- node_modules/
存放项目所有依赖的第三方库(安装 npm 包后自动生成)。 - public/
存放静态公共资源(如图标、全局字体),该目录下的文件会被直接复制到构建产物的根目录。
favicon.svg:网站标签页图标
icons.svg:可能存放多个 SVG 图标集合 - src/
项目源码目录,开发者主要工作区域:- assets/:存放图片、字体等模块化静态资源
- App.tsx:React 应用的根组件
- App.css:根组件的样式文件
- main.tsx:项目入口文件,负责渲染根组件到 DOM
-i ndex.css:全局样式文件
- 其他配置文件
- index.html:项目主页面模板,Vite 会自动注入打包后的脚本
- README.md:项目说明文档(通常包含启动方式、功能简介等)
认识 jsx
1. jsx 是什么
JSX 是 JavaScript XML 的缩写。它是一种 JavaScript 的语法扩展,允许你在 JavaScript 代码中编写类似 HTML 的结构。
简单说,JSX 让你能在 JavaScript 里写 HTML。
js
// 变量值是 html 的语法
const element = <h1 className="greeting">Hello, world!</h1>;
2. JSX 用来干嘛?
JSX 的核心目的只有一个:用来描述 UI 应该长什么样。
在 React 中,UI 是通过"组件"来构建的。JSX 让你能以一种 声明式、结构化、视觉上直观 的方式来描述组件的结构,而不是用一堆函数调用来"拼凑"它。这极大地提升了代码的可读性和开发效率。
你可以把它理解为 React 组件的"模板语法",但它比传统模板更强大,因为它完全是 JavaScript 的一部分。
3. JSX 怎么编译?
浏览器不认识 JSX!
所以,JSX 代码在最终交给浏览器运行之前,必须被编译成标准的 JavaScript 代码 (即 React.createElement(...)函数调用)。
这个过程通常由 Babel 这个工具来完成。
编译过程:
JSX-> (通过 Babel 编译) -> 普通的 JavaScript 函数调用-> (React 处理) -> React 元素-> (React DOM 渲染) -> 真实的 DOM
编译示例:
js
// 用 JSX 创建元素
const element = <h1 className="greeting">Hello, world!</h1>;
// Babel 帮你编译成的 JS
const element = React.createElement(
'h1',
{ className: 'greeting' },
'Hello, world!'
);
现代 React 项目(如用 Create React App, Vite 创建的)通常都内置配置好了 Babel,所以你无需手动操作,写 JSX 就行,工具链会自动完成编译。
4. JSX 的写法
核心规则: JSX 看起来像 HTML,但本质是 JavaScript 对象。遵循以下几个关键规则:
- 只能有一个根元素
JSX 表达式必须有一个顶层标签包裹。
js
// ❌ 错误!返回了两个同级元素
const element = (
<h1>标题</h1>
<p>段落</p>
);
// ✅ 正确!用一个 div 或空标签 <> 包裹
const element = (
<div>
<h1>标题</h1>
<p>段落</p>
</div>
);
// ✅ 也可以用 Fragment(幽灵标签),不会产生实际 DOM
const element = (
<>
<h1>标题</h1>
<p>段落</p>
</>
);
- 标签必须闭合
无论是单标签还是双标签,都必须闭合。
js
// 单标签
<img src="..." alt="..." /> // 单标签必须用 `/` 闭合
<br />
<input type="text" />
// 双标签
<div>内容</div> // 双标签正常闭合
- 使用 className代替 class,htmlFor代替 for
因为 class和 for是 JavaScript 的保留字。
js
// ❌
<div class="box">...</div>
<label for="name">Name:</label>
// ✅
<div className="box">...</div>
<label htmlFor="name">Name:</label>
- 在 JSX 中嵌入 JavaScript 表达式:用大括号 {}
这是 JSX 最强大的特性。在大括号里,你可以写任何有效的 JavaScript 表达式(变量、函数调用、三元运算符等)。
js
const name = '吃西瓜的年年';
const isLoggedIn = true;
const user = { avatarUrl: '...' };
const hasName = true;
const element = (
<div>
<h1>Hello, {name}!</h1> // 输出:Hello, 吃西瓜的年年!
<p>1 + 2 等于 {1 + 2}</p> //输出:1 + 2 等于 3
<img src={user.avatarUrl} />
// 条件渲染,三元表达式
{isLoggedIn ? <button>退出</button> : <button>登录</button>}
// 逻辑与 (&&) hasName 为true时就渲染 后面的 h1标签内容
{ hasName && <h1>Hello, {name}!</h1> }
// 调用函数
<p>时间:{new Date().toLocaleTimeString()}</p>
</div>
);
- 行内样式:用对象传递
样式属性名要写成小驼峰形式。
js
// ❌
<div style="color: red; font-size: 20px;">...</div>
// ✅
const divStyle = {
color: 'red',
fontSize: '20px', // font-size -> fontSize
backgroundColor: '#fff' // background-color -> backgroundColor
};
<div style={divStyle}>...</div>
// 或者直接写
<div style={{ color: 'red', marginTop: '10px' }}>...</div>
- 事件处理:属性名用小驼峰
事件处理属性名都必须以 on开头 ,后面跟随事件名称,并且采用小驼峰命 名法。
写法 :on+ 事件名(首字母大写)={方法名}
如:onClick={handleClick},onMouseOver={handleMouseOver}
js
<button onClick={handleClick}>点击</button>
<input onChange={handleInputChange} />
// 传递自定义参数:事件绑定的位置改造成箭头函数的写法,在执行clickHandler实际处理业务函数的时候传递实参
<button onClick={()=>clickHandler('jack')}>click me</button>
const clickHandler = (name)=>{
console.log('button按钮点击了', name)
}
// 使用事件参数:在事件回调函数中设置形参e即可
<button onClick={clickHandler}>click me</button>
const clickHandler = (e)=>{
console.log('button按钮点击了', e)
}
// 同时传递事件对象和自定义参数:在事件绑定的位置传递事件实参e和自定义参数,clickHandler中声明形参,注意顺序对应
<button onClick={(e)=>clickHandler('jack',e)}>click me</button>
const clickHandler = (name,e)=>{
console.log('button按钮点击了', name,e)
}
- 列表渲染 (使用 map)
为每个项返回 JSX,并给一个唯一的 key属性(通常是数据ID),帮助 React 高效更新。
js
const items = ['React', 'Vue', 'Angular'];
const list = (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li> // 尽量用稳定 ID,而非 index
))}
</ul>
);
组件化开发
1. React组件的基本概念
React组件类似于JavaScript函数,可以接受任意属性(Props),并返回页面显示的内容。
在 react 应用中,页面上所有的内容都是由一个个组件构成的。
组件可以并列,可以嵌套一个又一个。

组件的特点:
- 独立性:每个组件管理自己的状态和UI
- 可复用性:同一组件可在多处使用
- 可组合性:组件可嵌套形成复杂UI
- 单向数据流:数据从父组件流向子组件
2. 组件的基本结构
定义组件有两种方式:一种是函数式组件(function component),另一种是类组件(class component)
函数式组件(推荐)
js
function Badge() {
return <h1>Hello, my name is 吃西瓜的年年</h1>;
}
类组件 React 16.8之前的主流
js
class Badge extends React.Component {
render() {
return <h1>Hello, my name is 吃西瓜的年年</h1>;
}
}
父子组件的使用
子组件:
在 src 下建立 components 文件夹,再建立 helloWorld.tsx 文件

子组件:
1.helloWorld.tsx
js
// 具名导出
export const HelloWorld = () => {
return <div>Hello, World</div>
}
父组件
根目录的 app.tsx,改为以下内容
js
import './App.css'
// 导入子组件 组件名首字母要大写
import { HelloWorld } from './components/1.helloWorld'
function App() {
// 引用子组件
// 返回值就是组件的实体内容
// 页面上展示的是子组件的内容
return <HelloWorld />
}
export default App
打开页面就能看到

3. 组件通信的基础
父传子:Props
props 是 properties 的简写形式,它们仅指 React 组件的内部数据。
- 父组件 → 子组件:通过props传递数据
- 只读属性:子组件不能直接修改props
- 它们使用与 HTML 属性相同的语法,例如
<div id="name"></div>里的id="name"。
注意:
- props可以传递任意的合法数据,比如数字、字符串、布尔值、数组、对象、函数、JSX
- props是只读对象
子组件只能读取props中的数据,不能直接进行修改, 父组件的数据只能由父组件修改
子组件:
1.helloWorld.tsx
js
// 定义 props 类型
interface HelloWorldProps {
title: string,
num: number
}
// 使用父组件传进来的数据
export const HelloWorld = (props: HelloWorldProps) => {
// 解构数据
const { title, num } = props
// 使用数据
return <div>Hello, World {title} { num }</div>
}
父组件
根目录的 app.tsx
js
import './App.css'
import { HelloWorld } from './components/1.helloWorld'
function App() {
// 父组件以属性的形式给子组件传数据
return <HelloWorld title='hello, world' num={10} />
}
export default App
子传父:事件处理
基础概念:父组件通过 props 传递一个回调函数给子组件,子组件调用这个函数并传递数据。
- 事件名为驼峰命名
- 传递一个函数作为事件处理程序
子组件:
1.helloWorld.tsx
js
// 定义 props 类型
interface HelloWorldProps {
title: string,
// 定义一个函数的 props,类型为不接受参数,无返回值的函数
onHandleClick: () => void
}
// 使用父组件传进来的数据
export const HelloWorld = (props: HelloWorldProps) => {
// 解构数据
const { title, onHandleClick } = props
// 使用父组件传进来的函数
return <div onClick={onHandleClick}>{title}</div>
}
父组件
根目录的 app.tsx
js
import './App.css'
import { HelloWorld } from './components/1.helloWorld'
function App() {
const handleClick = () => { // 传递给子组件的函数
console.log('click')
}
return (
<>
{/* 事件名采用驼峰命名方式 */}
<HelloWorld title='hello, world' onHandleClick={handleClick} />
</>
)
}
export default App
函数带有带有参数,能把子组件的数据传给父组件
子组件:
1.helloWorld.tsx
js
import { useState } from "react"
// 定义 props 类型
interface HelloWorldProps {
title: string;
onChange?: (count: number) => void; // 带参数
}
export const HelloWorld = (props: HelloWorldProps) => {
// 解构数据
const { title, onChange } = props
const [count, setCount] = useState(0)
const handleAddd = () => {
// count + 1是异步,count还没 + 1,所以下面还是count + 1。下面会讲解到
setCount(count + 1)
// 传参数
onChange?.(count + 1)
}
// 使用父组件传进来的函数
return (
<div>
{title} --- {count}
<button onClick={handleAddd}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</div>
)
}
父组件
根目录的 app.tsx
js
import './App.css'
import { HelloWorld } from './components/1.helloWorld'
function App() {
const handleChange = (count: number) => {
// 接受子组件传过来的参数
console.log('change:', count);
}
return (
<>
<HelloWorld title='hello, world' onChange={handleChange} />
</>
)
}
export default App
Render Props
Render Props 是一种在 React 组件之间共享代码的技术,通过一个值为函数的 prop 来实现。这个函数返回一个 React 元素,父组件调用这个函数来渲染内容。
简单来说就是用来传递函数,类似Vue的插槽
子组件:
1.helloWorld.tsx
js
// 定义 props 类型
interface HelloWorldProps {
title: string,
// 父组件的 <div>你好</div> 属于 React.ReactNode 类型
render?: () => React.ReactNode // // "?"表示这是一个可选的prop,不是必填,父组件不用也不会报错
}
// 使用父组件传进来的数据
export const HelloWorld = (props: HelloWorldProps) => {
// 结构数据
const { title, render } = props
// render 为函数,需要加括号调用。内容是根据父组件的数据渲染的
return <div>Hello, World {title} { render?.() }</div>
}
父组件
根目录的 app.tsx
js
import './App.css'
import { HelloWorld } from './components/1.helloWorld'
function App() {
// 父组件以属性的形式给子组件传数据
return <HelloWorld title='hello, world' render={() => <div>你好</div>} />
}
export default App

状态提升
当多个子组件需要反映相同的数据变化时,我们通常将共享的状态提升到它们最近的共同父组件中去。这就叫状态提升 。在 React 中,状态提升是一种常见的模式,用于在多个组件之间共享状态。
子组件:
1.helloWorld.tsx
js
import { useState } from "react"
// 定义 props 类型
interface HelloWorldProps {
title: string,
}
// 使用父组件传进来的数据
export const HelloWorld = (props: HelloWorldProps) => {
// 解构数据
const { title } = props
// 使用 useState 存储组件的数据,被多次调用时数据有各自的状态,不会混在一起
// 下面有关于useState的详解
const [count, setCount] = useState(0)
// 使用父组件传进来的函数
return (
<div>
{title} --- {count}
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</div>
)
}
父组件
根目录的 app.tsx
js
import './App.css'
import { HelloWorld } from './components/1.helloWorld'
function App() {
return (
<>
{/* 多次使用同一组件 */}
<HelloWorld title='hello, world' />
<HelloWorld title='hello, world' />
</>
)
}
export default App
4. 状态管理:useState
React Hook 是 React 16.8 版本引入的一项重要特性,它让你在函数组件中也能使用 state(暂存组件的数据) 以及其他 React 特性(如生命周期方法、上下文等),而无需编写 class 组件。
1. 什么是 useState?**
1.1 理解"状态"
- 想象一下,你正在做一个记数器应用:
- 用户每点一次按钮,数字就增加 1
- 这个"数字"就是状态
- 它会在程序运行过程中变化
在 React 中,状态就是组件内部可以变化的数据。
1.2 为什么需要 useState?
在没有 Hooks 之前,函数组件被称为"无状态组件",因为它们无法存储变化的数据。比如:
js
// ❌ 这样不行!普通变量改变不会让页面更新
function Counter() {
let count = 0; // 普通变量
const handleClick = () => {
count = count + 1; // 变量变了,但页面不会重新显示
console.log(count); // 控制台能看到变化,但屏幕上还是 0
};
return (
<div>
<p>计数: {count}</p> {/* 永远显示 0 */}
<button onClick={handleClick}>增加</button>
</div>
);
}
问题:普通变量改变后,React 不知道要重新渲染页面。
解决方案:使用 useState,它能让 React "记住"数据,并在数据改变时自动更新页面。
js
// ✅ 使用 useState
function Counter() {
const [count, setCount] = useState(0); // 状态变量
const handleClick = () => {
setCount(count + 1); // 改变状态,页面自动更新
};
return (
<div>
<p>计数: {count}</p> {/* 每次点击都会显示新数字 */}
<button onClick={handleClick}>增加</button>
</div>
);
}
1.3 useState 的本质
useState 就像一个"记忆盒子":
- 它可以存储一个值
- 当这个值改变时,React 会自动重新渲染组件
- 每次渲染,你都能拿到最新的值
2. useState 怎么用?
2.1 基本语法
js
import React, { useState } from 'react';
function MyComponent() {
// 解构赋值: [当前值, 修改值的函数] = useState(初始值)
const [value, setValue] = useState(初始值);
return (
// JSX 代码
);
}
语法解释:
- useState 是一个函数,调用它会返回一个数组
- 数组的第一个元素是当前的状态值
- 数组的第二个元素是修改状态的函数
- 括号里的参数是初始值
2.2 理解解构赋值
我们来拆解一下const [count, setCount] = useState(0) 这种写法:
js
// useState(0) 返回一个数组,比如: [0, function]
const result = useState(0);
console.log(result); // [0, ƒ]
// 传统的写法
const count = result[0]; // 状态值
const setCount = result[1]; // 修改函数
// 解构赋值的写法(更简洁)
const [count, setCount] = useState(0);
2.3 命名规范
状态名 :用描述性的名词
修改函数名:通常用 set + 状态名(驼峰命名)
js
const [count, setCount] = useState(0);
const [userName, setUserName] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [items, setItems] = useState([]);
3. 示例
js
import React, { useState } from 'react';
function Counter() {
// 1. 声明状态:count 初始值为 0
const [count, setCount] = useState(0);
// 2. 定义事件处理函数
const addOne = () => {
setCount(count + 1); // 把 count 加 1
};
const subtractOne = () => {
setCount(count - 1); // 把 count 减 1
};
const reset = () => {
setCount(0); // 重置为 0
};
// 3. 渲染页面
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>计数器示例</h1>
<h2 style={{ fontSize: '48px' }}>{count}</h2>
<button onClick={addOne} style={{ margin: '5px' }}>➕ 增加</button>
<button onClick={subtractOne} style={{ margin: '5px' }}>➖ 减少</button>
<button onClick={reset} style={{ margin: '5px' }}>🔄 重置</button>
</div>
);
}
export default Counter;
对象状态
js
import { useState } from "react"
export const BasicState = () => {
const [info, setInfo] = useState({
num: 0
})
const handleAdd = () => {
// 响应式修改:
// 1.创建新对象,覆盖旧对象
setInfo({
...info,
num: info.num + 1
})
// 2.回调函数式写法:(pre) => (); pre就是原始的info(推荐)
setInfo((pre) => ({
...pre,
num: pre.num + 1
}))
}
return (
<>
<div>
{info.num}
<button onClick={handleAdd}>+</button>
</div>
</>
)
}
4. 注意事项
1. 状态是不可变的!
错误示范:
js
// ❌ 直接修改状态
const [user, setUser] = useState({ name: '小明', age: 18 });
// 这样不行!
user.age = 19;
setUser(user); // React 认为没有变化,不会重新渲染
// ❌ 数组也不能直接修改
const [list, setList] = useState(['a', 'b']);
list.push('c'); // 直接修改数组
setList(list); // 不会触发更新
正确做法:
js
// ✅ 创建新的对象
setUser({ ...user, age: 19 }); // 展开原对象,覆盖 age
// ✅ 创建新的数组
setList([...list, 'c']); // 展开原数组,添加新元素
setList(list.filter(item => item !== 'a')); // 过滤后返回新数组
- React 使用 Object.is() 比较状态是否改变
- 如果对象引用相同,React 认为没变化,不会重新渲染
- 所以必须创建新的对象或数组
- 多次更新合并
js
function MergeExample() {
const [count, setCount] = useState(0);
const handleClick = () => {
// setCount参数是 数字
// ❌ 这样只会加 1,而不是 3
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// 因为 count 始终是旧值,相当于:0+1, 0+1, 0+1
// setCount参数是 函数
// ✅ 这样会加 3
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// prev 依次是:0→1→2
};
return <button onClick={handleClick}>计数: {count}</button>;
}
为什么要异步?
- 性能优化:避免多次更新多次渲染
- 如果同步更新,每次 setCount 都会立即重新渲染,效率低