带你快速入门那些常用React Hooks...

206 阅读8分钟

前言

最近在重新学习 React 时,我越发体会到 React Hooks 的重要性。

本文将介绍 React 中最常用的几个 Hooks,包括它们的基本用法、适用场景以及一些最佳实践。无论你是刚开始接触 Hooks,还是希望更深入地掌握它们,相信这篇文章都能给你带来一些帮助。

接下来,让我们一起看看这些 Hooks 如何优化 React 开发体验。

eeb03be85c05026c9320a972ed05308.jpg

React Hooks简介

在React 16.8的时候引入了React Hooks,为什么要引入Hooks呢? 那就得了解没有Hooks之前的痛点:

  1. 在组件之间的复用很难
  2. 冗长的代码,难以维护
  3. 难以理解的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接收俩个参数: setupdependencies

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()
  },[])
  1. 依赖项为空和依赖项为空数组是俩个概念,依赖项为空时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}
    </>
}

注意事项

  1. 使用context时,传递的对象key值必须是value
  2. 如果需要更新Context,请与useState结合 <Mycontext.Provider value={value}> </Mycontext.Provider>
  const [context,setContext] = useState('我是context')
  const value = {
    context,
    setContext
  }
  1. 使用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>
  );
}

注意事项

  1. 避免重复创建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的好处:

  1. 代码更加有逻辑性,可扩展性更强 ;
  2. 性能优化,减少渲染次数 ;
  3. 更容易测试和排错 ;

基本使用

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的好处:

  1. 提高代码复用性
  2. 简化代码逻辑
  3. 逻辑解耦

如何自定义Hook

  1. 命名约定:必须以use开头(如useDebounce
  2. 状态隔离:每个使用Hook的组件都会获得完全独立的state和effects
  3. 组合能力:可以在自定义Hook中使用其他Hook
  4. 参数灵活:可以接受任意参数并返回任何需要的值
// 自定义防抖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。

创作不易, 简单的赞是最大的支持❀

db838dc92a7e1891aa6b41de3e14fc0.jpg