从零开始,用一个
react-basic项目串起 React 入门必备知识。
一、项目搭建
使用 Vite 创建 React 项目:
1npm create vite@latest react-basic -- --template react
2cd react-basic
3npm install
4npm run dev
常用命令:
| 命令 | 作用 |
|---|---|
npm run dev | 启动开发服务器 |
npm run build | 生产构建 |
npm run preview | 预览生产构建 |
npm run lint | 运行 Oxlint |
npm test -- --run | 运行测试 |
二、JSX 基础
JSX 允许在 JavaScript 中写 HTML 结构,并用 {} 嵌入表达式。
1function Demo() {
2 const count = 100
3 const getName = () => 'jack'
4
5 return (
6 <section>
7 this is App
8 {'this is message'} {/* 字符串 */}
9 {count} {/* 变量 */}
10 {getName()} {/* 函数调用 */}
11 {new Date().getDate()} {/* 方法调用 */}
12 <div style={{ color: 'red' }}>this is div</div> {/* JS 对象 */}
13 </section>
14 )
15}
注意点:
- 外层
{}表示 JSX 表达式 - 内层
{}是 JavaScript 对象{ color: 'red' } - 类名用
className,而不是class
三、组件与 Props
React 应用由组件组成。组件接收 props,返回 JSX。
3.1 基础组件
1function Header({ title }) {
2 return <h1>{title}</h1>
3}
4
5function Button({ children, onClick }) {
6 return <button onClick={onClick}>{children}</button>
7}
3.2 使用组件
1<Header title="Hello React Components" />
2<Button onClick={() => alert('clicked')}>Click me</Button>
props:父组件向子组件传递数据children:组件标签中间的内容
四、useState 基础
useState 让函数组件拥有状态。
1import { useState } from 'react'
2
3function Counter() {
4 const [count, setCount] = useState(0)
5
6 return (
7 <div>
8 <p>{count}</p>
9 <button onClick={() => setCount(count + 1)}>+1</button>
10 </div>
11 )
12}
- 返回
[state, setState] - 调用
setState会触发重新渲染 - 新值依赖旧值时,推荐函数式更新:
setCount((prev) => prev + 1)
4.1 对象状态
多个相关字段可以放在一个对象里管理:
1const [user, setUser] = useState({ name: '', age: '', email: '' })
2
3const handleChange = (event) => {
4 const { name, value } = event.target
5 setUser((prev) => ({ ...prev, [name]: value }))
6}
重点:只改一个字段时,也要保留其他字段。
五、状态不可变
不要直接修改 state,要创建新的值。
❌ 错误
1users.push(newUser)
2setUsers(users)
3
4user.name = 'Jerry'
5setUser(user)
✅ 正确
1setUsers([...users, newUser])
2setUser({ ...user, name: 'Jerry' })
3
4// 删除
5setUsers(users.filter((u) => u.id !== id))
6
7// 修改数组中某一项
8setUsers(
9 users.map((u) => (u.id === id ? { ...u, name: 'New' } : u))
10)
React 通过引用变化来判断是否需要重新渲染,直接修改原对象不会触发更新。
六、组件样式方案
6.1 CSS Modules
每个组件一个 .module.css 文件,类名会被自动哈希,避免全局污染。
1/* Card.module.css */
2.card {
3 padding: 16px;
4 border-radius: 8px;
5 background: #fff;
6}
1import styles from './Card.module.css'
2
3function Card({ title }) {
4 return <div className={styles.card}>{title}</div>
5}
6.2 集中式 CSS 变量
把设计 token 放在全局样式文件中:
1:root {
2 --color-primary: #1890ff;
3 --color-bg: #f5f5f5;
4 --spacing-md: 16px;
5 --radius: 8px;
6 --shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
7}
组件中引用:
1.card {
2 padding: var(--spacing-md);
3 background: var(--color-surface);
4 box-shadow: var(--shadow);
5}
6.3 classnames 处理条件类名
1import classNames from 'classnames'
2import styles from './Card.module.css'
3
4function Card({ highlight }) {
5 return (
6 <div className={classNames(styles.card, { [styles.highlight]: highlight })}>
7 ...
8 </div>
9 )
10}
classnames 让多条件类名更易读,也避免 false 时产生多余空格。
七、综合案例:B站评论
实现了一个完整的评论组件 CommentApp,覆盖以下功能:
- 渲染评论列表
- 删除评论
- Tab 导航与高亮
- 评论排序(最新 / 最热)
- 发布新评论
- 只有自己的评论才显示删除按钮
7.1 组件拆分
1src/comment/
2├── CommentApp.jsx
3├── CommentTabs.jsx
4├── CommentInput.jsx
5├── CommentList.jsx
6├── CommentItem.jsx
7├── comment.module.css
8└── CommentApp.test.jsx
7.2 核心状态
1const [comments, setComments] = useState(initialComments)
2const [activeTab, setActiveTab] = useState('all')
3const [inputValue, setInputValue] = useState('')
7.3 排序逻辑
1const sortedComments = [...comments].sort((a, b) => {
2 if (activeTab === 'latest') return b.date.localeCompare(a.date)
3 if (activeTab === 'hottest') return b.likes - a.likes
4 return b.date.localeCompare(a.date)
5})
7.4 权限控制
每条评论带 uid,只有 uid 与当前用户一致时才显示删除按钮:
1const isOwn = comment.uid === currentUserId
2
3{isOwn && <button onClick={() => onDelete(comment.id)}>删除</button>}
八、总结
今天从 0 搭建了一个 React 项目,依次学习了:
- JSX 语法与表达式
- 组件拆分、props、children
- useState 数字 / 布尔 / 对象状态
- 状态不可变原则
- 组件样式:CSS Modules、CSS 变量、classnames
- 一个完整的评论案例
下一步可以继续学习:
useEffect副作用- 父子组件通信 / 状态提升
- React Router 路由
- 数据请求(fetch / axios)
- 表单校验
文章为 React 12 天学习计划第 1 天笔记,代码位于 react-basic 项目中。