React 基础核心概念(8 个)------从入门到能写业务组件(上)
前言:为什么要先掌握这些基础概念?
对国内开发者来说,React 是开发中后台系统、电商前端、移动端 H5 的"刚需技能"。但很多人刚学就陷入"会写 JSX 却不懂原理"的困境------比如不知道为什么状态更新后页面不刷新,或者写组件时反复遇到"属性透传"的麻烦。
这篇整理的 8 个基础概念,是 React 开发的"地基":从 JSX 语法到最常用的 Hooks,每个都配了国内项目常见的示例(比如商品列表渲染、用户登录状态管理),帮你跳过"照抄代码却不懂逻辑"的坑,真正能独立写业务组件。
1. 什么是 React 中的 JSX?搞懂 React 专属语法
定义与作用
JSX(JavaScript XML)是 React 的"模板语法",允许你在 JS 代码里写类似 HTML 的结构。很多初学者以为它是"HTML 的变体",其实它最终会被 Babel 编译成React.createElement()
函数,生成 React 元素(虚拟 DOM 节点)。
国内开发场景中,JSX 是写组件的"标配"------无论是电商的商品卡片,还是后台的表格组件,都靠 JSX 描述 UI 结构。
国内项目示例:电商商品卡片(JSX 实战)
JavaScript
import React from 'react';
// 引入国内常用的UI组件库(如Ant Design)
import { Card, Button, Tag } from 'antd';
// 商品卡片组件:接收商品数据作为props
const ProductCard = ({ product }) => {
const { id, name, price, stock, isDiscount } = product;
return (
// JSX支持嵌入JS表达式(用{}包裹)
<card key="{id}" title="{name}" cover="{<img" src="{`/images/products/${id}.jpg`}" 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;
注意点(国内开发者常踩的坑)
- 不能直接写
class
:需用className
(因为class
是 JS 关键字); - 自闭合标签必须写
/
:比如<img src="">
,不像 HTML 里可以省略; - 只能有一个根节点:如果有多个元素,需用
<div>
或<react.fragment>
(简写<>
)包裹。
2. React 中的 State 与 Props:组件的"数据传递与状态管理"核心
定义与区别
- Props(属性) :父组件传给子组件的数据,类似"函数参数",只读不可改(子组件不能直接修改 props);
- State(状态) :组件内部管理的"动态数据"(如输入框内容、弹窗显示/隐藏),可修改但需用 setter 方法 (如
setCount
),修改后会触发组件重新渲染。
国内开发中,这两者是"父子组件通信"和"组件内部状态控制"的核心------比如后台管理系统中,父组件传"表格数据"给子表格组件(用 props),子组件内部管理"分页页码"(用 state)。
国内项目示例:后台表格组件(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;
3. React 事件处理:和原生 JS 有啥不一样?
核心特点
React 的"合成事件"(SyntheticEvent)是对原生 DOM 事件的封装,目的是解决"跨浏览器兼容"问题------国内开发者不用再写addEventListener
兼容 IE,直接用onClick
这类驼峰命名的事件即可。
另外,React 事件处理有两个关键点:
- 事件处理函数需要"正确绑定 this"(类组件中常见,函数组件用箭头函数可避免);
- 传参时需用"函数包裹"(如
onClick={() => handleDelete(id)}
),避免直接执行函数。
国内项目示例:表单提交与按钮点击(高频场景)
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('网络错误,请重试');
} finally {
setLoading(false); // 无论成功失败,关闭加载状态
}
};
// 2. 普通按钮点击事件(无参数)
const handleReset = () => {
// 重置表单(Ant Design Form方法)
Form.useForm()[0].resetFields();
};
return (
<form name="login_form" layout="vertical" onFinish="{handleSubmit}" 表单提交事件(React合成事件) 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>
</form.item>
</form>
);
};
export default LoginForm;
4. React 条件渲染:怎么优雅地控制 UI 显示?
核心场景
国内开发中,条件渲染几乎无处不在:用户登录/未登录显示不同按钮、接口请求中显示"加载中"、数据为空显示"暂无数据"......React 没有专门的"条件渲染语法",而是用 JS 的逻辑(如if-else
、三元运算符、&&)实现。
国内项目示例:多场景条件渲染(覆盖高频需求)
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>
{/* 3. 有消息:渲染列表(&& 语法) */}
{messages.length > 0 ? (
<list datasource="{messages}" renderitem="{(item)" => (
<list.item>
<list.item.meta avatar="{<Avatar">{item.sender[0]}}
title={item.sender}
description={item.content}
/>
<span style="{{" color: '#999' }}>{item.time}</span>
</list.item.meta></list.item>
)}
/>
) : (
// 4. 无消息:显示Empty组件(国内常用空状态组件)
<empty description="暂无消息" />
)}
</list></div>
);
};
export default MessageList;
5. React Context API:解决"属性透传"的痛点
什么是属性透传?
当组件层级很深时(比如"App→Layout→Sidebar→Menu→MenuItem"),如果要把"主题色"从 App 传给 MenuItem,需要每层组件都传 props------这就是"属性透传",国内开发者常称之为"props drilling",代码冗余且难维护。
Context API 的作用就是"跨层级传递数据",不用手动一层层传 props,适合传递"全局数据"(如主题、用户信息、语言设置)。
国内项目示例:后台系统主题切换(Context 实战)
JavaScript
import React, { createContext, useContext, useState } from 'react';
import { Button, Divider, Typography } from 'antd';
// 1. 创建Context(主题上下文)
const ThemeContext = createContext();
// 2. 创建Provider(提供主题数据和修改方法)
export const ThemeProvider = ({ children }) => {
// 主题状态:light(默认)/dark(暗黑模式)
const [theme, setTheme] = useState('light');
// 切换主题的方法
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
// 主题样式(根据主题切换)
const themeStyles = {
light: { background: '#fff', color: '#333' },
dark: { background: '#141414', color: '#fff' },
};
// 提供上下文数据(children是子组件树)
return (
<themecontext.provider value="{{" theme, toggletheme, themestyles }}>
<div style="{themeStyles[theme]}" classname="{`theme-${theme}`}">
{children}
</div>
</themecontext.provider>
);
};
// 3. 自定义Hook(简化使用Context,避免重复useContext)
const useTheme = () => useContext(ThemeContext);
// 4. 子组件:主题切换按钮(直接用useTheme获取全局主题)
const ThemeToggleButton = () => {
const { theme, toggleTheme } = useTheme();
return (
<button onclick="{toggleTheme}" type="primary">
当前主题:{theme === 'light' ? '浅色' : '暗黑'} → 切换
</button>
);
};
// 5. 深层子组件:页面内容(直接用主题样式)
const PageContent = () => {
const { themeStyles } = useTheme();
return (
<div style="{{" padding: 24, minheight: 300 }}>
<typography.title level="{2}">后台管理首页</typography.title>
<typography.paragraph style="{themeStyles}">
这是使用Context API实现的主题切换功能,所有组件都能直接获取主题数据,无需属性透传。
</typography.paragraph>
</div>
);
};
// 6. 根组件:使用Provider包裹
const App = () => {
return (
<themeprovider>
<div style="{{" minheight: '100vh' }}>
<themetogglebutton />
<divider />
<pagecontent />
</div>
</themeprovider>
);
};
export default App;
6. 如何正确使用 useState?React 状态管理的"入门 Hook"
核心作用
useState
是 React 最基础的 Hook,用于在函数组件中"管理状态"(如计数器、表单输入、弹窗显示)。它返回一个"状态变量"和"修改状态的函数"(setter),修改状态后会触发组件重新渲染。
国内开发者常踩的坑:直接修改状态(如count = count + 1
)不会触发渲染,必须用 setter 方法;如果状态是"引用类型"(对象/数组),需要传"新的引用"(如setUser({ ...user, name: '新名字' })
)。
国内项目示例:两种常见 useState 场景(基础+引用类型)
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%' }}>
<input name="name" 与user对象的属性名一致 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;
7. 精通 useEffect:React 副作用处理的"核心 Hook"
什么是副作用?
"副作用"是指组件中"不直接参与 UI 渲染"的操作,比如:
- 调用接口请求数据(国内开发最常见);
- 操作 DOM(如监听滚动事件);
- 设置定时器/清除定时器。
useEffect
的作用就是"管理副作用",它能控制"副作用何时执行"(组件挂载后、状态更新后、组件卸载前),避免副作用影响组件渲染逻辑。
国内项目示例:接口请求+清理副作用(高频场景)
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);
}
};
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>]}
>
<list.item.meta title="{<a" href="{`/articles/${item.id}`}">{item.title}}
description={
<div>
<span>{item.author}</span> · <span>{item.publishTime}</span>
</div>
}
/>
</list.item.meta></list.item>
)}
/>
{/* 加载更多时显示底部加载 */}
{loading && page > 1 && <spin style="{{" display: 'block', margin: '20px auto' }} />}
)}
</list></div>
);
};
export default ArticleList;
8. 用 useContext 避免属性透传:简化 Context 使用
核心作用
useContext
是"使用 Context"的 Hook,它能让组件直接获取 Context 中的数据,不用再通过Context.Consumer
嵌套(旧写法)。结合createContext
和Provider
,就能彻底解决"属性透传"问题。
国内开发中,useContext
常和useState
配合使用------用useState
管理全局状态(如用户信息),用Context
传递状态和修改方法,让所有组件都能访问。
国内项目示例:全局用户状态管理(useContext+useState)
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;
上篇小结
这 8 个基础概念是 React 开发的"敲门砖":JSX 决定你能不能写 UI,State 和 Props 决定你能不能传数据,事件处理和条件渲染决定你能不能做交互,Context 和 useState/useEffect 则帮你解决"全局状态"和"副作用"的核心问题。
如果能熟练掌握这些概念,你已经能独立开发国内常见的业务组件(如表单、列表、卡片),下一篇我们会深入 React 进阶概念------从 DOM 操作到性能优化,帮你写出更高效、更易维护的 React 项目。</react.fragment>