# React-quiak-start
**Repository Path**: hou-chengyi/react-quiak-start
## Basic Information
- **Project Name**: React-quiak-start
- **Description**: No description available
- **Primary Language**: TypeScript
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2024-09-11
- **Last Updated**: 2024-09-11
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# React 核心语法
> 目前最流行的现代前端框架 无疑是vue.js和react.js,国内的大部分项目都是vue.js,并且vue3 和react很想象,学习果vue3的人看会觉得相当熟悉,并且在开发跨平台ios或者安卓端的时候 reactnative 也是个不错的选择(原生安卓 ui匮乏,flutter 需要额外掌握一门新语言,uts目前并不完善,开发体验还不成熟)
## 视频教程参考
某马程序员(视频很完善但是不推荐 学会vue3 看个文档很快就能上手) https://www.bilibili.com/video/BV1ZB4y1Z7o8/?spm_id_from=333.337.search-card.all.click
30分钟速成核心语法 https://www.bilibili.com/video/BV1pF411m7wV/?spm_id_from=333.337.search-card.all.click&vd_source=c4237dd4bb302a93bf1fce783ce1f8cb
官网 https://zh-hans.react.dev/learn (建议快速核心语法过一遍 直接看官网 自己上手 看某马和某谷会浪费很多时间)
## 生态圈
- 路由中文文档 (对于目前主流的单页面应用路由肯定是构建大型web需要的) https://react-guide.github.io/react-router-cn/
- 全局状态管理库 (和vue 的pinia类似) https://cn.redux.js.org/
- reactNative (我学react 就是为了这个!!!!!) 跨平台 性能做过优化的 比一般的跨平台好点 稍稍弱于flutter https://reactnative.cn/
- Zustand 一个轻量级的 React 状态管理库,可以用来管理应用中的全局状态。它的目标是提供一种简单、快速且灵活的状态管理解决方案,尤其是在一些不需要 Redux 这种重量级解决方案的场景中。
### 一些好看的ui
[Material-UI:当下流行的 React UI 框架 (mui.com)](https://v4.mui.com/zh/)
[Arco Design - 企业级产品的完整设计和开发解决方案](https://arco.design/)
[Semi Design](https://semi.design/zh-CN/)
[TDesign (tencent.com)](https://tdesign.tencent.com/)
## 开始
```bash
npx create-next-app@latest
```
使用Vite创建项目
```bash
npx create-vite
```
idea编译器有 可以下载对应插件

## 核心语法TSX(JSX)
TSX(JSX)是ts和js 和html的混用
{} 对应vue的{{}} 插值语法
可以渲染任意东西,下面给出结构代码实列直观发现组件结构
### 组件
就是一个大写方法明的方法 然后返回TSX
```JS
import {useEffect, useState} from "react";
function HookEffect() {
const style={
border: "1px solid #ccc",
borderRadius: "5px",
boxShadow: "0 0 10px rgba(0,0,0,0.3)",
padding: "20px",
transform: "translate(-50%, -50%)",
color: "red",
margin: "20px 20px",
fontSize: "30px"
}
const [message, set] = useState("演示effect")
const [count, setCount] = useState(0)
const [isShow, setIsShow] = useState(false)
useEffect(() => {
set("特定监听的数据让useEffect渲染完成")
},[count])
return (
<>
HookEffect
渲染内容
{message}
计数器:{count}
{isShow &&
}
{!isShow &&
}
{isShow && }
>
)
}
const Clock = () => {
const [time, setTime] = useState(new Date().toLocaleTimeString());
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date().toLocaleTimeString());
console.log(new Date().toLocaleTimeString())
}, 1000);
// 清理函数,组件卸载时清除计时器
return () => {
clearInterval(timer);
};
}, []); // 依赖项为空,意味着只在组件挂载时执行
return (
当前时间: {time}
);
};
export default HookEffect;
```
jsx 语法和java函数式编程一样 返回一行省略() 多行则需要
### 函数组件
一个大学函数名的函数为一个组件,函数的返回值为要渲染的内容
```ts
export default function Home() {
return (
hello world
)
}
```
如果返回的内容只有一行,跟java的lambda 可以省略
```tsx
export default function Home() {
return hello world
}
```
如果返回的内容有多行,括号不能省略
```tsx
export default function Home() {
return (
hello world
hello world
hello world
)
}
```
TXS只能返回一个根元素
**注意**和vue中的template一样只有一个根元素
如果出现多个根元素 需要使用 标签包裹 简写<>
```tsx
export default function Home() {
return (
<>
hello world
hello world
hello world
>
)
}
```
### 插值语法
和vue差不多
标签内容
```tsx
export default function App() {
const msg="世界和平"
return (
<>
{/* 使用在标签内部 */}
{msg}
>
)
}
```
### 标签属性
除去数据,样式,tsx ,方法都可以这样渲染
``` tsx
import React from 'react';
const DemoComponent = () => {
// 定义一些示例数据
const name = 'Alice';
const age = 25;
const isStudent = false;
const items = ['Apple', 'Banana', 'Cherry'];
const user = {
name: 'Bob',
age: 30,
occupation: 'Engineer',
};
// 函数返回字符串
const greet = () => 'Hello, World!';
return (
React JSX Rendering Demo
{/* 渲染字符串 */}
Name: {name}
{/* 渲染数值 */}
Age: {age}
{/* 渲染布尔值(显示不同的文本) */}
Status: {isStudent ? 'Student' : 'Not a Student'}
{/* 渲染数组 */}
{items.map((item, index) => (
- {item}
))}
{/* 渲染对象的属性 */}
User Info:
- Name: {user.name}
- Age: {user.age}
- Occupation: {user.occupation}
{/* 渲染函数的返回值 有返回值的函数都可以渲染 包括返回tsx 和各个元素 */}
{greet()}
);
};
export default DemoComponent;
```
## 条件渲染
采用 &&与运算 实现 v-if
采用三元运算 x? case1:case2 实现 if else
案列也演示了样式的渲染方式
- {}插值行内代码块
- 设为变量 再插值
- 单独写一个css文件 为元素写classname 属性作为类名
```jsx
const bluecolor={display:"flex",justifyContent:"space-between",alignItems:'center' ,backgroundColor:"blue"}
/**
* 函数组件大写开头
* @constructor
*/
function Chose() {
const [isBlue, setColor] = useState(false)
return(
你喜欢蓝色吗
{/*类似v-if的用法,但是可以用jsx的语法来实现 如果是v-show则是通过改变style的display来实现*/}
{isBlue && ( //为true时才渲染
这是一个蓝色的内容
)}
{/*等效三元运算符*/}
{isBlue ? (
这是一个蓝色的内容
) : (
这是一个红色的内容
)}
)
}
```
## 列表渲染
先写的todolist再回过头来看基本语法,map在react中用的应该是非常多
列表中的key用于保证元素的唯一性,类似vue的v-for中的key
```tsx
type Student = {
name: string,
age: number
}
const students:Student[]=[{name:"John",age:25},{name:"Mary",age:23},{name:"Bob",age:24}]
function Index() {
const ref = useRef(null);
const [array, setStudents] = useState(students)
const [newName, setName] = useState("")
const [newAge, setAge] = useState(0)
return (
)
}
```
补充js的数组可以使用类似java的lambda 表达式的一些方法,filter ,map,foreach等,由于{}需要返回值 这里用的是map
## 事件处理
标签的属性通常使用驼峰
点击事件:onClick ,输入改变事件 onChange....等等等
jsx 语法和java函数式编程一样 一行代码省略{} 多行则需要
这里演示需要借助一个的响应式效果 ,react是通过管理状态来实现响应式的
useState API实现 下面是详细介绍
https://zh-hans.react.dev/learn/state-a-components-memory#adding-a-state-variable
```tsx
const [value,setValue]=useState()
```
value 变量名 setvalue()可以实现动态改变数值的方法 从而实现数据的响应式 (直接修改这个数值没有意义,必须调用api)
大部分的事件
```jsx
function EventTestComponent() {
const [inputValue, setInputValue] = useState('');
const [message, setMessage] = useState('');
const handleClick = (event) => {
setMessage(`点击事件: ${event.type}`);
};
const handleDoubleClick = (event) => {
setMessage(`双击事件: ${event.type}`);
};
const handleMouseEnter = (event) => {
setMessage(`鼠标进入事件: ${event.type}`);
};
const handleMouseLeave = (event) => {
setMessage(`鼠标离开事件: ${event.type}`);
};
/**
* 可以搭配
* 鼠标悬停事件在 React 中可以通过组合使用 onMouseEnter 和 onMouseLeave 来实现。
* onMouseEnter 事件在光标进入元素时触发,而 onMouseLeave 事件在光标离开元素时触发。通过这两个事件,您可以实现鼠标悬停效果。
* @param event
*/
const handleMouseMove = (event) => {
setMessage(`鼠标移动事件: ${event.type}`);
};
const handleMouseDown = (event) => {
setMessage(`鼠标按下事件: ${event.type}`);
};
const handleMouseUp = (event) => {
setMessage(`鼠标释放事件: ${event.type}`);
};
const handleKeyDown = (event) => {
setMessage(`按键按下事件: ${event.type}, 键: ${event.key}`);
};
const handleKeyPress = (event) => {
setMessage(`按键按下事件: ${event.type}, 键: ${event.key}`);
};
const handleKeyUp = (event) => {
setMessage(`按键释放事件: ${event.type}, 键: ${event.key}`);
};
const handleChange = (event) => {
setInputValue(event.target.value);
setMessage(`输入改变事件: ${event.type}, 值: ${event.target.value}`);
};
const handleInput = (event) => {
setMessage(`输入事件: ${event.type}, 值: ${event.target.value}`);
};
const handleSubmit = (event) => {
event.preventDefault();
setMessage(`表单提交事件: ${event.type}`);
};
const handleFocus = (event) => {
setMessage(`获得焦点事件: ${event.type}`);
};
const handleBlur = (event) => {
setMessage(`失去焦点事件: ${event.type}`);
};
const handleTouchStart = (event) => {
setMessage(`触摸开始事件: ${event.type}`);
};
const handleTouchMove = (event) => {
setMessage(`触摸移动事件: ${event.type}`);
};
const handleTouchEnd = (event) => {
setMessage(`触摸结束事件: ${event.type}`);
};
const handleCopy = (event) => {
setMessage(`复制事件: ${event.type}`);
};
const handleCut = (event) => {
setMessage(`剪切事件: ${event.type}`);
};
const handlePaste = (event) => {
setMessage(`粘贴事件: ${event.type}`);
};
return (
);
}
```
### 传值说明
```jsx
onKeyDown={handleKeyDown} //默认传递事件对象
onClick={()=>onclick(textname)//传递自定义参数
onClick={(e)=>onclick(textname,e)//即传递参数 又传递事件对象
```
## props(父传子)
和vue一样,props 实现父组件传递属性给子组件 可以是传递任意值
父组件在引用的子组件上直接传递数据 <子组件 数据名 ={传递数据}> ,子组件函数的参数添加一个对应的参数props对象就行了 还是很简单的和vue 的组合式api 还是很想
这里用的ts演示 如果是js的话就很简单,无需写类型
>细节: props和vue的也是一样的,props只可读 不能修改,如果需要响应式 需要useSate 进行初始化
```jsx
/**
* 演示props的使用
* 父组件向子组件传递数据
* 只能读取无法修改 需要修改则需要useState
*/
function Parent() {
return (
父组件
父组件传递了 JSX 元素 }
data={{ name: '小明', age: 20 } }
click={() => console.log('点击了子组件')}
/>
子组件3
姓名:小明
年龄:20
这是直接传递的 代码块
);
}
function Child(props: { name: string; age: number }) {
return (
子组件
姓名:{props.name}
年龄:{props.age}
)
;
}
// 子组件3 实现类似插槽
function Child3(porps: {children: JSX.Element}) {
return (
子组件3
{/*插入的代码块*/}
{porps.children}
)
}
function Child2(props:
{ name: string; age:
number ; techs: string[];
space: JSX.Element;
data: { name: string; age: number }
click: () => void
}) {
return (
子组件2
姓名:{props.name}
年龄:{props.age}
技术栈:{props.techs.join(', ')}
JSX 元素:{props.space}
对象数据:{JSON.stringify(props.data)}
);
}
//这样就实现了 父组件->子组件 ->孙组件
function Child4(props: { name: string; age: number }) {
return(
<>
孙子组件
姓名:{props.name}
年龄:{props.age}
>)
}
export default Parent;
```
## 插槽(slot)
react的插槽 也就是这样实现了
```jsx
function Parent() {
return (
父组件
子组件3
姓名:小明
年龄:20
这是直接传递的 代码块
);
}
// 子组件3 实现类似插槽
function Child3(porps: {children: JSX.Element}) {
return (
子组件3
{/*插入的代码块*/}
{porps.children}
)
}
```
### 插入多段代码块
和vue 一样可以使用名字来进行区分
```jsx
import React from 'react';
// 父组件
function Parent() {
return (
父组件
这是头部插槽
}
footer={
}
>
{/* 默认插槽内容 */}
);
}
// 子组件3 实现具名插槽
function Child3({ header, footer, children }) {
return (
子组件3
{/* 具名插槽:头部 */}
{header}
{/* 默认插槽 没有名 */}
{children}
{/* 具名插槽:底部 */}
{footer}
);
}
export default Parent;
```
为了方便记忆 对比vue
```vue
```
## 自定义事件(子传父)
和vue 也是通过类似 自定义事件的方式 子组件创建事件,父组件实现,这样就可以子组件参数传递给父组件( 其实有点rpc的思想,rpc 服务定义接口 具体服务交给后端实现)
```jsx
function EmitDemo() {
const [message , setMessage] = useState("")
// 首先父组件定义方法和实现
const handleClick = (message:string) => {
setMessage(message)
}
return (
Emit Demo
来自子组件的消息父组件显示
{message}
)
}
function Demoson(props:{
click:(msg:string) => void,
})
{
const [inputValue, setinput] = useState("")
function changeinput(e) {
setinput(e.currentTarget.value);
}
return (
子组件
)
}
```
此时可以实现一个子组件传递给兄弟组件的逻辑了 ,子1 ->父组件->子2 ,就是先兄弟之间的传递
```jsx
function EmitDemo() {
const [message , setMessage] = useState("")
// 首先父组件定义方法和实现
const handleClick = (message:string) => {
setMessage(message)
}
return (
Emit Demo
来自子组件的消息父组件显示
{message}
{/*传递给兄弟组件*/}
)
}
function Demoson(props:{
click:(msg:string) => void,
})
{
const [inputValue, setinput] = useState("")
function changeinput(e) {
setinput(e.currentTarget.value);
}
return (
子组件
)
}
/**
* 子组件
* 演示兄弟组件的使用
* 子组件传值给了父组件 父组件传值个另一个子组件
* @constructor
*/
function Brother(props:{
message:string
}) {
return ()
}
```
## 上下文 (多层级传参)
虽然刚才实现了兄弟组件传值 ,但是那样太麻烦了 数据经历了 俩次传递,所以上下文对象就诞生了,和后端一些web框架(go:gin,java :spring)一样,一个请求的处理是有专门的链式处理的,在一开始设置上下文,全链的处理都可以拿到这个数据
类似vue 的provider 和imject
1. 创建上下文对象
2. <上下文对象.Provider value={value}> 标签包裹 需要上下文环境的子对象
3. 子组件 孙. 已经更下层级的组件都可以使用 const value = useContext(MyContext); // 使用 useContext Hook 来获取上下文值
```ts
// 主应用组件用来显示
function Container() {
return (
<>
{/*范围内的统计对象可以使用 A设置上下文 包裹bc*/}
>
);
}
/**
* Context 是一个全局的共享对象,可以让组件之间共享数据,而不必显式地通过 props 传递。
* 它可以使得 props 在组件树中的任何位置都可以访问到,并且可以避免层层传递 props。
* 类似vue的provider 和inject
* 1.创建一个 Context 对象,并通过 createContext 函数创建。
* 2.使用 Provider 组件包装组件,并提供 Context 值。
* 3.使用 useContext Hook 来获取上下文值。
*/
// 1.创建一个 Context
const MyContext = createContext({
text: "",
number: 0,
component: 没有赋值成功
,
func: ()=>console.log("没有赋值成功"),
});
// 包装组件,提供 Context 值
function ComponentA(props:{ children: JSX.Element }) {
const value = {
text: "这是上下文包装对象!",
number: 42,
component: This is a component passed through Context.
,
func: () => alert("上下文传递的函数!")
};
return (
//2.使用 Provider 组件包装组件,并提供 Context 值 并且将需要使用到上下文的对象插入插槽中
{props.children}
);
}
// 消费组件,使用上下文值
function ComponentB() {
const value = useContext(MyContext); // 使用 useContext Hook 来获取上下文值
return (
{value.text}
{value.number}
{value.component}
{/*孙级的对象也可以使用*/}
);
}function ComponentC() {
const value = useContext(MyContext); // 使用 useContext Hook 来获取上下文值
return (
{value.text}
{value.number}
{value.component}
);
}
```
## 获取dom元素
这个完全和vue一模一样
1.使用useref() api
2.在想获取的组件或者标签使用ref=api返回的对象,就可以获取
3.然后再函数内使用ref.current 获取当前dom 元素
```ts
/**
* 演示获取虚拟对象
* 和vue3的类似 使用useRef获取虚拟对象
* dom可用时 ref.current 指向真实dom对象
* dom不可用时 ref.current 指向undefined
*/
function Demo() {
const ref = useRef(null)
const [value, setValue] = useState("")
return (
Hello World
console.log("keydown")} onChange={e => setValue(e.target.value)}/>
);
}
export default Demo;
```
## 自定义hook
> 自定义 Hook 是 React 中非常强大的一个特性,它允许你将组件逻辑抽取到一个独立的函数中,从而在多个组件中复用这部分逻辑。自定义 Hook 和普通的函数非常类似,唯一的区别是它的名称必须以 `use` 开头,并且可以调用其他 Hook(比如 `useState`、`useEffect` 等)。
### 创建自定义 Hook 的步骤
- **确定要复用的逻辑**:通常情况下,你会发现某些组件中使用的逻辑可以复用,比如表单处理、API 请求、窗口尺寸变化等。
- **抽取逻辑到自定义 Hook**:将这部分逻辑移到一个函数中,这个函数返回相关的状态和操作方法。
- **使用自定义 Hook**:在需要的组件中调用这个自定义 Hook,并使用它提供的状态和方法。
官网演示的是检测网络连接 那么这里就做一个检查窗口大小的hook
`useWindowSize` 自定义 Hook
```ts
/**
* 自定义 Hook: 用于获取并跟踪窗口的宽度和高度。
* 使用了 `useState` 来存储窗口尺寸,`useEffect` 来监听窗口尺寸变化事件。
*/
function useWindowSize() {
// 1. 初始化宽度和高度的状态
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
// 2. 定义更新窗口尺寸的函数
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
// 3. 监听窗口尺寸变化事件
window.addEventListener('resize', handleResize);
// 4. 清除事件监听器
return () => {
window.removeEventListener('resize', handleResize);
};
}, [windowSize]); // 窗口对象 确保对象该改变数据实时更新
// 5. 返回窗口尺寸
return windowSize;
}
export default useWindowSize;
```
然后组件内进行使用
```TS
import useWindowSize from '../../hooks/useWindowSize.ts'; // 假设自定义 Hook 保存在 useWindowSize.js 文件中
/**
* 这个组件使用了自定义 Hook `useWindowSize` 来显示和跟踪窗口的尺寸。
*/
function WindowSizeComponent() {
// 1. 使用自定义 Hook 来获取窗口尺寸
const { width, height } = useWindowSize();
return (
当前窗口尺寸
宽度: {width}px
高度: {height}px
);
}
export default WindowSizeComponent;
```
还可以再写一个 axios的hook
``` TS
import { useState, useEffect } from 'react';
import axios from 'axios';
/**
* 自定义 Axios Hook: 用于发起 HTTP 请求并处理数据的加载状态、错误和返回数据。
* @param {Object} config - Axios 请求配置,包括 method, url, data 等。
* @param {boolean} immediate - 是否立即发起请求,默认 true。
*/
const useAxios = (config, immediate = true) => {
const [data, setData] = useState(null); // 存储请求返回的数据
const [loading, setLoading] = useState(false); // 加载状态
const [error, setError] = useState(null); // 错误状态
const [trigger, setTrigger] = useState(immediate); // 是否立即触发请求
const fetchData = async () => {
setLoading(true);
try {
const response = await axios(config); // 通过 Axios 发起请求
setData(response.data); // 请求成功,保存返回的数据
setError(null); // 清除错误
} catch (err) {
setError(err.message || 'Something went wrong'); // 请求失败,保存错误信息
} finally {
setLoading(false); // 结束加载状态
}
};
useEffect(() => {
if (trigger) {
fetchData();
}
}, [trigger]); // 当 `trigger` 变化时发起请求
// 提供一个手动触发请求的方法
const refetch = () => setTrigger(true);
// 返回数据、加载状态、错误信息和重新请求的方法
return { data, loading, error, refetch };
};
export default useAxios;
```
使用的组件
```js
import React, { useState } from 'react';
import useAxios from './useAxios';
function AxiosComponent() {
// 1. 配置请求
const config = {
method: 'GET',
url: 'https://jsonplaceholder.typicode.com/posts/1',
};
// 2. 使用自定义 Hook
const { data, loading, error, refetch } = useAxios(config);
// 3. 处理加载状态、错误、数据展示
return (
Axios Hook 请求演示
{loading &&
加载中...
}
{error &&
错误: {error}
}
{data && (
)}
);
}
export default AxiosComponent;
```
## React Hooks
刚才用的useState ,UseRef ,UseContent 都属于hook 接下来说其他的hooks
建议直接看这个 官方文档 https://zh-hans.react.dev/reference/react/hooks
### effect
`useEffect` 是 React 中的一个 Hook,用于在组件渲染后执行副作用操作。`useEffect` 有几种使用形式,根据依赖项的不同,它可以控制副作用执行的时机。接下来,我们将详细分析 `useEffect` 的多种使用方式
#### 1. **无依赖项数组**(每次渲染都执行)
如果 `useEffect` 中没有依赖项数组,意味着每次组件更新时,都会执行 `useEffect`,即无论组件的任何状态或属性发生任何变化,`useEffect` 都会在每次重新渲染后运行。
```jsx
useEffect(() => {
set("useEffect渲染完成" + new Date().toLocaleTimeString());
});
```
##### 特点:
- 这种形式会在**每次渲染完成后**执行,包括初次挂载和之后的每次更新。
- **注意**:这样会导致频繁调用,可能会影响性能,尤其是在组件频繁更新的情况下。
#### 2. **空依赖项数组**(只在组件挂载时执行)
当依赖项数组为空 `[]` 时,`useEffect` 只会在组件**第一次渲染(挂载)**时执行一次,并且在组件卸载时清除(如果有清理函数)。
```javascript
useEffect(() => {
set("useEffect渲染完成" + new Date().toLocaleTimeString());
}, []);
```
##### 特点:
- 只会在**组件初次渲染时**执行一次(类似于 `componentDidMount`)。用来做初始渲染
- 对性能更加友好,因为它不会在每次组件更新时都执行。
#### 3. **依赖项数组**(依赖特定变量变化时执行)
如果你提供了一个依赖项数组,`useEffect` 只会在数组中的依赖项发生变化时执行。你可以将状态或属性值放入依赖项数组中,这样只有这些值发生变化时,`useEffect` 才会运行。
```javascript
useEffect(() => {
set("useEffect渲染完成" + new Date().toLocaleTimeString());
}, [count, name]);
```
##### 特点:
- 只会在依赖项数组中的变量变化时执行(类似于 `componentDidUpdate vue watch`)。
- 有效控制了副作用的执行时机,避免不必要的调用,提升性能。
#### 4. **带清理函数的 `useEffect`**(处理副作用清理)
`useEffect` 还可以返回一个清理函数,这通常用于处理订阅、计时器或其他需要在组件卸载时清除的副作用。
```js
useEffect(() => {
const timer = setInterval(() => {
set("useEffect渲染完成" + new Date().toLocaleTimeString());
}, 1000);
// 返回清理函数
return () => {
clearInterval(timer);
};
}, []);
```
##### 特点:
- 当组件卸载或者依赖项变化时,清理函数会执行,用于清除副作用(类似于 `componentWillUnmount`)。
- 常用于计时器、事件监听、网络请求等副作用的清理。
### **总结几种 `useEffect` 的使用方式:**
1. **不传依赖项**:每次组件渲染时都会执行。
2. **空依赖项数组 `[]`**:只在组件初次渲染时执行一次。
3. **传递依赖项 `[dep1, dep2]`**:仅在依赖项发生变化时执行。
4. **带清理函数**:在组件卸载或依赖项更新时清理副作用。
在 React 中,如果你想通过 `useEffect` 监听对象或数组中的某个值、对象本身或数组本身的变化,有几种不同的方式可以实现。这取决于你是否希望监听单个属性或元素的变化,还是整个对象或数组的变化。
#### 1. **监听对象中的某个值**
你可以在依赖项数组中直接指定对象的某个属性。如果只想监听对象中的某个属性的变化,可以将该属性作为依赖项。
```jsx
const [person, setPerson] = useState({ name: "Alice", age: 25 });
useEffect(() => {
console.log("person.name changed:", person.name);
}, [person.name]); // 只监听对象的 name 属性变化
```
#### 2. **监听数组中的某个值**
类似地,可以直接监听数组中的某个元素,通常会基于状态或其他条件动态指定依赖项。
```javascript
const [numbers, setNumbers] = useState([1, 2, 3, 4]);
useEffect(() => {
console.log("numbers[0] changed:", numbers[0]);
}, [numbers[0]]); // 只监听数组第一个元素的变化
```
#### 3. **监听整个对象**
如果你想监听整个对象的变化,可以将整个对象作为依赖项。不过需要注意的是,如果对象是复杂的引用类型,即使对象的内部属性改变,React 可能不会重新触发 `useEffect`,除非整个对象本身被替换。因此,更新对象时最好使用 **浅拷贝** 或 **结构赋值**,以确保 `useEffect` 能够监听到变化。
```javascript
const [person, setPerson] = useState({ name: "Alice", age: 25 });
useEffect(() => {
console.log("Person object changed");
}, [person]); // 监听整个 person 对象的变化
// 示例:更新对象属性时,确保重新创建对象引用
const updateAge = () => {
setPerson(prev => ({ ...prev, age: prev.age + 1 }));
};
```
##### 注意:
如果仅仅更新对象的某个属性而不替换整个对象(即直接修改引用类型的属性),不会触发 `useEffect` 重新运行。为了触发更新,**对象需要重新创建**。
#### 4. **监听整个数组**
同样的,监听整个数组也可以直接将数组作为依赖项。当数组中的内容发生变化(例如通过 `setNumbers([...newNumbers])` 更新数组时),会重新触发 `useEffect`。
```javascript
const [numbers, setNumbers] = useState([1, 2, 3, 4]);
useEffect(() => {
console.log("Numbers array changed:", numbers);
}, [numbers]); // 监听整个数组的变化
// 示例:更新数组时,确保创建新的数组引用
const addNumber = () => {
setNumbers(prev => [...prev, 5]);
};
```
#### 5. **监听对象或数组中的深层属性**
React 的 `useEffect` 默认不会对深层属性进行深度比较,只会对引用进行浅比较。如果你想监听对象或数组中的深层嵌套属性变化,简单的依赖项数组可能不会有效。这种情况下,可以考虑使用 `JSON.stringify` 进行深度比较(**仅适合简单的场景**),或者使用自定义的比较逻辑。
```js
const [person, setPerson] = useState({ name: "Alice", age: 25, address: { city: "New York" } });
useEffect(() => {
console.log("Person's city changed:", person.address.city);
}, [JSON.stringify(person.address)]); // 深层监听 address 对象变化
```
#### 演示代码
```jsx
import {useEffect, useState} from "react";
function HookEffect() {
const style={
border: "1px solid #ccc",
borderRadius: "5px",
boxShadow: "0 0 10px rgba(0,0,0,0.3)",
padding: "20px",
transform: "translate(-50%, -50%)",
color: "red",
margin: "20px 20px",
fontSize: "30px"
}
const [message, set] = useState("演示effect")
const [count, setCount] = useState(0)
const [isShow, setIsShow] = useState(false)
//如果 useEffect 中没有依赖项数组,意味着每次组件更新时,都会执行 useEffect,即无论组件的状态或属性发生任何变化,useEffect 都会在每次重新渲染后运行
// useEffect(() => {
// set("useEffect渲染完成"+new Date().toLocaleTimeString())
// })
// useEffect(() => {
// set("任一数据改变 useEffect渲染完成"+new Date().toLocaleTimeString())
// },[])
useEffect(() => {
set("特定监听的数据让useEffect渲染完成")
},[count])
return (
<>
HookEffect
渲染内容
{message}
计数器:{count}
{isShow &&
}
{!isShow &&
}
{isShow && }
>
)
}
const Clock = () => {
const [time, setTime] = useState(new Date().toLocaleTimeString());
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date().toLocaleTimeString());
console.log(new Date().toLocaleTimeString())
}, 1000);
// 清理函数,组件卸载时清除计时器
return () => {
clearInterval(timer);
};
}, []); // 依赖项为空,意味着只在组件挂载时执行
return (
当前时间: {time}
);
};
export default HookEffect;
```
- useXXX的hook函数只能在组件中使用
- 并且不运行在for,if等逻辑判断里面执行,只能在top 顶层使用
- **监听对象中的某个值**:可以直接将该值(如 `person.name`)作为 `useEffect` 的依赖项。
- **监听数组中的某个值**:可以直接将该值(如 `numbers[0]`)作为依赖项。
- **监听整个对象或数组**:将整个对象或数组作为依赖项,并且在更新时创建新的对象或数组引用。
- **深层属性监听**:可以通过 `JSON.stringify` 等技巧强制比较对象或数组的深层属性变化,但这并不是最佳方案,复杂场景下需要定制逻辑。
## 性能优化hook
当出现复杂计算结果的时候,比如cons agesum= 元素之和 ,由于响应式,每次有状态更新就会触发组件整体刷新,那么这个复杂结果也会更新,这样重复的计算 对性能会进行拖累,为此有一个类似计算属性的api 解决了问题
**useMemo(()=>返回计算结果的回调函数,[监听对象列表])**
缓存复杂计算的结果,只有第二个参数对象数组的状态发生改变才进行重新渲染
```JSX
function Index() {
const ref = useRef(null);
const [students, setStudents] = useState(initialStudents);
const [newName, setName] = useState("");
const [newAge, setAge] = useState(0);
const [value, setValue] = useState(false);
// 复杂计算的示例函数
const computeExpensiveValue = (students: Student[]) => {
console.log("计算复杂值");
// 模拟复杂计算
return students.map(student => student.age * 2).reduce((a, b) => a + b, 0);
};
// 使用 useMemo 缓存复杂计算的结果,只有当 newname 或者回调参数变化时才进行调用对调 的到计算属性
//一般第二个参数就是回调参数的传参 这里是为了演示
const totalAge = useMemo(() => computeExpensiveValue(students), [newName]);
return (
学生列表
{/* 显示缓存计算结果 */}
学生总年龄(*2后的和):{totalAge}
{students.map((item, index) => (
{item.name} - {item.age}
))}
添加新学生
setName(e.target.value)}
/>
setAge(Number(e.target.value))}
/>
{/* 模拟组件的状态变化引发更新 */}
状态变量:{value.toString()}
);
}
```
所以这里我点击切换状态刷新重新渲染以后复杂计算函数的回调,不会触发回调的日志,说明没有重新渲染,只有监听的属性改变时才会重新输出
### 子组件频繁渲染
> 下面这种情况,当一个父组件中调用了一个子组件的时候,父组件任意state更新 state 发生变化,会导致父组件更新页面重新渲染,而子组件虽然没有发生改变,但也会进行更新。简单的理解下,当一个页面内容非常复杂,模块非常多的时候,函数式组件会**从头更新到尾**,只要一处改变,所有的模块都会进行刷新,这种情况显然是没有必要的。
```jsx
// 定义学生类型
type Student = {
name: string;
age: number;
};
// 初始学生列表
const initialStudents: Student[] = [
{ name: "John", age: 25 },
{ name: "Mary", age: 23 },
{ name: "Bob", age: 24 }
];
function Song() {
console.log("我是子组件进行渲染");
return 我是子组件
;
}
function Index() {
const ref = useRef(null);
const [students, setStudents] = useState(initialStudents);
const [newName, setName] = useState("");
const [newAge, setAge] = useState(0);
const [value, setValue] = useState(false);
// 复杂计算的示例函数
const computeExpensiveValue = (students: Student[]) => {
console.log("计算复杂值");
// 模拟复杂计算
return students.map(student => student.age * 2).reduce((a, b) => a + b, 0);
};
// 使用 useMemo 缓存复杂计算的结果,只有当 students 变化时才重新计算
const totalAge = useMemo(() => computeExpensiveValue(students), [students]);
return (
学生列表
{/* 显示缓存计算结果 */}
学生总年龄(*2后的和):{totalAge}
{students.map((item, index) => (
{item.name} - {item.age}
))}
添加新学生
setName(e.target.value)}
/>
setAge(Number(e.target.value))}
/>
{/* 模拟组件的状态变化引发更新 */}
状态变量:{value.toString()}
);
}
```
只要点击切换状态 那么子组件就会重新更新

优化 使用memo 此时只有传递的props:{} 属性发生变化子组件才进行更新
```jsx
// 使用 React.memo 来优化 Song 组件
const Song = memo(() => {
console.log("我是子组件进行渲染");
return 我是子组件
;
});
```
```jsx
// 使用 React.memo 包裹 Song 组件,并添加 props
const Song = memo(({ message }) => {
console.log("我是子组件进行渲染");
return {message}
;
});
```
## useCallback
缓存回调函数
useCallback返回的是缓存函数运行的结果
注意:这个函数是父组件传递子组件的一个函数,防止做无关的刷新,所以这个组件必须配合memo,否则不但不会提升性能
**优化后**
```jsx
import React, { useState, useRef, useMemo, useCallback } from 'react';
// 定义学生类型
type Student = {
name: string;
age: number;
};
// 初始学生列表
const initialStudents: Student[] = [
{ name: "John", age: 25 },
{ name: "Mary", age: 23 },
{ name: "Bob", age: 24 }
];
// 使用 React.memo
const Song = memo(() => {
console.log("我是子组件进行渲染");
return 我是子组件
;
});
function Index() {
const ref = useRef(null);
const [students, setStudents] = useState(initialStudents);
const [newName, setName] = useState("");
const [newAge, setAge] = useState(0);
const [value, setValue] = useState(false);
// 模拟复杂计算函数
const computeExpensiveValue = useCallback((students: Student[]) => {
console.log("计算复杂值");
return students.map(student => student.age * 2).reduce((a, b) => a + b, 0);
}, []); // 依赖项为空,这个函数在组件挂载时只会创建一次
// 使用 useMemo 缓存复杂计算的结果,只有当 students 变化时才重新计算
const totalAge = useMemo(() => {
console.log("重新计算总年龄");
return computeExpensiveValue(students);
}, [students, computeExpensiveValue]); // 依赖项为 students 和 computeExpensiveValue
// 缓存的输入处理函数
const handleNameChange = useCallback((e: React.ChangeEvent) => {
console.log("Name 变化");
setName(e.target.value);
}, []); // 依赖项为空,函数在挂载时只会创建一次 其他时候不创建函数实列 配合memo 保证子组件的函数随着子组件的挂载才进行创建
const handleAgeChange = useCallback((e: React.ChangeEvent) => {
console.log("Age 变化");
setAge(Number(e.target.value));
}, []); // 依赖项为空
// 缓存的添加学生函数,依赖于 newName 和 newAge
const handleAddStudent = useCallback(() => {
console.log("添加学生");
const newStudent = { name: newName, age: newAge };
ref.current.focus(); // 聚焦到输入框
setStudents(prevStudents => [...prevStudents, newStudent]); // 使用扩展运算符创建新数组
setName(""); // 清空输入框
setAge(0); // 清空输入框
}, [newName, newAge]); // 依赖于 newName 和 newAge
return (
学生列表
{/* 显示缓存计算结果 */}
学生总年龄(*2后的和):{totalAge}
{students.map((item, index) => (
{item.name} - {item.age}
))}
添加新学生
{/* 模拟状态变化 */}
状态变量:{value.toString()}
);
}
export default Index;
```
### 其他hooks
其他的hooks使用场景较少 官方都单独把他们分到一起

官方文档 https://zh-hans.react.dev/reference/react/hooks