文章目录
-
- React
-
- [1. 样式 className](#1. 样式 className)
- [2. React 常用 Hooks](#2. React 常用 Hooks)
-
- [1. useState](#1. useState)
- [2. useEffect](#2. useEffect)
- [3. useMemo](#3. useMemo)
- [4. useCallback](#4. useCallback)
- [5. useRef ------ 组件的 "持久化抽屉"](#5. useRef —— 组件的 “持久化抽屉”)
- [6. useContext------组件的 "共享公告板"](#6. useContext——组件的 “共享公告板”)
- [7. useReducer------ 复杂状态的 "管理经理"](#7. useReducer—— 复杂状态的 “管理经理”)
- [3. 判断语句](#3. 判断语句)
- [4. 事件点击](#4. 事件点击)
- [4. 父子组件传递](#4. 父子组件传递)
-
- [1. 父组件](#1. 父组件)
- [2. 子组件](#2. 子组件)
- AntDesgin
-
- modal弹窗
- [Tree 树](#Tree 树)
React
语法返回,得添加括号,例如:
javascript
return ({
<>
<span></span>
<span></span>
</>
})
1. 样式 className
样式书写
classNamestyle={``{width:'100vh',height:'200px'}}- 样式定义为变量【建议】
javascript
const styleObj = {width:'100vh',height:'200px'}
style = {styleObj}

创建
npx create-react-app my-react-app 【推荐】或npx create-next-app@latest或npm create vite@latest my-react-app -- --template react
2. React 常用 Hooks
1. useState
javascript
import React,{ useState } from 'react'
function StateFunction () {
const [name, setName] = useState('函数')
return (
<div onClick={ () => setName('我使用hooks变成这样了') }>
// setName也可以写入方法,如setName( val => val+'xxxx' )
这是一个函数式组件------------{name}
</div>
)
}
export default StateFunction
2. useEffect
-
useEffect又称副作用hooks。
-
作用:比如请求数据、设置定时器、操作 DOM 等。
-
执行时机:在渲染结束之后执行
-
什么是副作用?
- 副作用 ( side effect ): 数据获取,数据订阅,以及手动更改 React 组件中的 DOM 都属于副作用
- 因为我们渲染出的页面都是静态的,任何在其之后的操作都会对他产生影响,所以称之为副作用
-
使用:
- 1.第一个参数,接收一个函数作为参数
- 2.第二个参数,接收【依赖列表】,只有依赖更新时,才会执行函数
- 3.返回一个函数,先执行返回函数,再执行参数函数
javascript
function UserProfile() {
const [user, setUser] = useState(null);
// 组件一加载就请求用户数据(类似"订位")
useEffect(() => {
// 发起请求(副作用操作)
fetch('https://api.example.com/user')
.then(res => res.json())
.then(data => setUser(data));
// 组件消失时清理(类似"买单")
return () => {
// 比如清除定时器、取消请求等
};
}, []); // 空数组表示:只在组件第一次加载和卸载时执行
useEffect( () => {
console.log('2222函数式组件结束渲染')
},[num])
useEffect( () => {
console.log('2222函数式组件结束渲染')
},[num,val])
return <div>{user ? user.name : '加载中...'}</div>;
}
3. useMemo
使用useMemo可以传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值。简单来说,作用是让组件中的函数跟随状态更新(即优化函数组件中的功能函数)。
javascript
const [num, setNum] = useState(1)
const [age, setAge] = useState(18)
const getDoubleNum = useMemo( () => {
console.log(`获取双倍Num${num}`)
return 2 * num // 假设为复杂计算逻辑
},[num] )
return (
<div onClick={ () => { setAge( age => age+1 ) } }>
<br></br>
这是一个函数式组件------------num:{ getDoubleNum } // 注意这里没括号,因为是返回值
<br></br>
age的值为------------{ age }
<br></br>
</div>
)
4. useCallback
- 在子组件不需要父组件的值和函数的情况下,只需要使用memo函数包裹子组件即可
- 如果有函数传递给子组件,使用useCallback
- 缓存一个组件内的复杂计算逻辑需要返回值时,使用useMemo
js
import React, { useState, useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<input value={name} onChange={e => setName(e.target.value)} />
<Child handleClick={handleClick} />
<p>Count: {count}</p>
</div>
);
}
const Child = React.memo(({ handleClick }) => {
console.log('Child rendered');
return <button onClick={handleClick}>Click me</button>;
});
5. useRef ------ 组件的 "持久化抽屉"
- useRef就是返回一个子元素索引,此索引在整个生命周期中保持不变。作用也就是:长久保存数据。注意事项,保存的对象发生改变,不通知。属性变更不会重新渲染,
- 保存一个值,在整个生命周期中维持不变
- ref保存的对象发生改变,不会主动通知,属性变更不会重新渲染
js
function InputFocus() {
// 创建一个"抽屉"存输入框DOM
const inputRef = useRef(null);
return (
<div>
{/* 把输入框"放进抽屉" */}
<input ref={inputRef} />
{/* 点击按钮时,从"抽屉"里拿出来并聚焦 */}
<button onClick={() => inputRef.current.focus()}>聚焦输入框</button>
</div>
);
}
6. useContext------组件的 "共享公告板"
- 让组件之间 "共享数据",避免一层层传递(比如爷爷组件给孙子组件传数据,不用经过爸爸组件)。
javascript
// 1. 创建公告板(Context)
const ThemeContext = createContext('light');
// 2. 上层组件提供数据(贴公告)
function App() {
return (
<ThemeContext.Provider value="dark">
<Navbar /> {/* 子组件 */}
</ThemeContext.Provider>
);
}
// 3. 深层组件直接读取(看公告)
function Navbar() {
// 用 useContext 直接获取公告板内容
const theme = useContext(ThemeContext);
return <div style={{ background: theme === 'dark' ? 'black' : 'white' }}>导航栏</div>;
}
7. useReducer------ 复杂状态的 "管理经理"
- 状态逻辑较复杂(比如多个状态相互依赖、有多种更新方式)时,用它来统一管理,比 useState 更清晰。
- 生活例子:小商店(简单状态)老板自己记账就行(useState);大超市(复杂状态)需要专门的会计(useReducer)来处理进货、销售、退货等多种操作,账目更清晰。
- 注意事项:
- useReducer接收两个参数,第一个是reducer函数,第二个是初始状态
- reducer函数根据不同的action决定如何更新状态
- dispatch用于发送action从而更新状态
javascript
// 1. 定义"会计规则"(reducer函数:接收当前状态和操作,返回新状态)
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
return [...state, action.item]; // 添加商品
case 'REMOVE_ITEM':
return state.filter(item => item.id !== action.id); // 移除商品
default:
return state;
}
}
function ShoppingCart() {
// 2. 初始化"账本",[当前状态, 操作函数] = useReducer(规则, 初始值)
const [cart, dispatch] = useReducer(cartReducer, []);
return (
<div>
<button onClick={() => dispatch({ type: 'ADD_ITEM', item: { id: 1, name: '苹果' } })}>
加入苹果
</button>
<p>购物车:{cart.map(item => item.name).join(',')}</p>
</div>
);
}
3. 判断语句
- 三元表达式
javascript
<div>{selectType === 'player' ? '参赛者信息' : '设备信息'}</div>
javascript
<div className="card-header">
{selectType === 'player' ? (
<>
<span className="status">参赛中</span>
</>
) : (
<>
<span className="status">在线</span>
</>
)}
</div>
<span>
{playersInfo['gender'] === '男' ? (
<ManOutlined className="sex-icon" />
) : (
<WomanOutlined className="sex-icon" />
)}
</span>
- 使用判断语句
javascript
{panelStatus !== 'off' && (
<Button
className="toggle-btn"
type="primary"
size="small"
onClick={() => setInfoCollapsed(!infoCollapsed)}
icon={
infoCollapsed ? <DoubleLeftOutlined /> : <DoubleRightOutlined />
}
/>
)}
- 使用 CSS 控制显示隐藏
javascript
<Button
className={`toggle-btn ${panelStatus === 'off' ? 'hidden' : ''}`}
type="primary"
size="small"
onClick={() => setInfoCollapsed(!infoCollapsed)}
icon={
infoCollapsed ? <DoubleLeftOutlined /> : <DoubleRightOutlined />
}
/>
.hidden {
display: none;
}
- 使用内联样式
javascript
<Button
className="toggle-btn"
type="primary"
size="small"
style={{ display: panelStatus === 'off' ? 'none' : 'inline-block' }}
onClick={() => setInfoCollapsed(!infoCollapsed)}
icon={
infoCollapsed ? <DoubleLeftOutlined /> : <DoubleRightOutlined />
}
/>
4. 事件点击
此处需要写成onClick={()=>handlePlay('live')}>而不能写成onClick={handlePlay('live')}>
javascript
const handlePlay = (key)=>{
console.log('播放');
setVideoStatus(key)
}
<button className="play-btn" onClick={()=>handlePlay('live')}></button>
4. 父子组件传递
- 父组件向子组件传递数据:
- 通过 props 传递基本类型数据(如字符串 message)
- 通过 props 传递复杂类型数据(如对象 userInfo)
- 子组件通过 props.属性名 访问这些数据
- 父组件向子组件传递方法:
- 父组件定义方法 handleParentMethod 并通过 props 传递给子组件
- 子组件通过 props.parentMethod() 调用该方法,还可以传递参数
- 子组件向父组件传递数据:
- 父组件定义回调函数 handleChildData 并传递给子组件
- 子组件通过调用 props.onDataChange(数据) 将数据传递给父组件
- 父组件在回调函数中更新自己的状态,实现数据传递
1. 父组件
js
<import React, { useState } from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
// 父组件的状态
const [parentMessage, setParentMessage] = useState("我是来自父组件的消息");
const [childInputValue, setChildInputValue] = useState("");
// 父组件的方法,将传递给子组件
const handleParentMethod = (value) => {
alert(`父组件接收到子组件的调用,参数:${value}`);
};
// 接收子组件传递的数据
const handleChildData = (data) => {
setChildInputValue(data);
};
return (
<div style={{
border: '2px solid #4285f4',
padding: '20px',
margin: '20px',
borderRadius: '8px'
}}>
<h3>父组件</h3>
<p>子组件输入的值:{childInputValue}</p>
{/* 向子组件传递数据和方法 */}
<ChildComponent
message={parentMessage}
parentMethod={handleParentMethod}
onDataChange={handleChildData}
userInfo={{ name: "张三", age: 30 }} // 传递对象
/>
</div>
);
};
export default ParentComponent;
2. 子组件
javascript
<import React, { useState } from 'react';
const ChildComponent = (props) => {
// 子组件的状态
const [childMessage, setChildMessage] = useState("");
// 调用父组件传递过来的方法
const handleCallParentMethod = () => {
props.parentMethod("Hello 父组件,我是子组件");
};
// 向父组件传递数据
const handleInputChange = (e) => {
const value = e.target.value;
setChildMessage(value);
// 通过回调函数将数据传递给父组件
props.onDataChange(value);
};
return (
<div style={{
border: '2px solid #34a853',
padding: '15px',
margin: '10px 0',
borderRadius: '8px'
}}>
<h4>子组件</h4>
{/* 使用父组件传递的数据 */}
<p>父组件传递的消息:{props.message}</p>
<p>父组件传递的用户信息:{props.userInfo.name},{props.userInfo.age}岁</p>
{/* 子组件输入框,数据会传递给父组件 */}
<input
type="text"
value={childMessage}
onChange={handleInputChange}
placeholder="输入内容将传递给父组件"
style={{
padding: '8px',
margin: '10px 0',
width: '300px'
}}
/>
{/* 按钮调用父组件方法 */}
<button
onClick={handleCallParentMethod}
style={{
padding: '8px 16px',
backgroundColor: '#4285f4',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
调用父组件方法
</button>
</div>
);
};
export default ChildComponent;
AntDesgin
modal弹窗
- 居中:
centered={true} - 取消底部按钮:
footer={null} - 内容居中:
style={``{ textAlign: 'center' }} - 关闭销毁弹窗:
destroyOnClose={true}
Tree 树

javascript
import { Card, Row, Col, Space, Tree, Tooltip, Modal, Button,Input } from 'antd'
import './index.scss'
import { useState, useEffect, useMemo } from 'react'
import playerList from './playerList.json'
import { beidouService } from '@/api/beidou'
const { Search } = Input
const transformData = data => {
return data.map(item => {
const transformedItem = {
...item,
title: item.label, // 将label转换为Tree组件需要的title字段
}
// 递归处理子节点
if (item.children && item.children.length > 0) {
transformedItem.children = transformData(item.children)
}
return transformedItem
})
}
// 节点遍历搜素
const getParentKey = (key, tree) => {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some(item => item.key === key)) {
parentKey = node.key;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey;
};
const TreeComponent = ({ setSelectedPlayers }) => {
// 状态管理
const [defaultData, setDefaultData] = useState([]) // 树形结构数据
// 模拟从接口获取数据(实际是从本地文件读取)
useEffect(() => {
// 模拟异步获取数据
const fetchData = async () => {
const transformedData = transformData(playerList)
setDefaultData(transformedData)
setSearchValue(transformedData)
}
fetchData()
}, [])
const [expandedKeys, setExpandedKeys] = useState([])
const [searchValue, setSearchValue] = useState('')
const [autoExpandParent, setAutoExpandParent] = useState(true)
const onExpand = newExpandedKeys => {
setExpandedKeys(newExpandedKeys)
setAutoExpandParent(false)
}
const onChange = e => {
const { value } = e.target
if (!value) {
setExpandedKeys([]);
setSearchValue('');
setAutoExpandParent(true);
return;
}
// 创建一个函数来递归查找所有匹配的节点
const findMatchingNodes = (nodes, allMatchingKeys = []) => {
nodes.forEach(node => {
if (node.title.indexOf(value) > -1) {
allMatchingKeys.push(node.key);
}
if (node.children && node.children.length > 0) {
findMatchingNodes(node.children, allMatchingKeys);
}
});
return allMatchingKeys;
};
// 查找所有匹配的节点
const allMatchingKeys = findMatchingNodes(defaultData);
// 为每个匹配的节点获取所有父节点
const newExpandedKeys = Array.from(
new Set(
allMatchingKeys.flatMap(key => {
const parents = [];
let parentKey = getParentKey(key, defaultData);
while (parentKey) {
parents.push(parentKey);
parentKey = getParentKey(parentKey, defaultData);
}
return [...parents, key];
})
)
);
setExpandedKeys(newExpandedKeys)
setSearchValue(value)
setAutoExpandParent(true)
}
const onCheck = (checkedKeys, info) => {
const leafNodes = info.checkedNodes.filter(
node => !node.children || node.children.length === 0
)
setSelectedPlayers(leafNodes)
}
const treeData = useMemo(() => {
const loop = data =>
data.map(item => {
const strTitle = item.title;
const index = strTitle.indexOf(searchValue);
const beforeStr = strTitle.substring(0, index);
const afterStr = strTitle.slice(index + searchValue.length);
const title =
index > -1 ? (
<span key={item.key}>
{beforeStr}
<span className="site-tree-search-value">{searchValue}</span>
{afterStr}
</span>
) : (
<span key={item.key}>{strTitle}</span>
);
if (item.children) {
return { title, key: item.key, children: loop(item.children) };
}
return {
title,
key: item.key,
};
});
return loop(defaultData);
}, [searchValue]);
return (
<div>
<Search
style={{ margin: '10px 0', width: 250 }}
placeholder="搜索"
onChange={onChange}
/>
<Tree
className="players-tree"
treeData={treeData}
checkable
onCheck={onCheck}
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
/>
</div>
)
}
const ContentComponent = ({ selectedPlayers }) => {
// 如果没有选中的信息,显示提示文本
if (!selectedPlayers || selectedPlayers.length === 0) {
return (
<div className="content-info">
<p style={{ textAlign: 'center', color: '#999' }}>
请从左侧选择选手查看详情
</p>
</div>
)
}
// 显示选中的选手信息
return (
<div className="content-info">
{selectedPlayers.map((playersInfo, index) => {
return (
<div className="content-info-item" key={index}>
<Row className="center mb1rem">
<Col span={24}>
<h3>{playersInfo.title || '选手信息'}</h3>
</Col>
</Row>
<Row className="mb1rem">
<Col span={12}>
<p className="mb1rem" style={{ display: 'inline-flex' }}>
<span>姓名: </span>
<Tooltip
placement="right"
title={playersInfo['label'] || '-'}
>
<span
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: 'inline-block',
maxWidth: '90px',
}}
>
{playersInfo['label'] || '-'}
</span>
</Tooltip>
</p>
<p className="mb1rem">
<span>性别: </span>
<span
style={{
color:
playersInfo['性别'] === '男'
? '#0eaff3'
: playersInfo['性别'] === '女'
? '#ed08ac'
: 'inherit',
}}
>
{playersInfo['性别'] || '-'}
</span>
</p>
<p className="mb1rem" style={{ display: 'flex' }}>
<span>国籍: </span>
{playersInfo['国籍'] && playersInfo['国籍'] !== '-' ? (
<span
style={{
display: 'flex',
alignItems: 'center',
gap: '4px',
}}
>
<img
src={`/src/assets/icon_country/${playersInfo['国籍']}.png`}
alt={playersInfo['国籍']}
style={{
width: '25px',
height: '20px',
verticalAlign: 'middle',
}}
onError={e => {
// 如果图标不存在,则显示文本
e.target.style.display = 'none'
}}
/>
{playersInfo['国籍']}
</span>
) : (
'-'
)}
</p>
</Col>
<Col span={12}>
<p className="mb1rem">
<span>参赛号: </span>
{playersInfo['参赛号'] || '-'}
</p>
<p className="mb1rem">
<span>所属大洲: </span>
{playersInfo['所属大洲'] || '-'}
</p>
</Col>
</Row>
</div>
)
})}
</div>
)
}
const VideoComponent = () => {
const [isModalOpen, setIsModalOpen] = useState(false)
const showModal = () => {
setIsModalOpen(true)
}
const handleOk = () => {
setIsModalOpen(false)
}
const handleCancel = () => {
setIsModalOpen(false)
}
// 测试登录接口
const onFinish = async () => {
await beidouService.getMatchList().then(res => {
console.log('res', res)
})
}
return (
<div>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Button type="primary" onClick={onFinish} style={{ marginLeft: 10 }}>
测试登录接口
</Button>
<Modal
title="八百流沙极限赛视频"
closable={{ 'aria-label': 'Custom Close Button' }}
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
centered={true}
footer={null}
style={{ textAlign: 'center' }}
width={900}
destroyOnHidden={true}
>
{isModalOpen && (
<>
{/* <video id="player-container-id" preload="auto" controls style={{ width: '100%', height: '600px'}}></video> */}
<iframe
src="//player.bilibili.com/player.html?isOutside=true&aid=582534850&bvid=BV1S64y1M7N6&cid=172373846&p=1"
border="0"
allowfullscreen="true"
style={{ width: '100%', height: '600px' }}
></iframe>
</>
)}
</Modal>
</div>
)
}
const Players = () => {
const [selectedPlayers, setSelectedPlayers] = useState([])
return (
<div className="players-page">
<Card title="选手管理" variant="outlined">
<div className="players-content">
<Row gutter={[24, 24]}>
<Col xs={6} className="center">
<Space>
<TreeComponent setSelectedPlayers={setSelectedPlayers} />
</Space>
</Col>
<Col xs={6}>
<p className="content-title">详细信息</p>
<ContentComponent selectedPlayers={selectedPlayers} />
</Col>
<Col xs={6}>
<p className="content-title">视频播放</p>
<VideoComponent />
</Col>
</Row>
</div>
</Card>
</div>
)
}
export default Players