Hook
React Hook 是 React 16.8 版本引入的一项新特性,它可以使函数组件也拥有状态管理机制和一些副作用处理机制。
Hook 一览:
- 组件状态处理相关:useState、useContext、useReducer
- 处理副作用:useEffect、useLayoutEffect
- 性能优化相关:useMemo、useCallback
- DOM 相关:useRef
- redux 相关:useSelector、useDispatch、useStore
- 用户自定义 hook 或 库里面带的 hook
useState
function Test() {
const [count, setCount] = React.useState(0)
const change = () => setCount(count + 1)
return <div>
{count}
<button onClick={change}>click</button>
</div>
}
useState 返回一个 state 和更新 state 的函数。
这个 newState 如果是对象的话,更新就要先拷贝之前的对象,再把要更新的对象合并进去。
const [state, setState] = useState({ name: 'oldName', age: 18});
setState({ ...state, name: 'newName' });
useEffect
useEffect 是 React Hook 中的一个函数,用于处理函数组件中的副作用。类比生命周期函数包括 componentDidMount、componentDidUpdate 和 componentWillUnmount。
useEffect(setup, dependencies?)
useEffect接受两个参数:
第一个参数函数:需要执行的函数。setup 函数里还可以选择返回一个 cleanup 函数,cleanup 函数会在组件卸载时执行。
第二个参数依赖数组:这是 setup 代码内部引用的所有响应式值的列表。响应式值包括 props、state 以及直接在组件主体中声明的所有变量和函数。依赖数组可以不传递、传空数组和非空数组。
依赖数组的不同情况:
- 没有依赖数组:在组件挂载卸载和每次重新渲染的时候都会执行。
- 空数组:在组件挂载卸载的时候执行。
- 依赖数组:依赖项发生变化时执行。
副作用和纯函数
副作用是和纯函数相关的一个词,纯函数是指函数执行过程中,只有返回值,它的过程中不会让外部环境产生变化:包括但不限于修改外部变量、状态、发送网络请求、操作 DOM 等。它们只依赖于传入的参数,并且仅根据参数的值来计算返回值。
而且给定相同的输入,纯函数总是返回相同的输出,不受外部环境或状态的影响。
而副作用就是函数执行过程中,对外部环境进行的操作,在前端项目中例如数据请求等。
useContext
useContext 用于父组件向子孙组件传递数据,不需要再通过 props 从父组件向子组件逐层传递。
例子:明暗主题覆盖
import { createContext, useContext } from 'react';
const ThemeContext = createContext(null);
export default function MyApp() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
)
}
function Panel({ title, children }) {
const theme = useContext(ThemeContext);
const className = 'panel-' + theme;
return (
<section className={className}>
<h1>{title}</h1>
{children}
</section>
)
}
定义一个 context 变量,用于存放当前上下文对象。将上下文对象的 Provider 作为父组件,通过 value 属性将要传递的值传给子孙组件。在子孙组件中就可以通过 useContext 获取到要传递的值。
https://react.dev/reference/react/useContext
useReducer
Reducer 是处理状态的另一种方式,它是类似 Redux 的写法,将设置状态的逻辑修改成 dispatch 的一个 action,将 state 和 action 传入 reducer 来获得新的状态。
例子:计数器
function Counter() {
const initState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return { count: state };
}
}
const [state, dispatch] = useReducer(reducer, initState);
return (
<div>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<span>{state.count}</span>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</div>
);
}
useRef
useRef 主要用于获取组件中的 dom 对象,来操作 DOM。但是实际上它可以保存任何值。
const ref = useRef(initialValue)
返回一个只有一个属性 current 的对象,current 初始值为 initialValue。之后可以将其设置为其他值。如果将 ref 传递给一个 JSX 节点的 ref 属性,React 将为它设置 current 属性。
改变 ref.current 属性时,React 不会重新渲染组件。
例子:用于操作DOM节点
import { useRef } from 'react';
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>
聚焦输入框
</button>
</>
);
}
例子:使用 useRef 来保存不需要变化的值
因为 useRef 的返回值在组件的每次 redner 之后都是同一个,所以它可以用来保存一些在组件整个生命周期都不需要变化的值。最常见的就是定时器的清除场景。
如果 timer 被声明为一个普通的变量,而不是使用 useState 或 useRef 来声明。这意味着每次组件重新渲染时,都会创建一个新的 timer 变量,而之前的定时器的引用会丢失。因此,当点击停止按钮时,clearTimer 函数中使用的 timer 变量与当前定时器不是同一个,所以清除定时器的操作失效。使用 useRef 来声明 timer,以确保在多次渲染之间保持其引用不变。这样,即使组件重新渲染,定时器的引用也会保持不变,从而可以正确地清除定时器。
const App = () => {
const timer = useRef();
useEffect(() => {
timer.current = setInterval(() => {
console.log('触发了');
}, 1000);
},[]);
const clearTimer = () => {
clearInterval(timer.current);
}
return (
<>
<Button onClick={clearTimer}>停止</Button>
</>)
}
https://react.docschina.org/reference/react/useRef#referencing-a-value-with-a-ref
https://www.jianshu.com/p/c1240516c44c
useMemo
为了优化组件渲染的性能问题,有时候父组件重新渲染子组件不需要重新渲染,如果子组件的逻辑较复杂,就是无意义的大量计算,浪费资源。
class 解决此问题可以使用 shouldCompnentUpdate(nextProps, nextState) 生命周期,在组件更新之前,判断当前组件是否受某个state或者prop更改的影响。而在函数组件中,也不再区分mount和update两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。
useMemo和useCallback都是解决上述性能问题的。
useMemo 用于对计算成本较高的值进行记忆化处理,以提高性能。它类似于 useEffect,但主要用于缓存和返回一个计算值,而不是执行副作用操作。
const memoizedValue = useMemo(() => computeExpensiveValue(dependencies), dependencies);
参数:
- computeExpensiveValue 是一个函数,用于计算成本较高的值。
- dependencies 是一个依赖数组,用于指定在其中某些值发生变化时才重新计算 computeExpensiveValue。如果依赖数组为空,则表示只在组件的每次渲染周期中都重新计算值。
返回:
useMemo 返回的是一个记忆化的值,当依赖数组中的某些值发生变化时,才会重新计算并返回新的值。否则,它会返回上一次计算得到的值,避免了不必要的重复计算。
import React, { useMemo } from 'react';
function ExpensiveComponent({ a, b }) {
const expensiveValue = useMemo(() => {
console.log('Compute expensive value');
return a + b;
}, [a, b]); // 只有在 a 或 b 发生变化时才重新计算值
return <div>{expensiveValue}</div>;
}
在这个示例中,useMemo 缓存了计算结果 a + b,并且只有当 a 或 b 的值发生变化时,才会重新计算结果。这样可以避免在每次组件渲染时都执行成本较高的计算操作。expensiveValue 没有变化的时候,即使父组件重新渲染了,子组件也不会重新渲染。
当需要对大量的数据进行处理和转换时,比如从服务器获取的原始数据需要进行筛选、排序、格式化等操作,这些计算都会耗费一定的时间。通过使用 useMemo 缓存处理后的数据,可以避免在每次组件渲染时都执行这些计算,提高页面性能。
React.memo
React.memo 是一个高阶组件,用于包装函数组件,以实现浅比较 props 的功能。当组件的 props 发生变化时,React.memo 会比较前后两次 props 是否相同,如果相同则跳过重新渲染。示例:
import React from 'react';
const MyComponent = React.memo(({ value }) => {
return <div>{value}</div>;
});
export default MyComponent;
useCallBack
useMemo 是对数据的记忆,useCallback 是对函数的记忆。
useMemo 用于对函数进行记忆化处理,以避免在每次渲染时都创建新的函数实例。它主要用于优化性能,特别是在处理函数作为 props 传递给子组件时。
const memoizedCallback = useCallback(callback, dependencies);
useCallback 返回的是一个记忆化的函数,当依赖数组中的某些值发生变化时,才会重新创建函数实例。否则,它会返回上一次创建的函数实例,避免了不必要的函数重新创建。
import React, { useCallback, useState } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
// 使用 useCallback 缓存回调函数,只在 count 发生变化时才重新创建函数实例
const handleClick = useCallback(() => {
console.log('Button clicked');
}, [count]);
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<ChildComponent onClick={handleClick} />
</div>
);
}
function ChildComponent({ onClick }) {
return <button onClick={onClick}>Click me</button>;
}
在这个示例中,useCallback 缓存了 handleClick 回调函数,并且只有在 count 的值发生变化时才会重新创建函数实例。这样可以避免在每次父组件渲染时都创建新的函数实例,提高性能。
自定义Hook
自定义 Hook 就是自己封装的函数功能和 react 中内置的 Hook 进行结合,用于组件间共享逻辑,本质是一个函数。自定义 Hook 必须以 use 开头。
import { useEffect, useState } from 'react';
// 自定义 Hook:用于跟踪组件的宽度变化
function useComponentWidth() {
const [width, setWidth] = useState(0);
useEffect(() => {
// 定义函数来更新组件宽度
function updateWidth() {
setWidth(window.innerWidth);
}
// 添加事件监听器来在窗口大小变化时更新宽度
window.addEventListener('resize', updateWidth);
// 初始化时获取一次宽度
updateWidth();
// 在组件卸载时移除事件监听器
return () => {
window.removeEventListener('resize', updateWidth);
};
}, []); // 依赖数组为空,表示只在组件挂载和卸载时执行一次
return width;
}
// 使用自定义 Hook 来获取组件宽度
function MyComponent() {
const width = useComponentWidth();
return <div>The width of the component is: {width}px</div>;
}