当前位置: 首页 > news >正文

【从Vue3到React】Day 1: React基础概念

Day 1: React基础概念

文章目录

  • Day 1: React基础概念
    • 📋 今日目标
    • 🌅 上午课程(3-4小时)
      • 1️⃣ React简介和环境搭建(1小时)
        • React是什么?
        • React vs Vue 设计思想对比
        • 创建你的第一个React项目
        • 项目结构解读
        • 关键文件解析
        • 安装React DevTools
      • 2️⃣ JSX语法深入(1.5小时)
        • 什么是JSX?
        • JSX基础语法
        • JSX与Vue Template对比
      • 3️⃣ 组件基础(1.5小时)
        • 函数组件
        • Props传递
        • 组件组合
    • 🌆 下午课程(3-4小时)
      • 4️⃣ 条件渲染实战(1小时)
        • 多条件渲染
        • 条件渲染最佳实践
      • 5️⃣ 列表渲染实战(1小时)
        • 基础列表渲染
        • 列表过滤
        • 列表排序
        • 空列表处理
        • 复杂列表示例
      • 6️⃣ 实践项目:待办事项列表(2小时)
        • 项目需求
        • 组件结构设计
        • 完整代码实现
        • 样式文件(App.css)
        • 运行结果
    • 📝 今日总结
      • 关键知识点
      • 完成检查清单
    • 📚 课后作业
      • 必做
      • 选做
    • 🚀 明天预告

学习时间:6-8小时
适合对象:有Vue3经验的开发者

📋 今日目标

  • ✅ 掌握React项目创建和结构
  • ✅ 熟练使用JSX语法
  • ✅ 理解函数组件和Props
  • ✅ 实现条件渲染和列表渲染
  • ✅ 完成第一个React项目

🌅 上午课程(3-4小时)

1️⃣ React简介和环境搭建(1小时)

React是什么?

React是一个用于构建用户界面的JavaScript库,由Facebook开发。核心特点:

  • 声明式:描述UI应该是什么样,React负责如何实现
  • 组件化:UI由独立、可复用的组件构成
  • 一次学习,随处编写:可用于Web、移动端、桌面端
React vs Vue 设计思想对比
特性VueReact
模板语法HTML模板 + 指令JSX (JavaScript + XML)
响应式自动依赖追踪手动管理状态
学习曲线平缓稍陡(需要深入JS)
灵活性中等很高(一切皆JS)
官方路由/状态管理有(Vue Router/Pinia)社区方案为主
创建你的第一个React项目

使用Vite创建项目(比Create React App更快):

# 创建项目
npm create vite@latest my-react-app -- --template react# 进入项目目录
cd my-react-app# 安装依赖
npm install# 启动开发服务器
npm run dev

项目创建过程

npm create vite@latest my-react-app -- --template react
Need to install the following packages:
create-vite@8.0.1
Ok to proceed? (y) y> npx
> create-vite my-react-app --template react|
o  Use rolldown-vite (Experimental)?:
|  No
|
o  Install with npm and start now?
|  Yes
|
o  Scaffolding project in C:\MyLearn\my-react-app...
|
o  Installing dependencies with npm...added 153 packages, and audited 154 packages in 17s32 packages are looking for fundingrun `npm fund` for detailsfound 0 vulnerabilities
|
o  Starting dev server...> my-react-app@0.0.0 dev
> viteVITE v7.1.7  ready in 374 ms➜  Local:   http://localhost:5173/➜  Network: use --host to expose➜  press h + enter to show help

访问 http://localhost:5173/

QQ_1759186937549

项目结构解读
my-react-app/
├── node_modules/       # 依赖包
├── public/             # 静态资源
├── src/                # 源代码目录
│   ├── assets/         # 资源文件(图片、样式等)
│   ├── App.jsx         # 根组件
│   ├── App.css         # 根组件样式
│   ├── main.jsx        # 入口文件(类似Vue的main.js)
│   └── index.css       # 全局样式
├── index.html          # HTML模板
├── package.json        # 项目配置
└── vite.config.js      # Vite配置

实际初始化后,项目结构截图

QQ_1759187131847

关键文件解析

main.jsx(入口文件):

/*** 从 react 包中导入 StrictMode 组件* StrictMode 是 React 的严格模式组件,类似于 Vue 3 的开发模式检查* 它会在【开发环境】下进行额外的检查和警告,帮助你发现潜在问题* 不会渲染任何可见的 UI,也不会影响生产构建*/
import { StrictMode } from 'react'/*** 从 react-dom/client 包中导入 createRoot 方法* 这是 React 18 新的渲染 API,类似于 Vue 3 的 createApp()* 用于创建一个 React 根节点,然后将组件渲染到 DOM 中** 对比 Vue 3:* Vue 3: createApp(App).mount('#app')* React: createRoot(document.getElementById('root')).render(<App />)*/
import { createRoot } from 'react-dom/client'/*** 导入全局样式文件* 类似于 Vue 3 中在 main.js 里 import './style.css'*/
import './index.css'/*** 导入根组件 App* 类似于 Vue 3 中的 import App from './App.vue'* 注意:React 组件文件通常使用 .jsx 或 .js 扩展名*/
import App from './App.jsx'/*** React 应用的启动流程(链式调用):** 1. createRoot() - 创建 React 根节点*    参数:DOM 元素,这里获取 id 为 'root' 的 div** 2. .render() - 将组件渲染到根节点*    参数:要渲染的 React 元素(JSX)** 对比 Vue 3 的启动方式:** Vue 3:* createApp(App).mount('#app')** React 18:* createRoot(document.getElementById('root')).render(<App />)** 主要区别:* - Vue 使用字符串选择器 '#app'* - React 需要传入实际的 DOM 元素对象*/
createRoot(document.getElementById('root')).render(/*** StrictMode 包裹根组件* 在开发模式下会:* 1. 识别不安全的生命周期方法* 2. 检测意外的副作用* 3. 检测过时的 API* 4. 组件会渲染两次(仅开发模式),帮助发现副作用问题** 类似于 Vue 3 开发工具的警告功能,但更严格*/<StrictMode>{/*渲染 App 根组件JSX 语法:类似于 Vue 的模板语法,但实际上是 JavaScript<App /> 等同于 React.createElement(App)对比:Vue 3 模板: <App />React JSX: <App />看起来相同,但 React 的 JSX 需要编译成 JavaScript 函数调用*/}<App /></StrictMode>,
)

App.jsx(根组件):

/*** 从 react 包中导入 useState Hook** Hook 是 React 16.8 引入的新特性,让你在函数组件中使用状态和其他 React 特性** 对比 Vue 3:* Vue 3 Composition API: import { ref } from 'vue'* React Hooks: import { useState } from 'react'** 相似点:都是在函数式组件中管理状态* 不同点:* - Vue 3 使用 ref() 创建响应式数据* - React 使用 useState() 创建状态*/
import { useState } from 'react'/*** 导入图片资源** 在 Vite 中,可以直接导入图片文件* 导入后得到的是图片的 URL 路径字符串** 类似于 Vue 3 中:* import logo from './assets/logo.png'*/
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg' // 以 / 开头表示从 public 目录导入/*** 导入组件样式* 这个样式只作用于当前组件相关的元素*/
import './App.css'/*** App 组件 - 函数组件写法** React 组件的两种写法:* 1. 函数组件(推荐,现代写法)* 2. 类组件(旧写法,逐渐被淘汰)** 对比 Vue 3:** Vue 3 组件:* <script setup>* import { ref } from 'vue'* const count = ref(0)* </script>** React 函数组件:* function App() {*   const [count, setCount] = useState(0)*   return (...)* }** 主要区别:* - Vue 3 使用 <script setup> 语法糖,代码更简洁* - React 需要显式返回 JSX* - React 组件名必须大写开头(Pascal命名)*/
function App() {/*** useState Hook - 状态管理** 语法:const [状态变量, 更新函数] = useState(初始值)** useState 返回一个数组,包含两个元素:* 1. count - 当前状态值(类似 Vue 的 count.value)* 2. setCount - 更新状态的函数(类似 Vue 的 count.value = newValue)** 对比 Vue 3:** Vue 3:* const count = ref(0)           // 创建响应式数据* count.value++                  // 修改需要 .value** React:* const [count, setCount] = useState(0)  // 创建状态* setCount(count + 1)            // 通过 setCount 函数修改** 重要区别:* - Vue 3 的 ref 是响应式对象,直接修改 .value* - React 的 state 是不可变的,必须通过 setState 函数更新* - React 的状态更新是异步的,可能会合并多次更新*/const [count, setCount] = useState(0)/*** 返回 JSX (组件的渲染内容)** JSX 是 JavaScript XML 的缩写,是 React 的模板语法* 看起来像 HTML,但实际是 JavaScript 表达式** 对比 Vue 3:* Vue 3 使用 <template> 标签包裹模板* React 直接在 return 中返回 JSX*/return (/*** Fragment (片段) - 空标签 <>...</>** 用途:包裹多个子元素,但不会在 DOM 中创建额外节点** 对比 Vue 3:* Vue 3: 可以直接写多个根元素(Vue 3.2+)* React: 必须有一个根元素,可以用 Fragment 避免额外的 div** 完整写法:<React.Fragment>...</React.Fragment>* 简写:<>...</>** 类似于 Vue 3 的 <template> 标签(但不完全一样)*/<><div>{/*target="_blank" 需要配合 rel="noopener noreferrer" 使用但这里省略了,不是最佳实践*/}<a href="https://vite.dev" target="_blank">{/*JSX 中使用动态值需要用 {} 包裹属性对比:- class 在 JSX 中要写成 className (因为 class 是 JS 关键字)- for 在 JSX 中要写成 htmlForVue 3 vs React:Vue 3: <img :src="viteLogo" class="logo" />React: <img src={viteLogo} className="logo" />主要区别:- Vue 用 : 绑定动态属性- React 用 {} 包裹 JavaScript 表达式*/}<img src={viteLogo} className="logo" alt="Vite logo" /></a><a href="https://react.dev" target="_blank"><img src={reactLogo} className="logo react" alt="React logo" /></a></div><h1>Vite + React</h1><div className="card">{/*事件处理 - onClick语法:onClick={事件处理函数}这里使用箭头函数调用 setCount:onClick={() => setCount((count) => count + 1)}详细解析:1. onClick={...} - 绑定点击事件2. () => ... - 箭头函数,点击时执行3. setCount(...) - 调用状态更新函数4. (count) => count + 1 - 函数式更新,推荐写法为什么用函数式更新?setCount(count + 1)     // ❌ 可能出现问题(基于旧值)setCount(c => c + 1)    // ✅ 推荐(总是基于最新值)对比 Vue 3:Vue 3:<button @click="count++">{{ count }}</button>或<button @click="increment">{{ count }}</button>React:<button onClick={() => setCount(c => c + 1)}>count is {count}</button>主要区别:- Vue 3 使用 @click 或 v-on:click- React 使用 onClick (驼峰命名)- Vue 3 可以直接修改响应式数据- React 必须使用 setState 函数- Vue 3 用 {{}} 插值- React 用 {} 插值*/}<button onClick={() => setCount((count) => count + 1)}>count is {count}</button><p>Edit <code>src/App.jsx</code> and save to test HMR</p></div><p className="read-the-docs">Click on the Vite and React logos to learn more</p></>)
}/*** 默认导出组件** React 组件的导出方式:* 1. 默认导出:export default App (推荐)* 2. 命名导出:export { App } 或 export function App() {}** 对比 Vue 3:* Vue 3 使用 <script setup> 时不需要显式导出* React 必须显式导出组件*/
export default App
安装React DevTools

在Chrome或Edge浏览器中安装React DevTools扩展,用于调试React应用。

实践任务:

  1. 创建一个新的React项目
  2. 启动开发服务器
  3. 修改App.jsx中的内容,观察热更新效果
  4. 打开React DevTools查看组件树

效果演示:

QQ_1759189944930


2️⃣ JSX语法深入(1.5小时)

什么是JSX?

JSX = JavaScript + XML,是JavaScript的语法扩展,允许在JS中写类似HTML的标记。

关键理解:

  • JSX不是字符串,也不是HTML
  • JSX会被编译成React.createElement()调用
  • JSX本质上就是JavaScript表达式
JSX基础语法

1. 嵌入JavaScript表达式

const name = '訾博'
const age = 25function Welcome() {return (<div><h1>你好,{name}!</h1><p>你今年 {age} 岁</p><p>明年你将 {age + 1} 岁</p><p>当前时间:{new Date().toLocaleTimeString()}</p></div>)
}

2. JSX中的属性

// 字符串属性:直接用引号
<img src="avatar.jpg" alt="头像" />// 动态属性:用花括号
const imageUrl = 'avatar.jpg'
<img src={imageUrl} alt="头像" />// 注意:class要写成className(因为class是JS关键字)
<div className="container">内容</div>// style是对象而不是字符串
<div style={{ color: 'red', fontSize: 16, marginTop: '10px' }}>红色文字
</div>

3. 条件渲染(没有v-if)

// 方式1:三元表达式
function Greeting({ isLogin }) {return (<div>{isLogin ? <h1>欢迎回来!</h1> : <h1>请先登录</h1>}</div>)
}// 方式2:逻辑与运算符(适合只显示或不显示)
function Notification({ count }) {return (<div>{count > 0 && <span>你有 {count} 条新消息</span>}</div>)
}// 方式3:提前return(适合多条件)
function UserStatus({ status }) {if (status === 'loading') {return <div>加载中...</div>}if (status === 'error') {return <div>出错了!</div>}return <div>加载完成</div>
}// 方式4:立即执行函数(复杂逻辑)
function ComplexComponent({ value }) {return (<div>{(() => {if (value > 100) return '很大'if (value > 50) return '中等'return '很小'})()}</div>)
}

4. 列表渲染(没有v-for)

function TodoList() {const todos = [{ id: 1, text: '学习React', done: false },{ id: 2, text: '做项目', done: false },{ id: 3, text: '休息', done: true }]return (<ul>{todos.map(todo => (<li key={todo.id}>{todo.text} {todo.done ? '✅' : '⏳'}</li>))}</ul>)
}

⚠️ key的重要性:

  • key帮助React识别哪些元素改变了
  • key必须在兄弟节点中唯一
  • 不要用index作为key(除非列表不会重排)
  • key应该是稳定的、可预测的
JSX与Vue Template对比
Vue TemplateReact JSX说明
{{ message }}{message}插值表达式
v-if="isShow"{isShow && <div />}条件渲染
v-for="item in list"{list.map(item => ...)}列表渲染
:class="className"className={className}动态class
:style="styleObj"style={styleObj}动态style
@click="handler"onClick={handler}事件绑定
v-model="value"value={value} onChange={handler}双向绑定

实践任务:

  1. 创建一个显示个人信息的组件,包含姓名、年龄、技能列表
  2. 根据年龄显示不同的称呼(未成年/成年)
  3. 用不同颜色标记不同技能等级

实践代码:

import { useState } from "react";function UserInfo() {const [user] = useState({name: '訾博',age: 29,skills: [{ id: 1, name: '阅读' }, { id: 2, name: '编程' }, { id: 3, name: '画图' }]})return (<><p>姓名:{user.name}</p><p>年龄:{user.age}, 是否成年: { user.age > 18 ? '成年' : '未成年' }</p><p>技能列表:</p><ul>{user.skills.map(skill => (<li key={skill.id}>{skill.name}</li>))}</ul></>);
}export default UserInfo;

实践结果:

QQ_1759191076248


3️⃣ 组件基础(1.5小时)

函数组件

现代React推荐使用函数组件(配合Hooks),不再使用类组件。

基础组件定义:

// 组件名必须大写开头
function Welcome() {return <h1>Hello, React!</h1>
}// 导出组件
export default Welcome// 或者使用箭头函数
const Welcome = () => {return <h1>Hello, React!</h1>
}export default Welcome
Props传递

Props类似于Vue的props,用于父组件向子组件传递数据。

基础用法:

// 父组件
function App() {return (<div><Greeting name="訾博" age={25} /><Greeting name="张三" age={30} /></div>)
}// 子组件(接收props)
function Greeting(props) {return (<div><h2>你好,{props.name}!</h2><p>年龄:{props.age}</p></div>)
}

Props解构(推荐):

// 直接解构props
function Greeting({ name, age }) {return (<div><h2>你好,{name}!</h2><p>年龄:{age}</p></div>)
}// 设置默认值
function Greeting({ name = '游客', age = 0 }) {return (<div><h2>你好,{name}!</h2><p>年龄:{age}</p></div>)
}// 使用剩余参数
function Button({ children, ...restProps }) {return <button {...restProps}>{children}</button>
}// 使用方式:<Button className="primary" onClick={handler}>点击</Button>

Props的特点:

  1. 只读性:不能修改props(单向数据流)

    function Greeting({ name }) {// ❌ 错误:不能修改props// name = '新名字'// ✅ 正确:需要修改时用state(明天学习)return <h1>{name}</h1>
    }
    
  2. 可以传递任何类型

    <Componentstring="文本"number={123}boolean={true}array={[1, 2, 3]}object={{ name: '张三' }}function={() => console.log('click')}element={<span>元素</span>}
    />
    
  3. children特殊prop

    function Card({ children }) {return (<div className="card">{children}</div>)
    }// 使用
    <Card><h2>标题</h2><p>内容</p>
    </Card>
    
组件组合

React推崇组合而非继承。

// 容器组件
function Container({ children }) {return <div className="container">{children}</div>
}// 布局组件
function Layout({ header, sidebar, content }) {return (<div className="layout"><header>{header}</header><aside>{sidebar}</aside><main>{content}</main></div>)
}// 使用
function App() {return (<Layoutheader={<Header />}sidebar={<Sidebar />}content={<Content />}/>)
}

实践任务:

  1. 创建一个UserCard组件,接收用户信息作为props
  2. 创建一个Button组件,可以接收不同的样式类型
  3. 创建一个Card容器组件,可以包裹任意内容

实践代码:

// UserCard组件
function UserCard({ name, age, skills}) {return (<><p>姓名:{name}</p><p>年龄:{age}, 是否成年: { age > 18 ? '成年' : '未成年' }</p><p>技能列表:</p><ul>{skills.map(skill => (<li key={skill.id}>{skill.}</li>))}</ul></>);
}export default UserCard// Button组件
function Button({ func, ele}) {return (<><button onClick={func}>{ele}</button></>);
}export default Button// Card组件
function Card({ title, children }) {return (<><div className="card-title">{title}</div><div className="card-body">{children}</div></>);
}export default Card// 使用演示代码
import './App.css'import UserCard from './components/UserCard'
import Button from './components/Button'
import Card from './components/Card'function App() {return (<>{/* UserCard */}<UserCard name="訾博" age="29" skills={[{ id: 1, name: '阅读' },{ id: 2, name: '编程' },{ id: 3, name: '画图' }]} />{/* Button */}<Button func={() => alert('按钮被点击了')} ele={<span>点我</span>} />{/* Card */}<Card title="卡片标题"><p>这是卡片的内容,可以是任意元素。</p><p>可以通过 children 属性传递内容。</p></Card></>)
}export default App

实践结果:

QQ_1759194981744


🌆 下午课程(3-4小时)

4️⃣ 条件渲染实战(1小时)

多条件渲染
function OrderStatus({ status }) {// 方式1:多个if语句if (status === 'pending') {return <span className="status-pending">待支付</span>}if (status === 'paid') {return <span className="status-paid">已支付</span>}if (status === 'shipped') {return <span className="status-shipped">已发货</span>}if (status === 'completed') {return <span className="status-completed">已完成</span>}return <span className="status-unknown">未知状态</span>
}// 方式2:switch语句
function OrderStatus({ status }) {let contentswitch (status) {case 'pending':content = <span className="status-pending">待支付</span>breakcase 'paid':content = <span className="status-paid">已支付</span>breakcase 'shipped':content = <span className="status-shipped">已发货</span>breakcase 'completed':content = <span className="status-completed">已完成</span>breakdefault:content = <span className="status-unknown">未知状态</span>}return content
}// 方式3:对象映射(推荐)
function OrderStatus({ status }) {const statusMap = {pending: <span className="status-pending">待支付</span>,paid: <span className="status-paid">已支付</span>,shipped: <span className="status-shipped">已发货</span>,completed: <span className="status-completed">已完成</span>}return statusMap[status] || <span className="status-unknown">未知状态</span>
}
条件渲染最佳实践
// ✅ 好的做法:清晰的条件逻辑
function ProductCard({ product, isLoggedIn, isPremium }) {// 提前处理复杂条件const canPurchase = isLoggedIn && product.stock > 0const showDiscount = isPremium && product.discount > 0return (<div className="product-card"><h3>{product.name}</h3><p className="price">¥{product.price}</p>{showDiscount && (<span className="discount">-{product.discount}%</span>)}{canPurchase ? (<button>立即购买</button>) : (<button disabled>暂不可购买</button>)}</div>)
}// ❌ 避免:过于复杂的嵌套条件
function BadExample({ a, b, c, d }) {return (<div>{a ? (b ? (c ? <ComponentA /> : <ComponentB />) : (d ? <ComponentC /> : <ComponentD />)) : (<ComponentE />)}</div>)
}

实践任务:
创建一个根据时间显示不同问候语的组件,早上/中午/晚上显示不同内容和样式

实践代码:

// TimeShow 组件
function TimeShow({ hour }) {const morning = <p style={{color: 'orange'}}>上午好</p>const afternoon = <p style={{color: 'blue'}}>下午好</p>const night = <p style={{color: 'black'}}>晚上好</p>return (<><div>当前时间:{hour}点</div><div>{hour < 12 ? morning : hour < 18 ? afternoon : night}</div></>)
}export default TimeShow// return 语句写法2
return (<><div>当前时间:{hour}点</div><div>{(() => {if (hour < 12) {return morning} else if (hour < 18) {return afternoon} else {return night}})()}</div></>
)// 组件使用
import './App.css'import TimeShow from './components/TimeShow'function App() {return (<>{/* TimeShow */}<TimeShow hour='9' /><TimeShow hour='15' /><TimeShow hour='21' /></>)
}export default App

实践结果:

QQ_1759199200981


5️⃣ 列表渲染实战(1小时)

基础列表渲染
function UserList() {const users = [{ id: 1, name: '张三', age: 25, role: 'admin' },{ id: 2, name: '李四', age: 30, role: 'user' },{ id: 3, name: '王五', age: 28, role: 'user' }]return (<ul>{users.map(user => (<li key={user.id}>{user.name} - {user.age}岁 - {user.role}</li>))}</ul>)
}
列表过滤
function FilteredUserList() {const users = [{ id: 1, name: '张三', age: 25, active: true },{ id: 2, name: '李四', age: 30, active: false },{ id: 3, name: '王五', age: 28, active: true }]// 过滤出活跃用户const activeUsers = users.filter(user => user.active)return (<ul>{activeUsers.map(user => (<li key={user.id}>{user.name}</li>))}</ul>)
}
列表排序
function SortedProductList() {const products = [{ id: 1, name: '商品A', price: 99 },{ id: 2, name: '商品B', price: 199 },{ id: 3, name: '商品C', price: 49 }]// 按价格排序const sortedProducts = [...products].sort((a, b) => a.price - b.price)return (<ul>{sortedProducts.map(product => (<li key={product.id}>{product.name} - ¥{product.price}</li>))}</ul>)
}
空列表处理
function ProductList({ products }) {// 处理空列表if (products.length === 0) {return <div className="empty">暂无商品</div>}return (<ul>{products.map(product => (<li key={product.id}>{product.name}</li>))}</ul>)
}// 或者使用条件渲染
function ProductList({ products }) {return (<>{products.length === 0 ? (<div className="empty">暂无商品</div>) : (<ul>{products.map(product => (<li key={product.id}>{product.name}</li>))}</ul>)}</>)
}
复杂列表示例
function StudentList() {const students = [{ id: 1, name: '张三', score: 85, passed: true },{ id: 2, name: '李四', score: 92, passed: true },{ id: 3, name: '王五', score: 58, passed: false },{ id: 4, name: '赵六', score: 75, passed: true }]// 统计信息const passedCount = students.filter(s => s.passed).lengthconst avgScore = students.reduce((sum, s) => sum + s.score, 0) / students.lengthreturn (<div><div className="summary"><p>总人数:{students.length}</p><p>及格人数:{passedCount}</p><p>平均分:{avgScore.toFixed(2)}</p></div><table><thead><tr><th>姓名</th><th>分数</th><th>状态</th></tr></thead><tbody>{students.map(student => (<tr key={student.id} className={student.passed ? 'passed' : 'failed'}><td>{student.name}</td><td>{student.score}</td><td>{student.passed ? '及格' : '不及格'}</td></tr>))}</tbody></table></div>)
}

实践任务:
创建一个商品列表组件,支持按价格过滤、排序,显示库存状态

实践代码:

// 商品列表组件
function GoodList({ tag }) {// 商品列表数据const goods = [{ id: 1, name: '苹果', price: 150, inStock: true },{ id: 2, name: '香蕉', price: 100, inStock: false },{ id: 3, name: '橘子', price: 50, inStock: true }]// 按照价格从低到高排序const sg = [...goods].sort((a, b) => a.price - b.price);// 过滤出有库存的商品const filteredGoods = [...goods].filter(good => good.inStock);// 筛选出价格在100元以下的商品const cheapGoods = [...goods].filter(good => good.price < 100);return (<div>{tag === 1 ? (<><p>按照价格从低到高排序</p><ul>{sg.map(good => (<li key={good.id}>{good.name} - ¥{good.price} - {good.inStock ? '有货' : '无货'}</li>))}</ul></>) : tag === 2 ? (<><p>过滤出有库存的商品</p><ul>{filteredGoods.map(good => (<li key={good.id}>{good.name} - ¥{good.price} - {good.inStock ? '有货' : '无货'}</li>))}</ul></>) : (<><p>筛选出价格在100元以下的商品</p><ul>{cheapGoods.map(good => (<li key={good.id}>{good.name} - ¥{good.price} - {good.inStock ? '有货' : '无货'}</li>))}</ul></>)}</div>)
}export default GoodList// 使用演示
import './App.css'import GoodList from './components/GoodList'function App() {return (<>{/* GoodList */}<GoodList tag={1} /><GoodList tag={2} /><GoodList tag={3} /></>)
}export default App

实践结果:

QQ_1759202496710


6️⃣ 实践项目:待办事项列表(2小时)

项目需求

创建一个静态版本的待办事项列表应用,包含以下功能:

  1. 显示待办事项列表
  2. 显示完成/未完成状态
  3. 统计总数、已完成数、未完成数
  4. 根据状态筛选显示(全部/未完成/已完成)
  5. 优先级标签显示
组件结构设计
TodoApp (容器组件)
├── Header (头部标题)
├── TodoInput (输入框 - 暂不实现功能)
├── FilterBar (筛选栏)
├── TodoList (列表容器)
│   └── TodoItem (单个待办项)
└── Footer (统计信息)
完整代码实现

准备数据(App.jsx):

import './App.css'
import Header from './components/Header'
import TodoInput from './components/TodoInput'
import FilterBar from './components/FilterBar'
import TodoList from './components/TodoList'
import Footer from './components/Footer'function App() {// 模拟数据(明天会用useState管理)const todos = [{ id: 1, text: '学习React基础', completed: true, priority: 'high' },{ id: 2, text: '完成待办事项项目', completed: false, priority: 'high' },{ id: 3, text: '阅读React文档', completed: false, priority: 'medium' },{ id: 4, text: '练习JSX语法', completed: true, priority: 'low' },{ id: 5, text: '理解组件和Props', completed: false, priority: 'medium' }]// 当前筛选条件(明天会用useState管理)const currentFilter = 'all' // all | active | completed// 根据筛选条件过滤todosconst filteredTodos = todos.filter(todo => {if (currentFilter === 'active') return !todo.completedif (currentFilter === 'completed') return todo.completedreturn true})// 统计数据const stats = {total: todos.length,completed: todos.filter(t => t.completed).length,active: todos.filter(t => !t.completed).length}return (<div className="app"><Header /><div className="todo-container"><TodoInput /><FilterBar currentFilter={currentFilter} /><TodoList todos={filteredTodos} /><Footer stats={stats} /></div></div>)
}export default App

创建components目录和各个组件:

components/Header.jsx:

function Header() {return (<header className="header"><h1>📝 我的待办事项</h1><p>今天也要加油哦!</p></header>)
}export default Header

components/TodoInput.jsx:

function TodoInput() {return (<div className="todo-input"><inputtype="text"placeholder="添加新的待办事项..."disabled/><button disabled>添加</button><p className="hint">💡 明天我们会让它动起来!</p></div>)
}export default TodoInput

components/FilterBar.jsx:

function FilterBar({ currentFilter }) {const filters = [{ key: 'all', label: '全部' },{ key: 'active', label: '未完成' },{ key: 'completed', label: '已完成' }]return (<div className="filter-bar">{filters.map(filter => (<buttonkey={filter.key}className={currentFilter === filter.key ? 'active' : ''}disabled>{filter.label}</button>))}</div>)
}export default FilterBar

components/TodoList.jsx:

import TodoItem from './TodoItem'function TodoList({ todos }) {if (todos.length === 0) {return (<div className="empty-state"><p>🎉 暂无待办事项</p></div>)}return (<ul className="todo-list">{todos.map(todo => (<TodoItem key={todo.id} todo={todo} />))}</ul>)
}export default TodoList

components/TodoItem.jsx:

function TodoItem({ todo }) {// 优先级颜色映射const priorityColors = {high: '#ff4d4f',medium: '#faad14',low: '#52c41a'}// 优先级文本映射const priorityLabels = {high: '高',medium: '中',low: '低'}return (<li className={`todo-item ${todo.completed ? 'completed' : ''}`}><div className="todo-check"><inputtype="checkbox"checked={todo.completed}disabled/></div><div className="todo-content"><span className="todo-text">{todo.text}</span><spanclassName="todo-priority"style={{backgroundColor: priorityColors[todo.priority],color: 'white',padding: '2px 8px',borderRadius: '4px',fontSize: '12px'}}>{priorityLabels[todo.priority]}优先级</span></div><div className="todo-actions"><button className="btn-delete" disabled>删除</button></div></li>)
}export default TodoItem

components/Footer.jsx:

function Footer({ stats }) {return (<footer className="footer"><div className="stats"><span>总计:<strong>{stats.total}</strong></span><span>已完成:<strong>{stats.completed}</strong></span><span>未完成:<strong>{stats.active}</strong></span></div><div className="progress"><div className="progress-bar"><divclassName="progress-fill"style={{width: `${stats.total > 0 ? (stats.completed / stats.total * 100) : 0}%`}}/></div><span className="progress-text">完成度:{stats.total > 0 ? Math.round(stats.completed / stats.total * 100) : 0}%</span></div></footer>)
}export default Footer
样式文件(App.css)
* {margin: 0;padding: 0;box-sizing: border-box;
}body {font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);min-height: 100vh;padding: 20px;
}.app {max-width: 800px;margin: 0 auto;
}/* Header */
.header {text-align: center;color: white;margin-bottom: 30px;
}.header h1 {font-size: 48px;margin-bottom: 10px;
}.header p {font-size: 18px;opacity: 0.9;
}/* Todo Container */
.todo-container {background: white;border-radius: 16px;padding: 30px;box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}/* Todo Input */
.todo-input {margin-bottom: 20px;
}.todo-input input {width: calc(100% - 90px);padding: 12px 16px;font-size: 16px;border: 2px solid #e0e0e0;border-radius: 8px;outline: none;transition: border-color 0.3s;
}.todo-input input:focus {border-color: #667eea;
}.todo-input input:disabled {background: #f5f5f5;cursor: not-allowed;
}.todo-input button {width: 80px;padding: 12px;margin-left: 10px;font-size: 16px;background: #667eea;color: white;border: none;border-radius: 8px;cursor: pointer;transition: background 0.3s;
}.todo-input button:hover:not(:disabled) {background: #5568d3;
}.todo-input button:disabled {background: #ccc;cursor: not-allowed;
}.todo-input .hint {margin-top: 10px;font-size: 14px;color: #999;
}/* Filter Bar */
.filter-bar {display: flex;gap: 10px;margin-bottom: 20px;padding-bottom: 20px;border-bottom: 2px solid #f0f0f0;
}.filter-bar button {flex: 1;padding: 10px;font-size: 14px;background: #f5f5f5;border: 2px solid transparent;border-radius: 8px;cursor: pointer;transition: all 0.3s;
}.filter-bar button.active {background: #667eea;color: white;border-color: #667eea;
}.filter-bar button:hover:not(:disabled):not(.active) {background: #e8e8e8;
}/* Todo List */
.todo-list {list-style: none;margin-bottom: 20px;
}.empty-state {text-align: center;padding: 60px 20px;color: #999;font-size: 18px;
}/* Todo Item */
.todo-item {display: flex;align-items: center;gap: 12px;padding: 16px;margin-bottom: 12px;background: #fafafa;border-radius: 8px;transition: all 0.3s;
}.todo-item:hover {background: #f0f0f0;transform: translateX(4px);
}.todo-item.completed {opacity: 0.6;
}.todo-item.completed .todo-text {text-decoration: line-through;color: #999;
}.todo-check input[type="checkbox"] {width: 20px;height: 20px;cursor: pointer;
}.todo-content {flex: 1;display: flex;align-items: center;gap: 12px;
}.todo-text {font-size: 16px;color: #333;
}.todo-priority {font-size: 12px;white-space: nowrap;
}.todo-actions button {padding: 6px 12px;font-size: 14px;background: #ff4d4f;color: white;border: none;border-radius: 6px;cursor: pointer;transition: background 0.3s;
}.todo-actions button:hover:not(:disabled) {background: #ff7875;
}.todo-actions button:disabled {background: #ccc;cursor: not-allowed;
}/* Footer */
.footer {padding-top: 20px;border-top: 2px solid #f0f0f0;
}.stats {display: flex;justify-content: space-around;margin-bottom: 20px;font-size: 16px;color: #666;
}.stats strong {color: #667eea;font-size: 20px;margin-left: 5px;
}.progress {text-align: center;
}.progress-bar {width: 100%;height: 8px;background: #f0f0f0;border-radius: 4px;overflow: hidden;margin-bottom: 10px;
}.progress-fill {height: 100%;background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);transition: width 0.3s ease;
}.progress-text {font-size: 14px;color: #999;
}
运行结果
QQ_1759203581915

📝 今日总结

关键知识点

  1. JSX = JavaScript + XML

    • {}嵌入JavaScript表达式
    • className代替classstyle用对象
    • 条件渲染用三元表达式或&&
    • 列表渲染用map(),必须提供key
  2. 函数组件

    • 组件名首字母大写
    • 通过props接收数据
    • props只读,不可修改
  3. Vue → React思维转换

    • 忘掉指令,拥抱JavaScript
    • 一切皆JavaScript表达式

完成检查清单

  • ✅ 创建React项目并理解项目结构
  • ✅ 掌握JSX基础语法
  • ✅ 理解函数组件和Props传递
  • ✅ 实现条件渲染(三元、逻辑与、提前return)
  • ✅ 实现列表渲染(map、key)
  • ✅ 完成待办事项静态版本

📚 课后作业

必做

  1. 完善待办事项样式

    • 调整颜色方案
    • 添加hover效果
    • 优化响应式布局
  2. 添加功能

    • 显示创建时间
    • 添加标签分类
    • 显示紧急程度图标

选做

  1. 创建新组件

    • 个人信息卡片(头像、姓名、简介)
    • 文章列表(标题、摘要、标签)
    • 天气卡片(城市、温度、图标)
  2. 思考题

    • 如果要让待办事项支持编辑,需要什么?
    • 如何实现拖拽排序?
    • 组件如何与后端API交互?

🚀 明天预告

Day 2: 状态管理与事件处理

明天我们将学习:

  • useState Hook - 让组件拥有自己的状态
  • 事件处理 - 响应用户操作
  • 表单处理 - 处理用户输入
  • 组件通信 - 父传子、子传父

完成后,我们的待办事项将真正"动起来"!


加油,訾博!第一天的学习辛苦了! 💪

http://www.dtcms.com/a/426789.html

相关文章:

  • Hotfixes数据库工作原理、机制和应用流程
  • 网站建设面试表wordpress建m域名网站
  • Node.js面试题及详细答案120题(93-100) -- 错误处理与调试篇
  • pc端js动态调用提示音音频报错的问题解决
  • 网站的建设特色网站开发培训哪家好
  • C# 中的 简单工厂模式 (Simple Factory)
  • Docker linux 离线部署springcloud
  • 第 2 天:搭建 C 语言开发环境 ——VS Code/Dev-C++/Code::Blocks 安装与配置全指南
  • 基于 Celery 的分布式文件监控系统
  • CATIA二次开发(2)C#启用AOT
  • Linux 驱动开发与内核通信机制——超详细教程
  • 【langgraph】本地部署方法及实例分析
  • Linux入门指南:从零掌握基础指令
  • 做笔记的网站源码江永网站建设
  • 是时候重启了:AIGC将如何重构UI设计师的学习路径与知识体系?
  • uniapp 请求接口封装和使用
  • AIGC重构数据可视化:你是进化中的“驯兽师”还是被替代的“画图工”?
  • Apache Doris 内部数据裁剪与过滤机制的实现原理
  • 专业做网站流程小程序开发步骤大全
  • C语言基础之指针2
  • 淘客网站怎么做 知乎wordpress淘宝联盟插件
  • flink工作流程
  • openHarmony之storage_daemon:分区挂载与设备节点管理机制讲解
  • 建站怎么赚钱个人官方网站怎么建设
  • 学习笔记093——Windows系统如何定时备份远程服务器的mysql文件到本地?
  • 操作系统内核架构深度解析:从单内核、微内核到鸿蒙分布式设计
  • MySQL 架构全景解析
  • .NET MVC中实现后台商品列表功能
  • oracle logwr,ckpt,dbwn 如何协同工作的
  • C# 网络通讯核心知识点笔记