react常用hooks

useState - 添加/修改state

  • useState 是最常用的 Hook,用于在函数组件中添加 state。

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

  • 用法:
jsx 复制代码
import React, { useState } from 'react';
function Counter() {
    // 声明一个叫做 "count" 的 state 变量,初始值为 0 5   
    const [count, setCount] = useState ( 0 ); 
    return (<div><p>You clicked {count} times</p>
        <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>);
} 

useReducer

  • 出自 Redux 作者,可以理解为 React 内部基于 Hooks 实现的类似 Redux 状态管理逻辑。
  • useReducer 是 useState 的替代方案,适用于 state 逻辑较复杂且包含多个子值,或下一个 state 依赖于之前的 state 的情况。
  • const [state, dispatch] = useReducer(reducer, initialState)

💡 其实,useState 是 useReducer 的语法糖,在实现上,useState 共用 useReducer 的底层实现。

  • 用法:
jsx 复制代码
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>Count: {state.count}</p>
        <button onClick={() => dispatch({ type: 'increment' })}>+</button>
        <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>);
}

useEffect - 监听依赖执行副作用 - 异步

useEffect(() => {/*副作用*/ return ()=>{/*清除副作用*/}}, [/*依赖项数组*/]);

  • useEffect 用于在函数组件中执行副作用操作,例如数据获取、订阅或手动更改 DOM。
  • useEffect还可以返回一个函数 ,这个返回的函数会在组件卸载时执行,用于清理副作用。
  • 依赖项为 [] 时,副作用func只在组件挂载时执行

useEffect(() => {/***挂载时**执行*/,return()=>{/***卸载时**执行*/}}, **[]** );

  • 依赖项不为空时,挂载时执行,依赖项中任意值更新(发生变化)时触发函数执行

useEffect(() => { () => {/***挂载时、count改变时**执行*/}, [count]);

  • 用法:
jsx 复制代码
import React, { useState, useEffect } from 'react';
function Example() {
    const [count, setCount] = useState(0);
    // 类似于 componentDidMount 和 componentDidUpdate
    useEffect(() => {
        // 使用浏览器 API 更新文档标题 
        document.title = `You clicked ${count} times`;
        // 清除副作用
        return () => {
            document.title = 'React App';
        };
    }, [count]); // 仅在 count 更改时重新运行
    
    return (<div>
        <p>You clicked {count} times</p>
        <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>);
}

useContext - 获取上下文-组件树之间共享数据

  • useContext 接受一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。用于在函数组件中访问 React 的上下文(Context)。
  • React Context 提供了一种在组件树中共享数据的方式,使得数据能够在多个组件之间传递,而无需通过层层传递 props(属性)
  • 创建 - const MyContext = React.createContext('initState')
  • 提供者 - <MyContext.Provider value={{name:'zh'}}>...
  • 消费者 - const state = useContext(MyContext)
  • 用法:
jsx 复制代码
import React, { useReducer, useContext } from 'react';
const ThemeContext = React.createContext('light');// 创建上下文

// 可修改值传送,一般 和 useReducer 或 useState 配合
const themeReducer = (state, action) => {// 创建 reducer
  switch (action.type) {
    case 'TOGGLE_THEME':
      return {...state, theme: state.theme === 'light'? 'dark' : 'light' };
    default:
      return state;
  }
};
const initialState = { theme: 'light' };// 设置初始值
/**
通常是在根组件或者接近根组件的位置,
提供者:上下文 ThemeContext.Provider value="传递值"
value直接传递单值 或 传递obj数据,如:value={{name:'name',age:age}}
**/
function App() {
    const [state, dispatch] = useReducer(themeReducer, initialState);// 可变传送
    return (
        <ThemeContext.Provider value={{fixedThem:'dark',state,dispatch}}>
            <ThemeButton />
        </ThemeContext.Provider>
    );
}

function ThemeButton() {
    const {fixedThem,state,dispatch} = useContext(ThemeContext);// 使用上下文
    // fixedThem 固定值 dark
    // state.them可编辑值,可使用dispatch修改
    const handleClick = () => {dispatch({ type: 'TOGGLE_THEME' });};
    return <button style={{ background: state.theme }} onClick={handleClick}>Theme Button</button>;
}

useMemo/useCallback - 根据依赖缓存 计算结果/函数

  • useMemo 和 useCallback 用于性能优化,分别用于记忆化计算结果 和记忆化函数
  • 用法:
jsx 复制代码
// useMemo(func,[依赖数据])  缓存计算结果,依赖变化时重新计算 
import React, { useMemo } from 'react';
function ExpensiveCalculationComponent({ number }) {
    const compute = (n) => {
        console.log('Expensive computation');
        return n * 2;
    };
    const result = useMemo( () =>  compute (number) , [number]);
    
    return <div>Result: {result}</div>;
}

// useCallback(func,[依赖数据])  缓存方法,依赖变化时重新创建方法。
import React, { useState, useCallback } from 'react';
function Button({ onClick, children }) {
    return <button onClick={onClick}>{children}</button>;
}
function App() {
    const [count, setCount] = useState(0);
    const handleClick = useCallback( () =>  { 
 setCount (count + 1 ); 
 } , [count]);
    
    return (<div>
        <p>Count: {count}</p>
        <Button onClick={handleClick}>Increment</Button>
    </div>);
}

useRef

  • useRef 返回一个可变的 ref 对象,该对象的 .current 属性被初始化为传入的参数(initialValue)。
  • useRef 常用于访问 DOM 元素 直接或存储某个可变值,该值在组件的整个生命周期内保持不变。
  • 用法:
jsx 复制代码
import React, { useRef } from 'react';
function TextInputWithFocusButton() {
    const inputEl = useRef(null);
    const onButtonClick = () => { 
        // 使用原生 DOM API 聚焦文本输入 
        inputEl.current.focus();
    };
    return (<div><input ref={inputEl} type="text" />
        <button onClick={onButtonClick}>Focus the input</button>
    </div>);
}

useImperativeHandle - 暴露属性和方法

  • 自定义 ref,暴露给父组件。
  • 用法:
jsx 复制代码
import React, { useRef, forwardRef, useImperativeHandle } from 'react';
// forwardRef是 React 中的一个高级特性,它用于将父组件创建的ref对象转发给子组件。
const FancyInput = forwardRef((props, ref) => {
    
    const inputRef = useRef();
    // 接收 ref 关联父组件创建的 ref
    useImperativeHandle(ref, () => ({
        focus: () => { inputRef.current.focus(); }
    }));
    return <input ref={inputRef} />; 
});

function App() {
    const inputRef = useRef();
    return (<div>
        <FancyInput ref={inputRef} />
        <button onClick={() => inputRef.current.focus()}>Focus the input</button>
    </div>);
}   
  • 在这个例子中,子组件 FancyInput 通过 forwardRef 接收父组件-App创建的 ref 并将其绑定到input元素上。在 App 中,通过 useRef 创建inputRef,将其传递给 FancyInput ,然后在onClick方法中可以通过 inputRef.current.focus() 让输入框获取焦点。
  • 通常情况下,useImperativeHandleforwardRef一起使用,因为useImperativeHandle需要一个ref来进行操作,而这个ref通常是由forwardRef传递进来的。

useId

  • useId 是一个用于生成唯一 ID 的 Hook,通常用于无障碍特性(例如绑定 label 和 input)。
  • 用法:
jsx 复制代码
import React, { useId } from 'react';
function Form() {
    const id = useId();
    return (<div>
        <label htmlFor={id}>Name</label>
        <input id={id} type="text" />
    </div>);
}   

useLayoutEffect - 同步执行于 DOM 更新后

useLayoutEffect 类似于 useEffect,但在所有 DOM 变更之后同步调用。

  • 用法:
jsx 复制代码
import React, { useLayoutEffect, useRef } from 'react';
function LayoutEffectExample() {
    const ref = useRef(null); 
    useLayoutEffect(() => {
        console.log('useLayoutEffect');
        ref.current.style.backgroundColor = 'yellow';
    });
    return <div ref={ref}>Layout Effect</div>;
}   
  • 同步执行于 DOM 更新后:useLayoutEffect的执行时机是在 DOM 更新之后,浏览器进行绘制(paint)之前。这意味着它会在组件的 DOM 更新完成后立即执行 ,并且是同步阻塞的。如果useLayoutEffect内部的操作耗时较长,会阻塞浏览器的绘制,导致页面出现短暂的 "卡顿" 现象。
  • 示例场景 - 计算元素位置和尺寸:假设要根据一个元素的新位置来进行一些布局调整。如果在useLayoutEffect中获取元素的位置并进行布局计算,能够保证在用户看到更新后的元素位置之前完成这些计算,从而避免布局的闪烁。
jsx 复制代码
import React, { useState, useLayoutEffect } from 'react';
function LayoutComponent() {
  const [width, setWidth] = useState(100);
  useLayoutEffect(() => {
    const element = document.getElementById('myElement');
    if (element) {
      const newWidth = element.offsetWidth;
      console.log('元素宽度:', newWidth);
    }
  }, [width]);
  return (
    <div>
      <div id="myElement" style={{ width: `${width}px` }}>
        这是一个元素
      </div>
      <button onClick={() => setWidth(width + 10)}>增加宽度</button>
    </div>
  );
}
  • 在这个例子中,每次点击按钮增加元素宽度后,useLayoutEffect会在 DOM 更新后同步执行。它获取更新后的元素宽度并打印出来,这个操作是在浏览器绘制之前完成的。
复制代码
### useTransition - **非紧急更新的,返回 isPending、startTransitionisPen**
  • useTransition 是一个用于处理 UI 过渡的 Hook,允许你将某些更新标记为过渡,从而避免阻塞界面。
  • 用法:
jsx 复制代码
import React, { useState, useTransition } from 'react';
function App() {
    const [isPending, startTransition] = useTransition();
    const [count, setCount] = useState(0);
    const handleClick = () => {
        startTransition(() => {
            setCount(c => c + 1);
        });
    };
    return (<div><button onClick={handleClick}>Increment</button>
        {isPending ? 'Loading...' : <p>Count: {count}</p>}
    </div>);
}   
  • 非紧急更新的标记与调度useTransition用于标记更新为非紧急更新,它本身并不会直接执行代码,而是返回一个用于启动过渡更新的函数和一个表示过渡是否正在进行的布尔值。当使用useTransition返回的函数来触发更新时,React 会将这个更新视为低优先级任务。
  • 与正常更新的关系:在 React 的并发模式下,正常的高优先级更新(如用户输入等紧急更新)会优先处理。而useTransition标记的更新会在浏览器空闲时间或者在高优先级更新处理完成后才会执行。例如,在一个搜索组件中,用户输入搜索词是高优先级更新,而根据搜索词更新搜索结果列表可以标记为非紧急更新,通过useTransition来调度。
  • 示例场景 - 搜索框和搜索结果更新:
jsx 复制代码
import React, { useState, useTransition } from 'react';
function SearchComponent() {
  const [inputValue, setInputValue] = useState('');
  const [searchResults, setSearchResults] = useState([]);
  const [isPending, startTransition] = useTransition();
  const handleInputChange = (e) => {
    setInputValue(e.target.value);
    startTransition(() => {
      // 模拟获取搜索结果
      const results = getSearchResults(e.target.value);
      setSearchResults(results);
    });
  };
  return (
    <div>
      <input type="text" value={inputValue} onChange={handleInputChange} />
      {isPending && <div>正在更新搜索结果...</div>}
      <ul>
        {searchResults.map((result, index) => (
          <li key={index}>{result}</li>
        ))}
      </ul>
    </div>
  );
}
  • 在这个例子中,用户输入搜索词时,输入值的更新是高优先级的。而更新搜索结果的操作被startTransition标记为非紧急更新。isPending可以用于显示一个加载指示器,当标记的更新正在进行时,会显示这个加载指示器,并且更新会在合适的时候完成,不会阻塞用户输入。
复制代码
### useDeferredValue - 延迟更新 用于一个值
  • useDeferredValue 是一个用于延迟更新的 Hook,它会返回一个新的值,这个值会在某个时间点更新到最新的值,用于优化性能。
  • 用法:
jsx 复制代码
import React, { useState, useDeferredValue } from 'react';
function App() {
    const [text, setText] = useState('');
    const deferredText = useDeferredValue(text);
    const handleChange = (e) => {
        setText(e.target.value);
    };
    return (<div><input type="text" value={text} onChange={handleChange} />
        <p>{deferredText}</p>
    </div>);
}   
  • 延迟更新的触发:useDeferredValue用于创建一个延迟更新的值。它接受一个值作为参数,并返回一个新的延迟值。当原始值发生变化时,React 会先处理其他高优先级的更新,然后在适当的时候更新这个延迟值。
  • useTransition的相似性和区别:和useTransition类似,useDeferredValue也是用于处理非紧急更新。不同的是,useDeferredValue是直接应用于一个值,自动延迟其更新。而useTransition需要手动标记更新的过程为非紧急更新。
  • 示例场景 - 输入框和延迟显示的内容更新:
jsx 复制代码
import React, { useState, useDeferredValue } from 'react';
function DeferredComponent() {
  const [inputValue, setInputValue] = useState('');
  const deferredValue = useDeferredValue(inputValue);
  const handleInputChange = (e) => {
    setInputValue(e.target.value);
  };
  return (
    <div>
      <input type="text" value={inputValue} onChange={handleInputChange} />
      <div>延迟显示的值: {deferredValue}</div>
    </div>
  );
}
  • 在这个例子中,当用户在输入框中输入时,inputValue会立即更新,但是deferredValue会延迟更新。这意味着如果有其他高优先级的更新(比如与输入框本身相关的更新,如输入框的样式调整等),会先处理这些更新,然后再更新deferredValue,从而避免对用户输入等紧急操作的干扰。
复制代码
### useSyncExternalStore - 外部存储函数,当前数据源快照,SSR数据源快照
jsx 复制代码
import React, { useSyncExternalStore } from 'react';
function useWindowWidth() {
    return useSyncExternalStore(
        (cb) => {
            window.addEventListener('resize', cb);
            return () => window.removeEventListener('resize', cb);
        },
        () => window.innerWidth,
        () => window.innerWidth
    );
}
function WindowWidth() {
    const width = useWindowWidth();
    return <div>Window width: {width}</div>;
}   
  1. 基本概念与用途

    1. 定义useSyncExternalStore是React提供的一个Hook,用于订阅外部数据源,并在数据源发生变化时强制组件重新渲染。它主要用于在React组件和外部状态管理系统之间建立连接,确保组件能够获取最新的外部状态。

    2. 目的 :在实际开发中,经常会遇到需要使用外部状态(如Redux存储、浏览器本地存储等)的情况。useSyncExternalStore使得React组件能够高效、安全地与这些外部存储进行交互,并且能够根据存储内容的变化及时更新组件的显示内容。

  2. 参数与工作原理

    1. 语法结构const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)

    2. subscribe 函数 :这是一个用于订阅外部数据源变化的函数。当外部数据源发生变化时,这个函数会被调用,通知React组件需要重新渲染。例如,在订阅Redux存储时,subscribe函数可能会调用store.subscribe来注册一个监听器。这个函数接收一个callback参数,当外部数据源变化时,应该调用这个callback来触发React组件的重新渲染。

    3. getSnapshot 函数 :用于获取外部数据源的当前"快照"(snapshot)。这个快照是一个数据表示,用于让React组件知道外部数据源的当前状态。例如,在使用Redux存储时,getSnapshot函数可能会返回store.getState(),以获取Redux存储的当前状态。

    4. getServerSnapshot 函数(可选) :用于在服务器端渲染(SSR)时获取初始快照。如果应用支持SSR,这个函数可以提供服务器端的初始状态快照,确保在客户端和服务器端渲染的一致性。

  3. 使用场景与示例

    1. 与Redux集成(简化示例)

      • 假设已经有一个Redux存储(store),想要在React组件中获取和响应Redux状态的变化。
      jsx 复制代码
      import React from 'react';
      import { useSyncExternalStore } from 'react';
      function ReduxConnectedComponent() {
        const state = useSyncExternalStore(
          store => store.subscribe(callback => setMyComponentNeedsUpdate(callback)),
          store => store.getState()
        );
        // 根据state进行组件渲染
        return <div>{state.someData}</div>;
      }
      • 在这个例子中,subscribe函数通过store.subscribe注册了一个监听器,当Redux存储发生变化时,会调用setMyComponentNeedsUpdate(这个函数可以是自定义的,用于触发组件重新渲染)。getSnapshot函数通过store.getState()获取Redux存储的当前状态,这样组件就可以根据Redux状态进行渲染,并且在状态变化时自动更新。
    2. 与浏览器本地存储集成

      • 当需要在React组件中读取和响应本地存储(localStorage)的变化时,useSyncExternalStore也很有用。
      jsx 复制代码
      import React from 'react';
      import { useSyncExternalStore } from 'react';
      function LocalStorageComponent() {
        const getLocalStorageSnapshot = () => JSON.parse(localStorage.getItem('myData'));
        const subscribeToLocalStorage = (callback) => {
          window.addEventListener('storage', callback);
          return () => window.removeEventListener('storage', callback);
        };
        const localStorageData = useSyncExternalStore(
          subscribeToLocalStorage,
          getLocalStorageSnapshot
        );
        return <div>{localStorageData.someProperty}</div>;
      }
      • 在这里,getLocalStorageSnapshot函数用于获取本地存储中数据的当前快照。subscribeToLocalStorage函数用于订阅本地存储的变化,它通过window.addEventListener('storage', callback)来监听storage事件,当本地存储变化时,会触发callback,通知组件重新渲染。当组件卸载时,返回的函数用于移除事件监听器,避免内存泄漏。
    3. 在浏览器环境下监听storage事件

  4. 优势与注意事项

    1. 优势

      • 性能优化useSyncExternalStore通过只在外部数据源真正发生变化时重新渲染组件,避免了不必要的渲染。这有助于提高应用的性能,特别是在处理大型数据集或者频繁变化的外部数据源时。
      • 数据一致性:确保了React组件与外部数据源之间的数据一致性。无论外部数据源是如何更新的,组件都能够获取最新的状态并正确地更新显示内容。
    2. 注意事项

      • 订阅和取消订阅的平衡 :在subscribe函数中,要注意正确地处理订阅和取消订阅的操作。如果没有正确地取消订阅(例如,在组件卸载时没有移除事件监听器),可能会导致内存泄漏或者不必要的更新。
      • 外部数据源的兼容性 :要确保getSnapshot函数返回的数据格式与组件的渲染逻辑相匹配。不同的外部数据源可能有不同的数据结构和更新方式,需要仔细考虑如何获取和处理快照数据。同时,对于不支持的外部数据源类型,可能需要进行适当的包装或者转换才能使用useSyncExternalStore
相关推荐
慧一居士29 分钟前
flex 布局完整功能介绍和示例演示
前端
DoraBigHead31 分钟前
小哆啦解题记——两数失踪事件
前端·算法·面试
一斤代码6 小时前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子6 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
光影少年6 小时前
从前端转go开发的学习路线
前端·学习·golang
中微子7 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina7 小时前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
前端_学习之路8 小时前
React--Fiber 架构
前端·react.js·架构
coderlin_8 小时前
BI布局拖拽 (1) 深入react-gird-layout源码
android·javascript·react.js
甜瓜看代码8 小时前
1.
react.js·node.js·angular.js