React18.3 hooks文档

240 阅读10分钟

React 18.3 官方 Hooks 详解

React 18.3 引入了多个官方 Hooks,这些 Hooks 使得在函数组件中管理状态和副作用变得更加简洁和高效。本文将详细介绍所有官方 Hooks,包括其用法、示例代码以及实际应用场景。

目录

  1. 基础 Hooks
  2. 额外 Hooks
  3. 自定义 Hooks
  4. Hooks 使用示例
  5. 总结

基础 Hooks

useState

useState 是最基本的 Hook,用于在函数组件中添加状态。

语法
const [state, setState] = useState(initialState);
  • state:当前的状态值。
  • setState:更新状态的函数。
  • initialState:状态的初始值。
示例
import React, { useState } from 'react';

function Counter() {
    const [count, setCount] = useState(0);

    const increment = () => {
        setCount(prevCount => prevCount + 1);
    };

    return (
        <div>
            <p>当前计数:{count}</p>
            <button onClick={increment}>+1</button>
        </div>
    );
}

export default Counter;
详细解释

在上面的示例中:

  1. 导入 Hook:从 react 中导入 useState
  2. 初始化状态:调用 useState(0) 初始化 count0,同时获取 setCount 方法用于更新状态。
  3. 更新状态increment 函数使用 setCount 来更新 count 的值,每次点击按钮时,count 增加 1
  4. 渲染:组件渲染当前的 count 值,并提供一个按钮触发 increment 函数。

useEffect

useEffect 用于在函数组件中执行副作用操作,如数据获取、订阅、手动更改 DOM 等。

语法
useEffect(() => {
    // 副作用逻辑

    return () => {
        // 清理逻辑
    };
}, [dependencies]);
  • 副作用逻辑:在组件渲染后执行的代码。
  • 清理逻辑:在组件卸载或依赖项变化前执行的清理代码。
  • dependencies:依赖项数组,控制副作用的执行时机。
示例
import React, { useState, useEffect } from 'react';

function Timer() {
    const [seconds, setSeconds] = useState(0);

    useEffect(() => {
        const interval = setInterval(() => {
            setSeconds(prev => prev + 1);
        }, 1000);

        // 清理定时器
        return () => clearInterval(interval);
    }, []);

    return (
        <div>
            <p>已运行 {seconds} 秒</p>
        </div>
    );
}

export default Timer;
详细解释
  1. 初始化状态seconds 初始化为 0
  2. 设置副作用useEffect 中设置一个定时器,每秒更新一次 seconds
  3. 清理副作用:返回一个函数,清除定时器,防止内存泄漏。
  4. 依赖项数组:传递空数组 [],意味着此副作用仅在组件挂载和卸载时执行一次。

useContext

useContext 用于在函数组件中使用 React 上下文(Context),避免通过层层传递 props。

语法
const contextValue = useContext(Context);
  • Context:通过 React.createContext 创建的上下文对象。
  • contextValue:上下文的当前值。
示例
import React, { useContext } from 'react';

// 创建上下文
const ThemeContext = React.createContext('light');

function ThemedButton() {
    const theme = useContext(ThemeContext);

    return (
        <button className={theme}>
            当前主题:{theme}
        </button>
    );
}

function App() {
    return (
        <ThemeContext.Provider value="dark">
            <ThemedButton />
        </ThemeContext.Provider>
    );
}

export default App;
详细解释
  1. 创建上下文:使用 React.createContext 创建 ThemeContext,默认值为 'light'
  2. 使用上下文:在 ThemedButton 组件中,通过 useContext(ThemeContext) 获取当前上下文的值。
  3. 提供上下文:在 App 组件中,使用 ThemeContext.Provider 提供 'dark' 作为上下文值。
  4. 渲染ThemedButton 显示当前主题为 'dark'

额外 Hooks

useReducer

useReduceruseState 的替代方案,适用于复杂的状态逻辑和多个状态值之间的依赖。

语法
const [state, dispatch] = useReducer(reducer, initialArg, init);
  • reducer:纯函数 (state, action) => newState
  • initialArg:初始状态值。
  • init:可选的初始化函数。
示例
import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 };
        case 'decrement':
            return { count: state.count - 1 };
        default:
            throw new Error();
    }
}

function Counter() {
    const [state, dispatch] = useReducer(reducer, initialState);

    return (
        <div>
            <p>计数:{state.count}</p>
            <button onClick={() => dispatch({ type: 'increment' })}>+1</button>
            <button onClick={() => dispatch({ type: 'decrement' })}>-1</button>
        </div>
    );
}

export default Counter;
详细解释
  1. 定义初始状态initialState{ count: 0 }
  2. 定义 reducer:根据 action.type 更改状态。
  3. 使用 HookuseReducer(reducer, initialState) 返回当前状态和 dispatch 方法。
  4. 分发动作:通过调用 dispatch 方法发送动作对象来更新状态。

useCallback

useCallback 返回一个记忆化的回调函数,避免在每次渲染时创建新的函数实例,优化性能。

语法
const memoizedCallback = useCallback(() => {
    // 回调逻辑
}, [dependencies]);
  • 回调逻辑:需要记忆化的函数。
  • dependencies:依赖项数组,控制回调函数的重新创建。
示例
import React, { useState, useCallback } from 'react';
import Child from './Child';

function Parent() {
    const [count, setCount] = useState(0);

    const increment = useCallback(() => {
        setCount(prev => prev + 1);
    }, []);

    return (
        <div>
            <p>父组件计数:{count}</p>
            <Child onIncrement={increment} />
        </div>
    );
}

export default Parent;
// src/view/Child.js
import React from 'react';

function Child({ onIncrement }) {
    console.log('Child 重新渲染');
    return (
        <button onClick={onIncrement}>子组件 +1</button>
    );
}

export default React.memo(Child);
详细解释
  1. 定义回调函数:在 Parent 组件中,使用 useCallback 记忆化 increment 函数。
  2. 传递给子组件:将 increment 作为 onIncrement 属性传递给 Child 子组件。
  3. 优化子组件Child 使用 React.memo 包裹,只有在 onIncrement 改变时重新渲染。
  4. 避免不必要渲染:由于 increment 是记忆化的,Child 组件不会因为父组件的其他状态变化而重新渲染。

useMemo

useMemo 返回一个记忆化的值,避免在每次渲染时执行高开销的计算。

语法
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • 计算逻辑:需要记忆化的计算函数。
  • dependencies:依赖项数组,控制计算函数的重新执行。
示例
import React, { useState, useMemo } from 'react';

function ExpensiveComponent({ num }) {
    const computeFactorial = (n) => {
        console.log('计算阶乘');
        return n <= 0 ? 1 : n * computeFactorial(n - 1);
    };

    const factorial = useMemo(() => computeFactorial(num), [num]);

    return (
        <div>
            <p>{num} 的阶乘是 {factorial}</p>
        </div>
    );
}

function App() {
    const [number, setNumber] = useState(1);
    const [text, setText] = useState('');

    return (
        <div>
            <input
                type="number"
                value={number}
                onChange={(e) => setNumber(parseInt(e.target.value))}
            />
            <ExpensiveComponent num={number} />
            <input
                type="text"
                value={text}
                onChange={(e) => setText(e.target.value)}
                placeholder="输入文本"
            />
            <p>输入内容:{text}</p>
        </div>
    );
}

export default App;
详细解释
  1. 定义计算函数computeFactorial 计算阶乘,并在每次计算时打印日志。
  2. 使用 useMemouseMemo 缓存 computeFactorial(num) 的结果,只有当 num 变化时才重新计算。
  3. 优化性能:在 App 组件中,输入文本 text 改变时,ExpensiveComponent 不会重新计算阶乘,避免不必要的开销。

useRef

useRef 返回一个可变的 Ref 对象,可以在整个组件生命周期内保持不变,常用于访问 DOM 元素或保持可变变量。

语法
const refContainer = useRef(initialValue);
  • initialValue:Ref 的初始值。
  • refContainer:包含 .current 属性的对象。
示例
import React, { useRef } from 'react';

function TextInput() {
    const inputRef = useRef(null);

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

    return (
        <div>
            <input ref={inputRef} type="text" placeholder="输入文本" />
            <button onClick={focusInput}>聚焦输入框</button>
        </div>
    );
}

export default TextInput;
详细解释
  1. 创建 Ref:使用 useRef(null) 创建一个 Ref 对象 inputRef,初始值为 null
  2. 绑定 Ref:将 inputRef 绑定到 <input> 元素上,通过 ref={inputRef}
  3. 访问 DOM 元素focusInput 函数通过 inputRef.current 访问实际的 DOM 元素,并调用 focus() 方法聚焦输入框。
  4. 保持引用inputRef 在组件的整个生命周期内保持不变,不会因重新渲染而丢失。

useImperativeHandle

useImperativeHandleforwardRef 一起使用,允许自定义暴露给父组件的实例值。

语法
useImperativeHandle(ref, () => ({
    // 暴露的方法或属性
}), [dependencies]);
  • ref:来自 forwardRef 的 Ref 对象。
  • 返回对象:父组件可以访问的属性或方法。
  • dependencies:依赖项数组,控制实例值的更新。
示例
import React, { useImperativeHandle, useRef, forwardRef } from 'react';

const CustomInput = forwardRef((props, ref) => {
    const inputRef = useRef();

    useImperativeHandle(ref, () => ({
        focus: () => {
            inputRef.current.focus();
        },
        clear: () => {
            inputRef.current.value = '';
        }
    }));

    return <input ref={inputRef} type="text" />;
});

function Parent() {
    const inputRef = useRef();

    return (
        <div>
            <CustomInput ref={inputRef} />
            <button onClick={() => inputRef.current.focus()}>聚焦输入框</button>
            <button onClick={() => inputRef.current.clear()}>清空输入框</button>
        </div>
    );
}

export default Parent;
详细解释
  1. 创建子组件CustomInput 使用 forwardRef 接收父组件传递的 ref
  2. 使用 useImperativeHandle:在子组件中,useImperativeHandle 定义了 focusclear 方法,暴露给父组件。
  3. 父组件调用方法:在 Parent 组件中,通过 inputRef.current.focus()inputRef.current.clear() 调用子组件暴露的方法。

useLayoutEffect

useLayoutEffectuseEffect 类似,但在 DOM 更新后同步执行,适用于需要立即读取或同步更改 DOM 的情况。

语法
useLayoutEffect(() => {
    // 副作用逻辑

    return () => {
        // 清理逻辑
    };
}, [dependencies]);
  • 副作用逻辑:在 DOM 更新后立即执行。
  • 清理逻辑:在依赖项变化或组件卸载前执行。
  • dependencies:依赖项数组,控制副作用的执行时机。
示例
import React, { useState, useLayoutEffect, useRef } from 'react';

function LayoutEffectDemo() {
    const [width, setWidth] = useState(0);
    const divRef = useRef();

    useLayoutEffect(() => {
        const divWidth = divRef.current.getBoundingClientRect().width;
        setWidth(divWidth);
    }, []);

    return (
        <div>
            <div ref={divRef} style={{ width: '50%' }}>
                这个 div 的宽度是父容器的 50%
            </div>
            <p>div 宽度:{width}px</p>
        </div>
    );
}

export default LayoutEffectDemo;
详细解释
  1. 创建 RefdivRef 用于引用 <div> 元素。
  2. 使用 useLayoutEffect:在 DOM 更新后立即获取 <div> 的宽度,并更新状态 width
  3. 渲染宽度:显示获取到的 <div> 宽度值。
  4. 不同于 useEffectuseLayoutEffect 中的代码会在浏览器绘制前同步执行,适合需要在渲染前读取布局的情况。

useDebugValue

useDebugValue 用于在 React DevTools 中显示自定义 Hook 的调试信息。

语法
useDebugValue(value);
  • value:要在 DevTools 中显示的值。
示例
import React, { useState, useEffect, useDebugValue } from 'react';

function useFriendStatus(friendID) {
    const [isOnline, setIsOnline] = useState(null);

    useEffect(() => {
        function handleStatusChange(status) {
            setIsOnline(status.isOnline);
        }

        // 假设订阅好友状态的方法
        ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
        return () => {
            ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
        };
    }, [friendID]);

    useDebugValue(isOnline ? '在线' : '离线');

    return isOnline;
}

function FriendStatus(props) {
    const isOnline = useFriendStatus(props.friend.id);

    if (isOnline === null) {
        return '加载中...';
    }
    return isOnline ? '在线' : '离线';
}

export default FriendStatus;
详细解释
  1. 定义自定义 HookuseFriendStatus 用于订阅好友的在线状态。
  2. 使用 useDebugValue:将 isOnline 状态转换为 '在线''离线',便于在 DevTools 中查看。
  3. 查看调试信息:在 React DevTools 中,开发者可以看到 useFriendStatus Hook 的当前状态值。

自定义 Hooks

自定义 Hooks 允许你封装可复用的逻辑,使组件更加简洁和可维护。

示例:使用自定义 Hook 处理表单输入

import React, { useState } from 'react';

// 定义自定义 Hook
function useFormInput(initialValue) {
    const [value, setValue] = useState(initialValue);

    const handleChange = (e) => {
        setValue(e.target.value);
    };

    return {
        value,
        onChange: handleChange
    };
}

function Form() {
    const name = useFormInput('');
    const email = useFormInput('');

    const handleSubmit = (e) => {
        e.preventDefault();
        console.log('姓名:', name.value);
        console.log('邮箱:', email.value);
    };

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label>姓名:</label>
                <input type="text" {...name} />
            </div>
            <div>
                <label>邮箱:</label>
                <input type="email" {...email} />
            </div>
            <button type="submit">提交</button>
        </form>
    );
}

export default Form;
详细解释
  1. 定义自定义 HookuseFormInput 管理输入值和变化处理。
  2. 使用 Hook:在 Form 组件中,分别为 姓名邮箱 创建输入状态。
  3. 绑定输入:通过展开运算符 {...name}{...email}valueonChange 绑定到 <input> 元素。
  4. 提交表单:在提交时,打印输入的值。

Hooks 使用示例

以下是一个综合示例,展示如何结合使用多个 Hooks 来构建一个功能丰富的组件。

// src/hooks/useFetch.js
import { useState, useEffect, useDebugValue } from 'react';

function useFetch(url) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        let isMounted = true;
        setLoading(true);
        fetch(url)
            .then(response => response.json())
            .then(json => {
                if (isMounted) {
                    setData(json);
                    setLoading(false);
                }
            })
            .catch(err => {
                if (isMounted) {
                    setError(err);
                    setLoading(false);
                }
            });
        return () => {
            isMounted = false;
        };
    }, [url]);

    useDebugValue(data ? '数据加载完成' : '加载中');

    return { data, loading, error };
}

export default useFetch;
// src/view/UserList.js
import React, { useState, useCallback } from 'react';
import useFetch from '../hooks/useFetch';

function UserList() {
    const [url, setUrl] = useState('https://jsonplaceholder.typicode.com/users');
    const { data, loading, error } = useFetch(url);

    const refresh = useCallback(() => {
        setUrl('https://jsonplaceholder.typicode.com/users');
    }, []);

    if (loading) return <p>加载中...</p>;
    if (error) return <p>发生错误:{error.message}</p>;

    return (
        <div>
            <h1>用户列表</h1>
            <button onClick={refresh}>刷新</button>
            <ul>
                {data.map(user => (
                    <li key={user.id}>{user.name} ({user.email})</li>
                ))}
            </ul>
        </div>
    );
}

export default UserList;
详细解释
  1. 自定义 Hook useFetch

    • 管理数据、加载状态和错误状态。
    • 使用 useEffect 发起数据请求,并在组件卸载时取消更新状态。
    • 使用 useDebugValue 提供调试信息。
  2. UserList 组件

    • 使用 useState 管理请求的 URL。
    • 调用 useFetch 获取用户数据。
    • 使用 useCallback 记忆化 refresh 函数,避免不必要的重新渲染。
    • 根据加载状态和错误状态渲染不同的内容。
    • 显示用户列表,并提供刷新按钮重新发起数据请求。

总结

React Hooks 为函数组件带来了强大的功能,使得状态管理和副作用处理更加简洁和高效。本文详细介绍了 React 18.3 中的所有官方 Hooks,包括基础 Hooks、额外 Hooks 以及自定义 Hooks,并通过示例代码展示了它们的实际应用。掌握这些 Hooks 能够帮助开发者编写更清晰、可维护的 React 应用。

附录:完整代码示例

由于篇幅限制,本节仅提供上述示例的完整代码文件结构和内容概要。

import { useState, useEffect, useDebugValue } from 'react';

function useFetch(url) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        let isMounted = true;
        setLoading(true);
        fetch(url)
            .then(response => response.json())
            .then(json => {
                if (isMounted) {
                    setData(json);
                    setLoading(false);
                }
            })
            .catch(err => {
                if (isMounted) {
                    setError(err);
                    setLoading(false);
                }
            });
        return () => {
            isMounted = false;
        };
    }, [url]);

    useDebugValue(data ? '数据加载完成' : '加载中');

    return { data, loading, error };
}

export default useFetch;
import React, { useState, useCallback } from 'react';
import useFetch from '../hooks/useFetch';

function UserList() {
    const [url, setUrl] = useState('https://jsonplaceholder.typicode.com/users');
    const { data, loading, error } = useFetch(url);

    const refresh = useCallback(() => {
        setUrl('https://jsonplaceholder.typicode.com/users');
    }, []);

    if (loading) return <p>加载中...</p>;
    if (error) return <p>发生错误:{error.message}</p>;

    return (
        <div>
            <h1>用户列表</h1>
            <button onClick={refresh}>刷新</button>
            <ul>
                {data.map(user => (
                    <li key={user.id}>{user.name} ({user.email})</li>
                ))}
            </ul>
        </div>
    );
}

export default UserList;
import React, { useState } from 'react';

// 定义自定义 Hook
function useFormInput(initialValue) {
    const [value, setValue] = useState(initialValue);

    const handleChange = (e) => {
        setValue(e.target.value);
    };

    return {
        value,
        onChange: handleChange
    };
}

function Form() {
    const name = useFormInput('');
    const email = useFormInput('');

    const handleSubmit = (e) => {
        e.preventDefault();
        console.log('姓名:', name.value);
        console.log('邮箱:', email.value);
    };

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label>姓名:</label>
                <input type="text" {...name} />
            </div>
            <div>
                <label>邮箱:</label>
                <input type="email" {...email} />
            </div>
            <button type="submit">提交</button>
        </form>
    );
}

export default Form;
import React, { useState, useCallback } from 'react';
import Child from './Child';

function Parent() {
    const [count, setCount] = useState(0);

    const increment = useCallback(() => {
        setCount(prev => prev + 1);
    }, []);

    return (
        <div>
            <p>父组件计数:{count}</p>
            <Child onIncrement={increment} />
        </div>
    );
}

export default Parent;
import React from 'react';

function Child({ onIncrement }) {
    console.log('Child 重新渲染');
    return (
        <button onClick={onIncrement}>子组件 +1</button>
    );
}

export default React.memo(Child);

以上代码示例展示了如何在不同场景下使用 React Hooks,包括状态管理、性能优化、处理副作用等。通过这些示例,开发者可以更好地理解和掌握 React Hooks 的使用方法。

参考资料