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 实战)
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 结合)
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和columnsreturn <datatable tableData="{userData}" columns="{userColumns}" />;
};export default UserManagePage;
3. React 事件处理:和原生 JS 有啥不一样?
核心特点
React 的“合成事件”(SyntheticEvent)是对原生 DOM 事件的封装,目的是解决“跨浏览器兼容”问题——国内开发者不用再写addEventListener
兼容 IE,直接用onClick
这类驼峰命名的事件即可。
另外,React 事件处理有两个关键点:
- 事件处理函数需要“正确绑定 this”(类组件中常见,函数组件用箭头函数可避免);
- 传参时需用“函数包裹”(如
onClick={() => handleDelete(id)}
),避免直接执行函数。
国内项目示例:表单提交与按钮点击(高频场景)
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
、三元运算符、&&)实现。
国内项目示例:多场景条件渲染(覆盖高频需求)
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 实战)
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 场景(基础+引用类型)
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
的作用就是“管理副作用”,它能控制“副作用何时执行”(组件挂载后、状态更新后、组件卸载前),避免副作用影响组件渲染逻辑。
国内项目示例:接口请求+清理副作用(高频场景)
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)
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>