前言
最近在重新学习 React 时,我越发体会到 React Hooks 的重要性。
本文将介绍 React 中最常用的几个 Hooks,包括它们的基本用法、适用场景以及一些最佳实践。无论你是刚开始接触 Hooks,还是希望更深入地掌握它们,相信这篇文章都能给你带来一些帮助。
接下来,让我们一起看看这些 Hooks 如何优化 React 开发体验。
React Hooks简介
在React 16.8的时候引入了React Hooks,为什么要引入Hooks呢? 那就得了解没有Hooks之前的痛点:
- 在组件之间的复用很难
- 冗长的代码,难以维护
- 难以理解的class
React Hooks应运而生,它可以让我们更加轻松地完成对数据的状态,副作用的管理。它不仅让代码更加简洁,还提供了更灵活的状态管理和逻辑复用方式。相较于传统的类组件,Hooks 让函数组件具备了更强大的能力,同时减少了冗余代码。如今,useSate,useEffct,useConetxt等已经成为开发中必不可少的工具。
React中常见的Hooks
接下来我们来盘点一下react中常见的Hooks以及使用方法和场景。
useState
说起React Hooks中最常用的Hook,useState绝对稳坐第一把交椅。它用于在组件中添加一个状态变量,能让你的变量在更新时重新渲染组件。
基本使用
useState接收一个初始值,返回一个数组,分别是该变量的状态和改变状态的方法。
import { useState } from "react";
export default function Counter(){
const [count,setCount] = useState(0)
const handleClick = ()=>{
setCount(count+1)
}
return <>
<p>{count}</p>
<button onClick={handleClick}>点击++</button>
</>
}
set方法
set允许你传递一个函数,函数返回值就是状态变量修改后的值,set没有返回值。
setCount((count)=>count+1)
注意 :set方法仅更新下一次渲染的状态变量。如果set后读取状态变量 仍是旧值。
useEffct
useEffct常用于处理组件的副作用 (网络请求,资源清理等)。官网给出的解释是用于与外部系统进行同步。
这里需要说明的是,外部系统是指"不在React生命周期内管理的系统或资源"。例如,定时器,事件监听器,第三方库等。由于React 组件可能会被多次挂载和卸载,如果不清理这些外部系统,可能会导致内存泄漏等性能问题。
基本使用
useEffct接收俩个参数: setup和dependencies
setup是一个函数,它可以选择性地返回一个清理函数函数clear。当组件第一次加载时,会运行setup函数。当依赖项发生变化时,会用旧值运行clear函数,再用新值运行setup函数。
dependencies是一个数组,即依赖项数组。该参数用于指定哪些值会触发setup函数。当数组为空的时候,副作用只会在组件挂载和卸载时执行一次。
//用useEffect完成屏幕监听以及清理
import { useEffect } from "react";
import { useState } from "react";
export default function ListenWidth(){
const [width,setWidth] = useState(window.innerWidth)
useEffect(()=>{
//设置屏幕监听器
const resize = ()=>{
setWidth(window.innerWidth)
}
window.addEventListener('resize',resize)
//返回清理函数
return ()=>window.removeEventListener('resize',resize)
},[])
return <>
<p>当前屏幕宽度:{width}</p>
</>
}
注意事项
1.useEffct( )很多时候用来在组件加载之前请求数据,但useFeect中不能直接返回异步函数,所以一般是声明一个函数再调用
useEffect(()=>{
const getData = async ()=>{
const res = await fetch('api/data')
const data = await res.json()
setData(data)
}
getData()
},[])
- 依赖项为空和依赖项为空数组是俩个概念,依赖项为空时useEffect将在渲染后运行,为空数组时,只在组件挂载和卸载时运行
useContext
useContext可以让你在组件中订阅和发布消息。通过上下文对象,可以在不使用props的情况下共享数据。
基本使用
useContext接收一个React Context参数,这个参数是使用createContext创建的。返回的结果是这个参数所引用的值。我们来看一个使用useContext在组件中通信的简单示例。
// ./MyContext.ts
import {createContext} from 'react'
//这里可以定义context的类型
export const Mycontext = createContext(defaultContext)
// 父组件
import { MyContext } from './context/MyContext';
import ChildComponent from './ChildComponent';
function App() {
const value = { /* 你的共享数据 */ };
return (
<MyContext.Provider value={value}>
<ChildComponent />
</MyContext.Provider>
);
}
// 子组件
import { Mycontext } from "../../context/Mycontext";
import { useContext } from "react";
export default function ChildrenComponent(){
const value = useContext(Mycontext)
return <>
{value}
</>
}
注意事项
- 使用context时,传递的对象key值必须是value。
- 如果需要更新Context,请与useState结合 <Mycontext.Provider value={value}> </Mycontext.Provider>
const [context,setContext] = useState('我是context')
const value = {
context,
setContext
}
- 使用context的时候,由于数据更新时,所有消费context的组件都要重新渲染,因此会带来性能问题。 解决办法:细化context,使用useMemo优化value等
useRef
useRef官网给出的定义是:它能帮助引用一个不需要渲染的值。此外,useRef常用来获取操作DOM元素。
基本使用
useRef会返回一个ref对象,可以通过.current来访问其值。我们先来看useRef是如何操作DOM的,先声明一个初始值为null的ref对象,然后将ref对象绑定到DOM元素就可以操作。
import { useRef } from 'react'
export default function Ref(){
const inputRef = useRef(null)
function hanldClick(){
console.log(inputRef.current.value)
}
return <>
<input ref={inputRef} onClick={handleClick}/>
</>
}
除了操作DOM以外,useRef还常用于保持在渲染过程中不断更新的值。你可以通过改变current来存储需要的值,与state不同的是,ref并不会触发渲染。
import { useRef } from 'react';
export default function Counter() {
let ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
alert('你已经点击了' + ref.current + '次!');
}
return (
<button onClick={handleClick}>
点击!
</button>
);
}
注意事项
- 避免重复创建ref的内容,这样会增加程序开销。
//错误用法,此后每次渲染都会创建对象
const playerRef = useRef(new VideoPlayer());
//正确用法
const playerRef = useRef(null)
if(playerRef.current == null){
playerRef.currrent = new VideoPlayer()
}
useMemo
useMemo能在每次重新渲染的时候缓存计算结果。可以用于跳过重新计算,组件的重新渲染,记忆另一个hook的依赖等。合理使用useMemo,可以提高我们应用的响应速度,避免不必要的计算。
基本使用
useMemo接收俩个参数: 一个需要计算缓存值的无参数函数 (calculateValue) 和一个所有在calculateValue中使用的响应式变量所组成的数组 (dependencies)。
返回值: 在初次渲染时返回计算的值。在接下来的渲染过程中,如果变量没有改变,则直接返回上一次缓存的值。否则,则计算新值返回。
简单示例一个使用useMemo的计数器用法,当然这里完全不需要使用useMemo,因为计算特别简单。
import { useState,useMemo } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
const [color, setColor] = useState('#000'); // 初始设为黑色
const handleClick = () => {
setCount(count + 1);
};
const displayText = useMemo(() => {
return count;
}, [count]);
return (
<>
<p style={{ color: color }}>{displayText}</p>
<button onClick={handleClick}>点击++</button>
<button onClick={() => setColor('red')}>更换count的显示颜色</button>
</>
);
}
跳过组件的重新渲染
除了上述useMemo可以避免昂贵的计算以外,memo还能跳过组件的重新渲染,借用react官网的例子: 只要传递的props items没有改变,就不会触发重新渲染
import { memo } from 'react';
const List = memo(function List({ items }) {
// ...
});
export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(()=>{
return filterTodos(todos, tab)
},[todos, tab])
// 只要依赖项不变,visibleTodos就不会重新计算
return (
<div className={theme}>
{/* ... 所以List的props永远不会一样,每次都会重新渲染 */}
<List items={visibleTodos} />
</div>
);
}
useReducer
useReducer用来向组件中添加一个reducer,用来处理根据不同的动作进行更新状态。
使用useReducer的好处:
- 代码更加有逻辑性,可扩展性更强 ;
- 性能优化,减少渲染次数 ;
- 更容易测试和排错 ;
基本使用
useReducer接收俩个参数reducer 函数 和 初始状态,返回当前状态和dispatch函数(用来更改状态)
let [state,dispatch] = useReducer(reducer,initialState)
我们来使用useReducer简单完成一个计数器的加减
import { useReducer } from "react"
export default function ReducerTest(){
interface action {
type:string
}
const initialState = 0
const countReducer = (state:number,action:action)=>{
switch(action.type){
case 'add':{
return state+1
}
case 'del':{
return state-1
}
default:
return state
}
}
const [count,dispact] = useReducer(countReducer,initialState)
return <div>
<button onClick={()=>dispact({type:'del'})}>--</button>
<span>计数值:{count}</span>
<button onClick={()=>dispact({type:'add'})}>++</button>
</div>
}
但上面的逻辑显然不够复杂,Reducer更适用于多个子状态的复杂组件,能避免使用多个State的混乱。
注意事项
不可变状态: 不能直接修改状态的值,应该将新值返回
//错误用法
case 'ADD':
state.count += 1;
return state;
//正确用法
return {...state,count:state.count+1}
自定义Hook
它本质上是一个以use开头的JavaScript函数,可以将组件逻辑提取到可重用的函数中。
自定义Hook的好处:
- 提高代码复用性
- 简化代码逻辑
- 逻辑解耦
如何自定义Hook
- 命名约定:必须以
use开头(如useDebounce) - 状态隔离:每个使用Hook的组件都会获得完全独立的state和effects
- 组合能力:可以在自定义Hook中使用其他Hook
- 参数灵活:可以接受任意参数并返回任何需要的值
// 自定义防抖Hook
import { useEffect, useRef } from "react";
export function useDebounce<T extends (...args: never[]) => void>(
callback: T,
delay: number
): (...args: Parameters<T>) => void {
const timerRef = useRef<number | null>(null);
// 清除定时器的副作用
useEffect(() => {
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
};
}, []);
return (...args: Parameters<T>) => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
timerRef.current = setTimeout(() => {
callback(...args);
}, delay);
};
}
如何做好一个Hook
要做好一个Hook,就必须遵守以下准则:
- 单一准则 : 一个Hook只做一件事;
- 命名明确 : 准确表达Hook作用;
- 完整类型 :用TypeScript规定完整类型定义;
自定义Hook是React应用中强大的抽象工具,正确地使用自定义Hook能快速提高我们开发效率和代码质量。
结语
React Hooks 通过函数式编程范式重构了组件开发模式,让逻辑复用更简单、状态管理更灵活。掌握useState/useEffect 等基础 Hook 是入门关键,而自定义 Hook 则是提升开发效率的核心能力。
希望这篇文章能帮你快速入门React Hooks。
创作不易, 简单的赞是最大的支持❀