React18.3 hooks文档

React 18.3 官方 Hooks 详解

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

目录

  1. [基础 Hooks](#基础 Hooks "#%E5%9F%BA%E7%A1%80-hooks")
  2. [额外 Hooks](#额外 Hooks "#%E9%A2%9D%E5%A4%96-hooks")
  3. [自定义 Hooks](#自定义 Hooks "#%E8%87%AA%E5%AE%9A%E4%B9%89-hooks")
  4. [Hooks 使用示例](#Hooks 使用示例 "#hooks-%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B")
  5. 总结

基础 Hooks

useState

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

语法
javascript 复制代码
const [state, setState] = useState(initialState);
  • state:当前的状态值。
  • setState:更新状态的函数。
  • initialState:状态的初始值。
示例
javascript 复制代码
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 等。

语法
javascript 复制代码
useEffect(() => {
    // 副作用逻辑

    return () => {
        // 清理逻辑
    };
}, [dependencies]);
  • 副作用逻辑:在组件渲染后执行的代码。
  • 清理逻辑:在组件卸载或依赖项变化前执行的清理代码。
  • dependencies:依赖项数组,控制副作用的执行时机。
示例
javascript 复制代码
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。

语法
javascript 复制代码
const contextValue = useContext(Context);
  • Context :通过 React.createContext 创建的上下文对象。
  • contextValue:上下文的当前值。
示例
javascript 复制代码
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 的替代方案,适用于复杂的状态逻辑和多个状态值之间的依赖。

语法
javascript 复制代码
const [state, dispatch] = useReducer(reducer, initialArg, init);
  • reducer :纯函数 (state, action) => newState
  • initialArg:初始状态值。
  • init:可选的初始化函数。
示例
javascript 复制代码
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 返回一个记忆化的回调函数,避免在每次渲染时创建新的函数实例,优化性能。

语法
javascript 复制代码
const memoizedCallback = useCallback(() => {
    // 回调逻辑
}, [dependencies]);
  • 回调逻辑:需要记忆化的函数。
  • dependencies:依赖项数组,控制回调函数的重新创建。
示例
javascript 复制代码
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;
javascript 复制代码
// 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 返回一个记忆化的值,避免在每次渲染时执行高开销的计算。

语法
javascript 复制代码
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • 计算逻辑:需要记忆化的计算函数。
  • dependencies:依赖项数组,控制计算函数的重新执行。
示例
javascript 复制代码
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 元素或保持可变变量。

语法
javascript 复制代码
const refContainer = useRef(initialValue);
  • initialValue:Ref 的初始值。
  • refContainer :包含 .current 属性的对象。
示例
javascript 复制代码
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 一起使用,允许自定义暴露给父组件的实例值。

语法
javascript 复制代码
useImperativeHandle(ref, () => ({
    // 暴露的方法或属性
}), [dependencies]);
  • ref :来自 forwardRef 的 Ref 对象。
  • 返回对象:父组件可以访问的属性或方法。
  • dependencies:依赖项数组,控制实例值的更新。
示例
javascript 复制代码
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 的情况。

语法
javascript 复制代码
useLayoutEffect(() => {
    // 副作用逻辑

    return () => {
        // 清理逻辑
    };
}, [dependencies]);
  • 副作用逻辑:在 DOM 更新后立即执行。
  • 清理逻辑:在依赖项变化或组件卸载前执行。
  • dependencies:依赖项数组,控制副作用的执行时机。
示例
javascript 复制代码
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 的调试信息。

语法
javascript 复制代码
useDebugValue(value);
  • value:要在 DevTools 中显示的值。
示例
javascript 复制代码
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 处理表单输入

javascript 复制代码
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 来构建一个功能丰富的组件。

javascript 复制代码
// 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;
javascript 复制代码
// 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 应用。

附录:完整代码示例

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

javascript:title=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;
javascript:title=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;
javascript:title=src/view/Form.js 复制代码
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;
javascript:title=src/view/Parent.js 复制代码
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;
javascript:title=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);

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

参考资料

相关推荐
JarvanMo40 分钟前
借助FlutterFire CLI实现Flutter与Firebase的多环境配置
前端·flutter
Jedi Hongbin1 小时前
echarts自定义图表--仪表盘
前端·javascript·echarts
凯哥19701 小时前
Sciter.js指南 - 桌面GUI开发时使用第三方模块
前端
边洛洛1 小时前
对Electron打包的exe文件进行反解析
前端·javascript·electron
财神爷亲闺女1 小时前
js 实现pc端鼠标横向拖动滚动
前端
用户2031196600961 小时前
sheet在SwiftUI中的基本用法
前端
晴殇i1 小时前
一行代码搞定防抖节流:JavaScript新特性解析
前端·javascript
David凉宸1 小时前
HTML表单(二)
前端