# 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编译器有 可以下载对应插件 ![image-20240910173838438](D:\ReactProject\react-quiak-start\image\image-20240910173838438.png) ## 核心语法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'}

{/* 渲染数组 */} {/* 渲染对象的属性 */}

User Info:

{/* 渲染函数的返回值 有返回值的函数都可以渲染 包括返回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 (

遍历多个元素

{/*只要有返回值无论是样式渲染的对象还是函数都可以直接渲染*/} {array.map((item, index) =>
{item.name} - {item.age}
)}

添加元素

setName(e.target.value)}/> setAge(Number(e.target.value))}/>
) } ``` 补充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 (

事件测试组件

尝试点击、双击、移动鼠标或触摸此区域

事件信息: {message}

); } ``` ### 传值说明 ```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={

这是底部插槽

} > {/* 默认插槽内容 */}

子组件内容

姓名:小明

年龄:20

); } // 子组件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 (

兄弟组件


{props.message}

) } ``` ## 上下文 (多层级传参) 虽然刚才实现了兄弟组件传值 ,但是那样太麻烦了 数据经历了 俩次传递,所以上下文对象就诞生了,和后端一些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 && (

{data.title}

{data.body}

)}
); } 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()}
); } ``` 只要点击切换状态 那么子组件就会重新更新 ![image-20240911144419184](C:\Users\侯\AppData\Roaming\Typora\typora-user-images\image-20240911144419184.png) 优化 使用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使用场景较少 官方都单独把他们分到一起 ![image-20240911111238820](D:\ReactProject\react-quiak-start\image\image-20240911111238820.png) 官方文档 https://zh-hans.react.dev/reference/react/hooks