目录
[1.1 什么是 React 中的 JSX?搞懂 React 专属语法-电商商品卡片](#1.1 什么是 React 中的 JSX?搞懂 React 专属语法-电商商品卡片)
[1.2 React 中的 State 与 Props:组件的"数据传递与状态管理"核心-后台表格分页](#1.2 React 中的 State 与 Props:组件的“数据传递与状态管理”核心-后台表格分页)
[1.3 React 事件处理:和原生 JS 有啥不一样?-登录注册](#1.3 React 事件处理:和原生 JS 有啥不一样?-登录注册)
[1.4 React 条件渲染:怎么优雅地控制 UI 显示?-](#1.4 React 条件渲染:怎么优雅地控制 UI 显示?-)
[1.5. React Context API:解决"属性透传"的痛点-切换主题属性透传](#1.5. React Context API:解决“属性透传”的痛点-切换主题属性透传)
[1.6. 如何正确使用 useState?React 状态管理的"入门 Hook"](#1.6. 如何正确使用 useState?React 状态管理的“入门 Hook”)
[1.7. 精通 useEffect:React 副作用处理的"核心 Hook"-副作用hook](#1.7. 精通 useEffect:React 副作用处理的“核心 Hook”-副作用hook)
[1.8. 用 useContext 避免属性透传:简化 Context 使用-避免属性透传](#1.8. 用 useContext 避免属性透传:简化 Context 使用-避免属性透传)
前言:
如果你已经是个有多年开发经验的工程师,对于后端和前端都有一些认识和理解,并且可能独立开发过一个完整的基础性后端系统,在对js,css,html有了基础认识,尤其是对jQuery或者Vue这种有实操经验话,对于上手react 是非常有利的。没有耐心从头学起语法的小伙伴可以直接从实战出发快速了解其技巧和生态。下面推荐一个网址供大家参考学习。
1.葡萄城技术团队的关于如何构建基础前端系统的博客文章
React 基础核心概念(8 个)------从入门到能写业务组件(上)| 葡萄城技术团队 - 葡萄城技术团队 - 博客园 (cnblogs.com)
博客中的代码直接抄写或者放入vscode是会报错的,尤其是转义字符和语法错位。我花了点时间将其整理了一下。下面我将按照其原网站内容的顺序分享我的代码。
1.1 什么是 React 中的 JSX?搞懂 React 专属语法-电商商品卡片
javascript
import React from 'react';
// 引入国内常用的UI组件库(如Ant Design)
import { Card, Button, Tag } from 'antd';
// https://www.cnblogs.com/powertoolsteam/p/19112516
// 商品卡片组件:接收商品数据作为props
const ProductCard = ({ product }) => {
console.log(product);
const { id, name, price, stock, isDiscount } = product;
console.log(id, name, price, stock, isDiscount);
return (
<>
<Card key={id} title={name} cover={<img src={`/${id}.png`} alt={name}/>} style={{ "width": 240, "margin": '16px' }}/>
{isDiscount && <Tag color="red">限时折扣</Tag>}
<div style={{"margintop": 8}}>
<span style={{"color": '#f40', "fontsize": 16 }}>¥{price.toFixed(2)}</span>
<span style={{"marginleft": 8, "color": '#999' }}>库存:{stock}件</span>
</div>
<Button type="primary" danger style={{"width": '100%', "margintop": 12 }} onClick={() => console.log(`添加商品 ${name} 到购物车`)}>
加入购物车
</Button>
</>
);
};
// 使用组件:传入国内电商常见的商品数据
const ProductList = () => {
const products = [
{ id: 1, name: '华为Mate 60 Pro', price: 6999, stock: 120, isDiscount: false },
{ id: 2, name: '小米14', price: 4999, stock: 86, isDiscount: true },
];
return (
<div style={{"display": 'flex', flexwrap: 'wrap' }}>
{products.map(product => <ProductCard product={product} />)}
</div>
);
};
export default ProductList;
1.2 React 中的 State 与 Props:组件的"数据传递与状态管理"核心-后台表格分页
javascript
import React, { useState } from 'react';
import { Table, Pagination } from 'antd';
// 子组件:表格(接收父组件传的props)
const DataTable = ({ tableData, columns }) => {
// 子组件内部状态:当前页码(只能自己修改)
const [currentPage, setCurrentPage] = useState(1);
// 每页条数(国内后台常用10/20/50条)
const pageSize = 10;
// 处理页码变化(子组件内部更新state)
const handlePageChange = (page) => {
setCurrentPage(page);
};
// 分页逻辑:截取当前页数据
const currentData = tableData.slice(
(currentPage - 1) * pageSize,
currentPage * pageSize
);
return (
<div>
{/* 表格:用props传的columns和处理后的currentData */}
{/* 关闭表格自带分页,用自定义分页 */}
<Table columns={columns} dataSource={currentData} rowKey="id" pagination={false}></Table>
{/* 分页组件:状态currentPage控制显示 */}
<Pagination current={currentPage} pageSize={pageSize} total={tableData.length} onChange={handlePageChange} style={{"marginTop": 16, textAlign: 'right'}}/>
</div>
);
};
// 父组件:使用表格(传props给子组件)
const UserManagePage = () => {
// 父组件数据:用户列表(模拟接口返回的国内后台数据)
const userColumns = [
{ title: '用户ID', dataIndex: 'id', key: 'id' },
{ title: '用户名', dataIndex: 'username', key: 'username' },
{ title: '角色', dataIndex: 'role', key: 'role' },
{ title: '状态', dataIndex: 'status', key: 'status', render: (status) => status ? '启用' : '禁用' },
];
const userData = [
{ id: 1, username: 'zhangsan', role: '管理员', status: true },
{ id: 2, username: 'lisi', role: '普通用户', status: true },
// ... 更多数据
];
// 父组件传给子组件的props:tableData和columns
return <DataTable tableData={userData} columns={userColumns} />;
};
export default UserManagePage;
1.3 React 事件处理:和原生 JS 有啥不一样?-登录注册
javascript
import React, { useState } from 'react';
import { Form, Input, Button, message } from 'antd';
const LoginForm = () => {
const [loading, setLoading] = useState(false); // 登录加载状态
// 1. 表单提交事件(带参数,需用箭头函数包裹)
const handleSubmit = async (values) => {
setLoading(true); // 点击提交后显示加载状态
try {
// 模拟国内接口请求(如调用后端登录接口)
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values),
});
const data = await res.json();
if (data.success) {
message.success('登录成功!');
// 跳转到首页(国内常用react-router)
// navigate('/home');
} else {
message.error(data.message || '登录失败');
}
} catch (error) {
message.error('网络错误,请重试:', error);
} finally {
setLoading(false); // 无论成功失败,关闭加载状态
}
};
// 2. 普通按钮点击事件(无参数)
const handleReset = () => {
// 重置表单(Ant Design Form方法)
console.log(Form);
// Form.useForm[0].resetFields();
};
// 表单提交事件(React合成事件)
return (
<Form name="login_form" layout="vertical" onFinish={handleSubmit} style={{"width": 350, "margin": '0 auto' }}>
<Form.Item name="username" label="用户名" rules={[{"required": true, "message": '请输入用户名' }]}>
<Input placeholder="请输入用户名"/>
</Form.Item>
<Form.Item name="password" label="密码" rules={[{"required": true, "message": '请输入密码' }]}>
<Input.Password placeholder="请输入密码" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={loading} style={{"marginright": 8 }}>
登录
</Button>
{/* <Button onClick={handleReset}>重置</Button> */}
<Button htmlType="reset" onClick={handleReset}>重置</Button>
</Form.Item>
</Form>
);
};
export default LoginForm;
1.4 React 条件渲染:怎么优雅地控制 UI 显示?-
javascript
import React, { useState, useEffect } from 'react';
import { Spin, Empty, List, Avatar } from 'antd';
const MessageList = () => {
// 状态:加载中、消息数据、是否登录
const [loading, setLoading] = useState(true);
const [messages, setMessages] = useState([]);
const [isLogin, setIsLogin] = useState(false);
// 模拟接口请求(国内后台常见的"消息列表"接口)
useEffect(() => {
const fetchMessages = async () => {
try {
const res = await fetch('/api/messages');
const data = await res.json();
setMessages(data.list);
setIsLogin(data.isLogin); // 接口返回登录状态
} catch (error) {
console.error('获取消息失败:', error);
} finally {
setLoading(false);
}
};
fetchMessages();
}, []);
// 1. 加载中:显示Spin组件(国内常用加载组件)
if (loading) {
return <Spin size="large" style={{"display": 'block', "margin": '40px auto' }} />;
}
// 2. 未登录:显示"请登录"提示(三元运算符)
if (!isLogin) {
return (
<div style={{"textalign": 'center', padding: 40 }}>
<h3>请先登录查看消息</h3>
<button type="primary" onClick={() => setIsLogin(true)}>
立即登录
</button>
</div>
);
}
return (
<div style={{"width": 300, "margin": '0 auto' }}>
<h3>我的消息</h3>
{messages.length > 0 ? (
<List datasource={messages} renderitem={(item) => (
<List.Item>
<List.Item.Meta Avatar={item.sender[0]}
title={item.sender}
description={item.content}
/>
<span style={{"color": '#999' }}>{item.time}</span>
</List.Item>
)}
/>
) : (
// 4. 无消息:显示Empty组件(国内常用空状态组件)
<Empty description="暂无消息" />
)}
</div>
);
};
export default MessageList;
1.5. React Context API:解决"属性透传"的痛点-切换主题属性透传
javascript
import React, { useState, useEffect } from 'react';
import { Spin, Empty, List, Avatar } from 'antd';
const MessageList = () => {
// 状态:加载中、消息数据、是否登录
const [loading, setLoading] = useState(true);
const [messages, setMessages] = useState([]);
const [isLogin, setIsLogin] = useState(false);
// 模拟接口请求(国内后台常见的"消息列表"接口)
useEffect(() => {
const fetchMessages = async () => {
try {
const res = await fetch('/api/messages');
const data = await res.json();
setMessages(data.list);
setIsLogin(data.isLogin); // 接口返回登录状态
} catch (error) {
console.error('获取消息失败:', error);
} finally {
setLoading(false);
}
};
fetchMessages();
}, []);
// 1. 加载中:显示Spin组件(国内常用加载组件)
if (loading) {
return <Spin size="large" style={{"display": 'block', "margin": '40px auto' }} />;
}
// 2. 未登录:显示"请登录"提示(三元运算符)
if (!isLogin) {
return (
<div style={{"textalign": 'center', padding: 40 }}>
<h3>请先登录查看消息</h3>
<button type="primary" onClick={() => setIsLogin(true)}>
立即登录
</button>
</div>
);
}
return (
<div style={{"width": 300, "margin": '0 auto' }}>
<h3>我的消息</h3>
{messages.length > 0 ? (
<List datasource={messages} renderitem={(item) => (
<List.Item>
<List.Item.Meta Avatar={item.sender[0]}
title={item.sender}
description={item.content}
/>
<span style={{"color": '#999' }}>{item.time}</span>
</List.Item>
)}
/>
) : (
// 4. 无消息:显示Empty组件(国内常用空状态组件)
<Empty description="暂无消息" />
)}
</div>
);
};
export default MessageList;
1.6. 如何正确使用 useState?React 状态管理的"入门 Hook"
javascript
import React, { useState } from 'react';
import { Input, Button, Card, Space } from 'antd';
// 场景1:基础类型状态(数字/字符串)------计数器
const Counter = () => {
// 初始化状态:count=0,setCount是修改方法
const [count, setCount] = useState(0);
// 正确修改状态:用setter方法(接收新值或函数)
const increment = () => {
// 方式1:直接传新值(简单场景)
// setCount(count + 1);
// 方式2:传函数(依赖前一个状态,更安全)
setCount(prevCount => prevCount + 1);
};
const decrement = () => {
setCount(prevCount => Math.max(0, prevCount - 1)); // 避免负数
};
return (
<Card title="计数器(基础状态)" style={{"width": 300 }}>
<Space size="middle">
<Button onClick={decrement}>-</Button>
<span style={{"fontsize": 18 }}>{count}</span>
<Button onClick={increment}>+</Button>
</Space>
</Card>
);
};
// 场景2:引用类型状态(对象)------用户信息表单
const UserForm = () => {
// 初始化对象状态(国内表单常见的用户信息)
const [user, setUser] = useState({
name: '',
phone: '',
email: '',
});
// 正确修改对象状态:展开原对象,修改指定属性(生成新引用)
const handleInputChange = (e) => {
const { name, value } = e.target;
setUser(prevUser => ({
...prevUser, // 展开原对象,保留未修改的属性
[name]: value, // 修改当前输入的属性
}));
};
const handleSubmit = () => {
console.log('提交用户信息:', user);
// 调用接口提交数据...
};
return (
<Card title="用户信息表单(对象状态)" style={{"width": 400, "margintop": 16 }}>
<Space direction="vertical" size="large" style={{"width": '100%' }}>
{/* 与user对象的属性名一致 */}
<Input name="name" placeholder="请输入姓名" value={user.name} onChange={handleInputChange}/>
<Input name="phone" placeholder="请输入手机号" value={user.phone} onChange={handleInputChange}/>
<Input name="email" placeholder="请输入邮箱" value={user.email} onChange={handleInputChange}/>
<Button type="primary" onClick={handleSubmit} style={{"width": '100%' }}>
提交
</Button>
</Space>
</Card>
);
};
// 组合使用两个组件
const UseStateDemo = () => {
return (
<div style={{"padding": 24 }}>
<Counter />
<UserForm />
</div>
);
};
export default UseStateDemo;
1.7. 精通 useEffect:React 副作用处理的"核心 Hook"-副作用hook
javascript
import React, { useState, useEffect } from 'react';
import { List, Spin, message, Button } from 'antd';
const ArticleList = () => {
const [articles, setArticles] = useState([]);
const [loading, setLoading] = useState(true);
const [page, setPage] = useState(1); // 页码状态
// 副作用1:请求文章列表(依赖page,页码变时重新请求)
useEffect(() => {
// 定义异步请求函数(useEffect回调不能直接是async)
const fetchArticles = async () => {
setLoading(true);
try {
// 模拟国内博客平台接口(如掘金、CSDN文章列表)
const res = await fetch(`/api/articles?page=${page}&size=10`);
const data = await res.json();
if (data.success) {
setArticles(data.list);
} else {
message.error('获取文章失败');
}
} catch (error) {
message.error('网络错误,请重试');
console.error('请求失败:', error);
} finally {
setLoading(false);
}
console.log(articles);
};
fetchArticles(); // 执行请求
}, [page]); // 依赖数组:只有page变化时,才重新执行副作用
// 副作用2:监听窗口滚动(组件挂载时监听,卸载时清除)
useEffect(() => {
const handleScroll = () => {
// 滚动到底部时加载下一页(国内列表常见的"无限滚动"前置逻辑)
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const scrollHeight = document.documentElement.scrollHeight;
const clientHeight = document.documentElement.clientHeight;
if (scrollTop + clientHeight >= scrollHeight - 100 && !loading) {
setPage(prevPage => prevPage + 1);
}
};
// 绑定滚动事件(副作用)
window.addEventListener('scroll', handleScroll);
// 清理函数:组件卸载时执行,避免内存泄漏
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, [loading]); // 依赖loading:加载中时不触发下一页
return (
<div style={{"width": 800, "margin": '0 auto', "padding": 24 }}>
<h2>最新技术文章</h2>
{loading && page === 1 ? ( // 第一页加载时显示全屏加载
<Spin size="large" style={{"display": 'block', "margin": '40px auto' }} />
) : (<List dataSource={articles} renderItem={(item) => (
<List.Item key="read" actions={[<a>阅读全文, <span key="view">{item.viewCount} 阅读</span></a>]}>
<List.Item.Meta title={<a href={`/articles/${item.id}`}>{item.title}
description={
<div>
<span>{item.author}</span> · <span>{item.publishTime}</span>
</div>
}
</a>}>
</List.Item.Meta>
</List.Item>
)}>
</List>)}
</div>
// let s = {"id": 1, "title": "a", "viewCount": 1200, "author": "韩小顺", publishTime: "202510053423"}
)
}
export default ArticleList;
1.8. 用 useContext 避免属性透传:简化 Context 使用-避免属性透传
javascript
import React, { createContext, useContext, useState, useEffect } from 'react';
import { Button, Avatar, Dropdown, Menu } from 'antd';
// 1. 创建用户Context
const UserContext = createContext();
// 2. 创建用户Provider(管理全局用户状态)
export const UserProvider = ({ children }) => {
const [user, setUser] = useState(null); // 用户信息(null表示未登录)
// 模拟初始化:从localStorage获取登录状态(国内项目常用)
useEffect(() => {
const savedUser = localStorage.getItem('userInfo');
if (savedUser) {
setUser(JSON.parse(savedUser));
}
}, []);
// 登录方法:模拟接口登录后保存用户信息
const login = async (username, password) => {
// 模拟登录接口
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
const data = await res.json();
if (data.success) {
setUser(data.user);
// 保存到localStorage,刷新页面不丢失
localStorage.setItem('userInfo', JSON.stringify(data.user));
}
return data;
};
// 退出方法:清除用户信息
const logout = () => {
setUser(null);
localStorage.removeItem('userInfo');
// 调用后端退出接口(如清除token)
fetch('/api/logout', { method: 'POST' });
};
// 提供给子组件的数据和方法
const userValue = {
user,
isLogin: !!user, // 是否登录(布尔值)
login,
logout,
};
return <UserContext.Provider value={userValue}>{children}</UserContext.Provider>;
};
// 3. 自定义Hook:简化useContext调用
const useUser = () => useContext(UserContext);
// 4. 头部组件:显示用户信息或登录按钮(深层组件,无需透传)
const Header = () => {
const { user, isLogin, login, logout } = useUser();
// 未登录:显示登录按钮
if (!isLogin) {
const handleLogin = async () => {
// 模拟输入用户名密码(实际项目用登录表单)
const res = await login('admin', '123456');
if (res.success) {
console.log('登录成功');
} else {
console.error('登录失败:', res.message);
}
};
return (
<div style={{"display": 'flex', "justifycontent": 'flex-end', "padding": 16 }}>
<Button onClick={handleLogin} type="primary">
登录
</Button>
</div>
);
}
// 已登录:显示用户头像和下拉菜单
const menu = (
<Menu items={[{ key: 'profile', label: '个人中心' }, 'settings', '账号设置', 'logout', '退出登录',{onClick: logout} ]}></Menu>
);
return (
<div style={{"display": 'flex', "justifycontent": 'flex-end', "padding": 16, "alignitems": 'center' }}>
<span style={{"marginright": 8 }}>欢迎,{user.nickname}</span>
<Dropdown overlay={menu}>
<Avatar src={user.avatar || 'images default-avatar.png'}>
{user.nickname[0]}
</Avatar>
</Dropdown>
</div>
);
};
// 5. 页面组件:使用用户信息(无需透传)
const Dashboard = () => {
const { user } = useUser();
return (
<div style={{"padding": 24 }}>
<h2>仪表盘</h2>
<p>当前登录用户:{user.username}</p>
<p>用户角色:{user.role}</p>
</div>
);
};
// 6. 根组件:用Provider包裹
const App = () => {
return (
<UserProvider>
<Header></Header>
<Dashboard />
</UserProvider>
);
};
export default App;
提示:
如果按照原博客内容的话将遇到如下不同种类的问题,在解决这些问题的过程中,也能增强对react语法的掌握和理解。图片仅作为示例,因为我在上述代码中已经解决大部分了,可以直接用的代码和原代码进行比较。但在第6,7代码片段中我的环境仍然有未处理的问题,不过貌似不影响整体执行。
例如:














2.环境介绍
vite 构建,版本是7.1.7

antd 版本:1.0.7
react 版本:19.1.1

图片放在了public目录下,引用的时候直接写上图片名字就可以


最新的官方react Vite脚手架是index.html -->main,js->App.jsx,而并不像很多博客提到的有index.jsx。当然你也可以自定义文件名称。
