当前位置: 首页 > news >正文

React hooks详解

React hooks详解

      • 一、为什么react需要引入hooks?
      • 二、useState状态维护函数
      • 三、useEffect副作用优化
      • 四、useMemo与useCallback缓存
      • 五、useRef处理ref对象
      • 六、useContext共享状态
      • 七、useReducer优化组件
      • 八、自定义组件

一、为什么react需要引入hooks?

    react中hooks占有特别重要的地位。你可能会想,在react中有了类式组件,那么代码复用已经特别方便了,加上继承,配合typescript进行类型限制,那么整个代码就有点像Java风格了。为什么还是有特别多的人选择使用函数式组件呢,甚至还提出了专门处理函数式组件的hooks内置函数?
    React 引入 Hooks 并不是因为类组件 “不好”,而是为了提供一种更简洁、灵活的方式来处理组件的状态和副作用等功能,以满足不同的开发需求。类组件有其自身的优势,但也存在一些局限性,而 Hooks 则有效地解决了这些问题。
    对于类组件,类组件提供了诸如 componentDidMount等生命周期方法,在处理组件的初始化、更新和销毁时,能够清晰地分离和管理不同阶段的逻辑。可以通过继承来复用代码逻辑,减少重复代码。对于一些复杂的状态管理场景,类组件的 this.state 和 this.setState 可以方便地管理和更新状态,尤其是当状态之间存在依赖关系时,通过 setState 的回调函数可以确保状态更新的一致性。
    但是,类组件的语法相对繁琐,需要继承 React.Component,并且在方法中使用 this 来访问状态和方法,容易出现 this 指向问题,增加了代码的理解和维护成本。例如,为了在事件处理函数中正确使用 this,需要进行绑定操作(如 this.handleClick = this.handleClick.bind(this);)。类组件之间的逻辑复用通常通过高阶组件(HOC)或 Render Props 模式(具体来说指的是允许组件通过 props 接收一个函数并调用它来动态决定渲染内容)实现,但这些模式会增加组件的层级嵌套,导致组件树变得复杂,难以理解和调试,同时还可能引发命名冲突和性能问题。
    相比之下,Hooks 允许使用纯函数来编写组件,避免了类组件中的繁琐语法和 this 指向问题,使代码更加简洁、易读和易于维护。例如,使用 useState 钩子可以简单地在函数组件中添加状态,无需像类组件那样定义 this.state 和 this.setState。此外,Hooks 可以将相关的逻辑提取到自定义的 Hook 函数中,实现逻辑的复用,避免了高阶组件和 Render Props 模式带来的问题。例如,可以创建一个自定义的 useFetch Hook 来处理网络请求逻辑,多个组件可以复用这个 Hook,提高了代码的复用性和可维护性。通过 Hooks,可以将组件的逻辑按照功能拆分成多个小的函数,每个函数专注于一个特定的任务,使代码的结构更加清晰,便于理解和调试。最后,许多 React 库和工具(如 Redux、React Router 等)都开始提供基于 Hooks 的 API,使得在项目中使用这些库更加方便和直观,进一步提升了开发效率。

二、useState状态维护函数

    由于函数式组件本身相当于一个render函数,如果里面存在视图相关的值发生了变更,那么函数会重新执行,如果值频繁修改,那么这个函数会频繁执行,从而影响性能。于是为了维护函数式组件的状态值,Hooks中提供了专门的函数来维护——useState,首先给出useState的用法:

function App() {const [count, setCount] = useState(0);return <><button className='btn' onClick={() => setCount(count+1)}>{count}</button></>
}

    这里useState中的参数为左侧count的默认值,setCount作为唯一函数来修改count。但是这种方法依旧存在重复渲染的可能,举例如下:
在这里插入图片描述
    对这种情形进行解释,这里描述的是在函数式组件render内部修改了状态值,由于setState在异步代码中是同步执行的,因此setTimeout函数触发之后render会重新渲染,这时会重新执行一个setTimeout函数,最终导致组件会无限循环渲染。

三、useEffect副作用优化

    为了解决上面这种情况,出现了useEffect函数(可以使用多次),useEffect使用如下:
在这里插入图片描述
    useEffect接受两个参数,第一个是存在副作用的代码,第二个是依赖项,如果依赖项变化,那么会重复执行,如果是个空数组,那么只执行一次。
在这里插入图片描述
    下面以天气为例,展示怎么借助于useState和useEffect高效实现天气的动态切换,程序代码如下:

function App() {const [city, setCity] = useState("北京");const [weatherInfo, setWeatherInfo] = useState<WeatherItem[]>([]);useEffect(() => {axios.get(`http://m7200.tianqiapis.com/?version=day&unit=m&language=zh&query=${city}&appid=test66&appsecret=test666`).then(response => {const { month } = response.data;if (month && Array.isArray(month)) {setWeatherInfo(month as WeatherItem[]);}}).catch(error => {console.log("输出请求错误信息:", error);});}, [city]);return (<><input placeholder='请输入城市名称' value={city} onChange={(event) => { setCity(event.target.value); }} /><div><div>结果</div><div><ul>{weatherInfo.map((item: WeatherItem, key: number) => (<li className='weather-item' key={key}>{item.dateOfWeek}{item.night.narrative}</li>))}</ul></div></div></>);
}

在这里插入图片描述

四、useMemo与useCallback缓存

    useMemo 是 React 中的一个性能优化 Hook,用于缓存计算结果,避免在每次渲染时都进行高开销的计算。它返回一个记忆化的值,只有当依赖项发生变化时才会重新计算。例如,在React中如果父组件改变,那么父组件下的子组件也会跟着渲染,但这个过程中可能子组件根本没有变化,这个时候便可以利用useMemo来优化渲染过程。

function Box() {const [count, setCount] = useState(Math.random());return (<div><p>随机数:{Math.random()}</p><button onClick={() => setCount(count + 1)}>Box子组件的按钮,count={count}</button></div>)
}function App() {const [count, setCount] = useState(0);return (<div><button onClick={() => setCount(count + 1)}>App组件的按钮,count={count}</button><Box></Box></div>)
}

    点击父组件之后,子组件中的随机数也会进行刷新,但是其实父组件中其实没有明确需要更新子组件的内容。
在这里插入图片描述
    但是需要注意的是这里useSate能够缓存初始值,虽然能够随着父组件变化触发子组件中p标签的Math.random()函数,但是count的属性值是不会改变的,也就是没有重新触发子组件中useState相关的操作。
    下面介绍一下useMemo的用法,useMemo接受两个参数一个是回调函数,返回需要被缓存的结果,另外一个是绑定的对象列表,如果该值变化那么useMemo中的回调函数会被重新执行:

function Box() {const [count, setCount] = useState(Math.random());const testData = useMemo(() => Math.random(), []);return (<div><p>随机数:{testData}</p><button onClick={() => setCount(count + 1)}>Box子组件的按钮,count={count}</button></div>)
}

在这里插入图片描述
    应用场景就比如缓存网络接口请求的返回结果,这样能够减少请求次数,提高性能。还有一种场景,可以模拟computed属性,通过监听多个状态对象(和Vue的computed一致)。
    useCallback和useMemo类似,能够返回一个缓存函数。和useMemo的关系可以理解成:

useMemo(()=>fn,deps);//等价于
useCallback(fn,deps)();

五、useRef处理ref对象

    useRef 是 React 提供的一个 Hook,主要用于在组件的多次渲染之间存储一个可变的引用值。它返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数,这个 ref 对象在组件的整个生命周期内保持不变,即可以随便修改.current属性,但是在组件刷新的时候不能够修改触发useRef的重新渲染。关于ref在React Ref引用机制解析中有详细介绍。

六、useContext共享状态

    在提及这个问题之前,首先需要明确react同级组件共享同一个属性是怎么实现,下面直接给出演示代码:
在这里插入图片描述
    通过createContext机制可以实现组件共享一个state值。但是这种写法不是特别方便,需要全局提供createContext的绑定,之后还需要使用provider和consumer组件的传递模式,代码嵌套层级较深,显得有些冗余。因此出现了useContext方法,简化成如下写法:
在这里插入图片描述

七、useReducer优化组件

    useReducer 是 useState 的升级方案,用于管理复杂的状态逻辑。与 useState 不同,useReducer 使用一个 reducer 函数来处理状态更新,使状态管理更加可预测和结构化。常见用法如下:

const initialState = 10;
function reducer(state: any, action: any) {const {type,val}=action;const newState = { ...state };switch (type) {case 'add': {newState.num += val; break;}case 'sub': {newState.num -= val; break;}default: ;}return newState;
}
function init(initialState: any) {return {num: initialState};
}
function App() {const [state, dispatch] = useReducer(reducer, initialState, init);return (<div><div><button className='btn' onClick={() => dispatch({ type: 'add', val: 1 })}>点击+1</button></div><div>输出当前的值:num={state.num}</div><div><button className='btn' onClick={() => dispatch({ type: 'sub', val: 1 })}>点击-1</button></div></div>)
}

    useReducer接受三个参数,分别是用于处理更新state的函数,初始值,用于对初始值进行封装的函数。使用这种更新方式之后,能够将state的管理与原来的管理方式分离,提高复用性。比如不同的state值都需要进行增加或者减少,可以复用上面的reducer函数。另外useReducer 还可以和useContext组件配合使用,和useContext里面GlobalContext.Provider向子组件传值一样,这里可以将reducer生成的dispatch函数传递给子组件共享,也能实现子组件状态共享和同时维护:
在这里插入图片描述

八、自定义组件

    当我们在多个函数组件中存在相似的状态逻辑(例如处理网络请求、管理定时器等)时,如果重复编写这些逻辑,会导致代码冗余。自定义 Hook 提供了一种将这些可复用的逻辑提取出来的方式,提高代码的可维护性和复用性。比如以上面天气请求为例,封装里面的input的请求体,可以得到如下代码:
在这里插入图片描述
    这里代码可能会提示请求次数过高,这里是因为input本身监听的问题,可以设置防抖函数来处理,或者使用真实密钥,这里只是测试数据,所以没有对QPS优化。

相关文章:

  • 新能源实验室电磁兼容设计优化方案论述
  • HTML02:网页基本信息
  • 苍穹外卖部署到云服务器使用Docker
  • 软考 系统架构设计师系列知识点之杂项集萃(52)
  • 观察者模式(Observer Pattern)详解
  • 自由学习记录(58)
  • n8n工作流自动化平台的实操:利用本地嵌入模型,完成文件内容的向量化及入库
  • 从 0 到 1:使用 Jetpack Compose 和智能自动化实现高效 Android UI 开发
  • 2025 年如何使用 Pycharm、Vscode 进行树莓派 Respberry Pi Pico 编程开发详细教程(更新中)
  • HTML学习笔记(7)
  • PHP的include和require
  • 基于STM32的心电图监测系统设计
  • 【前端】【面试】在 Vue-React 的迁移重构工作中,从状态管理角度来看,Vuex 迁移到 Redux 最大的挑战是什么,你是怎么应对的?
  • 力扣面试150题--相同的树
  • 嵌入式按键原理、中断过程与中断程序设计(键盘扫描程序)
  • 【CISCO】什么是静态路由(Static Route)?ip route 192.0.1.0 255.255.255.0 200.0.0.1
  • 高等数学同步测试卷 同济7版 试卷部分 上 做题记录 第四章 不定积分同步测试卷 B卷
  • LeetCode刷题链表
  • Spring AI 实战:第四章、Spring AI多模态之看图说话
  • Go语言实现Kafka消息队列
  • 超越关税陷阱,不遗余力塑造产业的长期竞争力
  • 香港金紫荆广场举行五四升旗礼
  • 本周看啥|《乘风》迎来师姐们,《天赐》王蓉搭Ella
  • 从“长绳系日”特展看韩天衡求艺之路
  • 七部门联合发布《终端设备直连卫星服务管理规定》
  • 亚马逊拟为商品标注“关税成本”,特朗普致电贝索斯讨说法