React常见的Hook钩子函数

发布于:2025-08-30 ⋅ 阅读:(16) ⋅ 点赞:(0)
1、useState

useState 在函数组件中管理状态,useState 可以传递一个参数,做为状态的初始值,返回一个数组,数组的第一个元素是返回的状态变量,第二个是修改状态变量的函数

// 代码示例
import { useState } from 'react';
 
function Demo() {
    const [count, setCount] = useState(0);
 
    const add = () => {
        setCount(count + 1);
    };
 
    return (
        <div>
            <button onClick={add}>+1</button>
            <p>{`count: ${count}`}</p>
        </div>
    );
}
 
export default Demo;

注意:在类组件中 this.setState 是异步执行的,同样 useState 修改状态也是异步的。也就是每次修改状态不是立马生效的。那如何在每次修改状态后可以拿到最新的数据呢?就可以用我们下面讲的useEffect实现。

2、useEffect

useEffect又称副作用hooks。作用:给没有生命周期的组件,添加结束渲染的信号。执行时机:在渲染结束之后执行。每个effect函数都属于一次特定的渲染:

  1. useEffect调度不会阻塞浏览器更新屏幕<异步>
  2. 每次重新渲染都会生成新的effect,替换掉之前的,确保effect中获取的值是最新的,不用担心过期。
  • 什么是副作用?
    • 副作用 ( side effect ): 数据获取,数据订阅,以及手动更改 React 组件中的 DOM 都属于副作用
    • 因为我们渲染出的页面都是静态的,任何在其之后的操作都会对他产生影响,所以称之为副作用
useEffect(() => {
  // 此处编写 组件挂载之后和组件重新渲染之后执行的代码
  ...
 
  return () => {
    // 此处编写 组件即将被卸载前执行的代码
    ...
  }
}, [dep1, dep2 ...]); // 依赖数组

useEffect 可以传入2个参数,第1个参数为我们定义的执行函数,第2个参数是依赖关系(可选参数)。若一个函数组件中定义了多个useEffect,那么他们实际执行顺序是按照在代码中定义的先后顺序来执行的。

第一个参数中的代码是组件挂载和更新就会执行的代码。

return 出去的代码会在组件卸载时才会执行。

依赖数组不是必填项,如果不传则每次渲染都会去执行,传值的话在依赖项发生改变时函数中的代码才会执行,如果传空数组则会在组件第一次挂载才会执行。

// 代码示例
import { useState, useEffect } from 'react';
 
function Demo() {
    const [a, setA] = useState(0);
 
    useEffect(() => {
    	// 点击一次按钮就会触发一次useEffect
        console.log("执行了useEffect");
        return () => {
            console.log("组件卸载");
        }
    }, [a]);
 
    const add = () => {
        setA(a + 1);
    };
 
    return (
        <div>
            <button onClick={add}>add</button>
            <p>{`a: ${a}`}</p>
        </div>
    );
}
 
export default Demo;
清除副作用

上面写的都是一些不需要清除的副作用,只是回调触发一些简单的方法,但是有一些副作用是需要清除的。例如绑定一些DOM事件,在这种情况下,清除工作是非常重要的,可以防止引起内存泄露,例如下面给出的代码对比

未清除副作用的情况下。此时第一次点击正常输出一次打印当前位置,而后每一次useEffect调用都会新绑定一个updateMouse方法,那么点击一次所触发绑定的方法越来越多,那么之后点击一次就会疯狂打印打印当前位置,这也就造成了页面性能、内存泄露等问题

const [positions,setPositions] = useState({ x:0,y:0 })

useEffect( () => {
    console.log('2222函数式组件结束渲染')

    const updateMouse = (e) => {
        console.log('打印当前位置')
        setPositions({ x:e.clientX, y:e.clientY })
    }
    document.addEventListener('click',updateMouse)
})

return (
    <div>
        <p>x:{ positions.x }</p>
        <p>y:{ positions.y }</p>
    </div>
)

清除副作用的情况下(仅修改部分代码,其它代码同上)。例如示例代码

  • 首次刷新或进入页面会先执行除return以外的内容,也就是会执行一个绑定的方法,然后将updateMouse方法绑定到click事件上
  • 并将改次useEffect中的事件清除返回出去,但是此时是并没有执行return中的内容的(重点注意)
  • 然后当你点击第一次的时候,就会打印设置当前鼠标页面坐标,然后先执行上一次return返回出去的内容,注意这里是执行上一次return中的清除事件绑定方法,然后执行该清除事件绑定方法,当然清除也是清除的上一个useEffect中的绑定事件
  • 然后再开始执行新的useEffect中的绑定事件方法,并再次将改次useEffect清除事件绑定的方法return返回出去,如此就形成了一个链式一样的过程
  • 当页面卸载的时候,会执行最后一次return返回出来的清除事件绑定的方法,这样也就保证了页面卸载的时候,移除了绑定添加的DOM事件方法
  • (上述写的执行过程并没有从原理出发去分析的,只是简单的描述。可能稍微有点乱,如果你理解不了,可以多看几遍并动手执行示例代码结合进行理解)
useEffect( () => {
    console.log('2222函数式组件结束渲染')
    const updateMouse = (e) => {
        console.log('打印当前位置')
        setPositions({ x:e.clientX, y:e.clientY })
    }
    document.addEventListener('click',updateMouse) //  添加绑定方法事件(要修改依赖,绑定到依赖上)

    return () => {
        //  在每次执行useEffect之前都会执行上一次return中内容
        document.removeEventListener('click',updateMouse)
        //  移除绑定方法事件(要修改依赖,绑定到依赖上)
        console.log('1111销毁')
    }
})
3、useMemo

useMemo 是为了减少组件重新渲染时不必要的函数计算,可以用来做性能优化

类似于vue中的计算属性,他的依赖值是后面数组中的值

const memoizedValue = useMemo(() => {
  // 计算逻辑
  ...
  // return res;
}, [a, b]);

useMemo 可以传入2个参数,第1个参数为函数,用来进行一些计算,第2个参数是依赖关系(可选参数),返回值为第一个函数 return 出去的值,只有在依赖项发生变化时才会重新执行计算函数进行计算,如果不传依赖项,每次组件渲染都会重新进行计算

// 代码示例
import { useState, useMemo } from 'react'
 
function Demo() {
    const [num, setNum] = useState(0);
 
 
    const addNum = () => {
        setNum(num + 100);
    };
 
    const total = useMemo(() => {
        console.log('---求和---');
        // 求和计算
        let temp = 0;
        for(let i = num; i > 0; i--) {
            temp += i;
        }
        return temp;
    }, [num]);
 
    return (
        <div>
            <button onClick={addNum}>addNum</button>
            <p>{`num: ${num}`}</p>
            <p>{`total: ${total}`}</p>
        </div>
    )
}
 
export default Demo;
4、useContext

在 React 中传递属性只能一层一层传,如果组件结构比较复杂,层级比较深的时候,数据传递起来就比较麻烦,可能会经过很多次的传递才能将属性传递到目标组件中,那么有没有一种可以在全局进行状态共享的实现方法呢?useContext 就是为了解决这个问题的,可以实现不必层层传递就能共享状态的功能。具体用法看下面步骤:

先封装一个js,里面可以设置初始值,这个初始值,可以在任何地方使用

import React from 'react';
// React.createContext()中的参数是默认值,可填可不填
const UserContext = React.createContext( { name: '张三' });
export default UserContext;
import React, { useContext } from 'react'
import UserContext from './context';
 
// const UserContext = React.createContext();
 
function Demo() {
	// 如果React.createContext没有指定默认值,也可以在对应子组件上套上UserContext.Provider来指定值
    return (
        // <UserContext.Provider value={{ name: '张三' }}>
            <Child />
        // </UserContext.Provider>
    )
}
 
 
 
function Child() {
 
    const user = useContext(UserContext);
    return (
        <div>
            <p>{`name: ${user.name}`}</p>
        </div>
    )
}
 
export default Demo;
5、useReducer

useReducer 也是用来实现状态管理的 hook,useState 就是基于 useReducer 实现的,useReducer 可以实现比 useState 更复杂的状态管理逻辑。

它可以对多个值进行管理,他的定义方式和useState很像,但是,useState 是基于它实现的,使用时注意定义的参数。

// 代码示例
import React, { useReducer } from 'react'
 
// 1.需要有一个 reducer 函数,第一个参数为之前的状态,第二个参数为行为信息
function reducer(state, action) {
    switch (action) {
        case 'add':
            return state + 1;
        case 'minus':
            return state - 1;
        default:
            return 0;
    }
}
 
 
 
function Demo() {
 
    // 2.引入useReducer,第一个参数时上面定义的reducer,第二个参数时初始值
    // 3.返回为一个数组,第一项为状态值,第二项为一个 dispatch 函数,用来修改状态值
    const [count, dispatch] = useReducer(reducer, 0);
    return (
        <div>
            <button onClick={() => { dispatch('add') }} >add</button>
            <button onClick={() => { dispatch('minus') }} >minus</button>
            <button onClick={() => { dispatch('unknown') }} >unknown</button>
            <p>{`count: ${count}`}</p>
        </div>
    );
}
 
export default Demo;
6、useRef

useRef 可以帮助我们获取 dom 和 react 组件实例,类组件中的 React.createRef() 也有相同的功能。

const xxxRef = useRef(initialValue);
// 使用 xxxRef.current 获取引用的值

------------------------------------------
  // 代码示例
import { useRef } from 'react'
 
function Demo() {
    const inputRef = useRef();
 
    const handleFocus = () => {
        // document.getElementById('my-input').focus();
        inputRef.current.value = 'focus';
        inputRef.current.focus();
    }
 
    const handleBlur = () => {
        // document.getElementById('my-input').blur();
        inputRef.current.value = 'blur';
        inputRef.current.blur();
    }
 
    return (
        <div>
            <input ref={inputRef} id="my-input" />
            <button onClick={handleFocus}>focus</button>
            <button onClick={handleBlur}>blur</button>
        </div>
    )
}
 
export default Demo;

除了用 useRef 获取组件实例,还可以用来存储变量的值,但是需要注意的一点是,修改 .current 的值不会触发组件的重新渲染,请看下面示例:

import { useState, useRef } from 'react'
 
function Demo() {
    const countRef = useRef(0);
    const [num, setNum] = useState(0);
 
    const addCount = () => {
        // 使用 useRef 去更新值并不会出发组件渲染
        countRef.current = countRef.current + 1;
    }
 
    const addNum = () => {
        // 使用 useState 去更新会触发组件渲染
        setNum(num + 1);
    }
 
    return (
        <div>
            <button onClick={addCount}>addCount</button>
            <button onClick={addNum}>addNum</button>
            <p>{`count: ${countRef.current}`}</p>
            <p>{`num: ${num}`}</p>
        </div>
    )
}
 
export default Demo
7、useCallback

返回一个缓存的回调函数,缓存一个函数类型因变量

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b]
);

useCallback 的用法和 useMemo 完全一样,useMemo 返回的是计算函数 return 出去的,而 useCallback 可以理解成返回的是那个计算函数

// 代码示例
import { useState, useCallback } from 'react'
 
function Demo() {
    const [count1, setCount1] = useState(0);
    const [count2, setCount2] = useState(0);
    const funcHang = useCallback(function() {
        console.log("function run:", count1, count2);
    }, [count1]);
 
    return (
        <div>
            <h2>params: { count1 } { count2 }</h2>
            <button onClick={ funcHang }>触发</button>
            <button onClick={ e => { setCount1(pre => pre + 1);} }>update Count1</button>
            <button onClick={ e => { setCount2(pre => pre + 1);} }>update Count2</button>
        </div>
    )
}
 
export default Demo;
  • 当count1变化,useCallback内部依赖比较发生变化,返回当前执行Demo上下文包裹的函数。
  • 当count2变化,useCallback内部依赖比较没有变化,返回之前Demo上下文包裹的函数。
8、useImperativeHandle

用于自定义暴露给父组件的实例值

  • 使用场景: 使父组件可以获取子组件实例的句柄,调用子组件的方法。
  • 注意事项:
    • 需要配合forwardRef来使用
    • 父组件通过ref获取子组件实例,子组件通过useImperativeHandle定义暴露给父组件的方法
import React, { useRef, forwardRef, useImperativeHandle } from 'react';
const Input = forwardRef((props, ref) => {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
  }));

  return <input ref={inputRef} {...props} />;
});

const App = () => {
  const inputRef = useRef();

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <Input ref={inputRef} />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
};
9、useLayoutEffect

与 useEffect 相同,但是会在所有DOM改变之后同步触发。

  • 使用场景: 在DOM更新后立即执行一些同步操作,例如获取DOM元素位置等。
  • 注意事项:
    • useEffect的执行时机不同,useLayoutEffect会在所有DOM变更之后同步执行
      -如果操作非常昂贵,应该考虑使用useEffect以免阻塞浏览器渲染
import React, { useLayoutEffect, useState } from 'react';

function App() {
  const [value, setValue] = useState(0);

  useLayoutEffect(() => {
    if (value === 0) {
      setValue(10 + Math.random() * 200);
    }
  }, [value]);

  console.log('render', value);

  return (
    <div onClick={() => setValue(0)}>
      Value: {value}
    </div>
  );
}
10、useDebugValue

用于在 React 开发工具中显示自定义 hook 的标签。

  • 使用场景: 在 React 开发者工具中显示自定义 Hook 的标签,方便调试。
  • 注意事项:
    • 只在开发环境下有效,生产环境会被自动忽略
    • 第一个参数是一个格式化的字符串,用于显示标签内容
import React, { useDebugValue, useState } from 'react';
function useCustomHook(initialValue) {
  const [value, setValue] = useState(initialValue);
  useDebugValue(`Current value: ${value}`);
  return [value, setValue];
}

function App() {
  const [value, setValue] = useCustomHook(0);
  return (
    <div>
      <p>Value: {value}</p>
      <button onClick={() => setValue(value + 1)}>Increment</button>
    </div>
  );
}