React Hooks

什么是Reack Hooks

React Hooks 是 React 16.8 版本新增的特性,它允许在函数组件中使用 state 及其他 React 特性,不再必须转换成 class 组件。

React Hooks 的主要功能

1. useState: 在函数组件中声明状态变量

js 复制代码
import React, { useState } from 'react';

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

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

2. useEffect: 在函数组件各个生命周期执行副作用操作。

  • 基本语法
js 复制代码
useEffect(callback, dependencies);
// callback: 在组件渲染后执行的副作用操作的函数
// dependencies(可选): 这是一个数组,它包含影响副作用操作执行的依赖项。当依赖项发生变化时,副作用操作会被重新执行。
javascript 复制代码
import React, { useState, useEffect } from 'react';

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

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me  
      </button>
    </div>
  );
}

useEffect结合了 class 组件中生命周期函数(componentDidMountcomponentDidUpdatecomponentWillUnmount)的功能

常见的 useEffect 使用方式:

  • 执行只运行一次的 effect(componentDidMount 等价)
scss 复制代码
useEffect(() => {
  // 数据获取操作
  fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => {
      // 更新组件状态
      setData(data);
    });
}, []); // 空数组表示副作用操作只在组件挂载时执行
  • 根据依赖条件执行 effect(componentDidUpdate 等价)
ini 复制代码
useEffect(() => {
  // 这个副作用操作依赖于count的值
  document.title = `Count: ${count}`;
}, [count]); // 依赖项列表中的任何值发生变化,副作用操作将被重新执行。
  • 清除 effect(componentWillUnmount 等价)
scss 复制代码
useEffect(() => { 
  const timer = setInterval(() => { 
    // 定时操作 
  }, 1000); 
  return () => { 
    // 清理操作,在组件卸载时执行 
    clearInterval(timer); 
  }; 
}, []);
  • 多个 useEffect
js 复制代码
// 在一个组件中使用多个 `useEffect`,每个 `useEffect` 可以负责不同的副作用操作和依赖项
useEffect(() => {
  // 副作用操作 A
}, [dependencyA]);

useEffect(() => {
  // 副作用操作 B
}, [dependencyB]);

3. useContext:用于在函数式组件中访问上下文(context)

  • 基本语法
js 复制代码
const value = useContext(MyContext);
// MyContext: 是一个 React 上下文对象,它通常是通过 `React.createContext` 创建的。
  • 使用方式

例如A、B、C三个组件,逐层嵌套A>B>C

js 复制代码
// MyContext.js
import { createContext } from 'react';

// 使用createContext建立一个context,并导出
const MyContext = createContext();
export default MyContext;
js 复制代码
//组件A 
import React from "react"; 
import B from "./B";
import MyContext from './MyContext';

function App() {
  const value = 'This is the context value'; 
  return (
    // 传递数据组件里使用Provide包裹着子组件,并且在用value属性来传递数据
    <MyContext.Provider value={value}> 
      {/* 这里可以包含你的组件树 */} 
      <B />
    </MyContext.Provider> 
  );
}
export default App;
js 复制代码
//组件B
import C from "./C";

function B() {
  return (
    <>
      <C />
    </>
  );
}

export default B;
js 复制代码
//组件C
import React from "react";
import MyContext from './MyContext';

function C() {
  return (
    // 接受数据的组件导入定义的context使用Consumer来接收,可接收到的是一个函数。
    <MyContext.Consumer>
      {(value) => <span>{value}</span>}
    </MyContext.Consumer>
  );
}

export default C;

但是,当有多个context,在C中使用将会变得很不友好

js 复制代码
// myContext2.js
import { createContext } from 'react';

// 使用createContext建立一个context,并导出
const MyContext2 = createContext();
export default MyContext2;
js 复制代码
//组件A
import React from "react";
import B from "./B";
import MyContext from './MyContext';
import MyContext2 from './MyContext2';

function App() {
  return (
    const value = 'This is the context value'; 
    const value2 = 'This is the context2 value'; 
    
    <MyContext.Provider value={value}>
      <MyContext2.Provider value={value2}>
        <B />
      </MyContext2.Provider>
    </MyContext.Provider>
  );
}

export default App;
js 复制代码
//组件C
import React from "react";
import MyContext from './MyContext';
import MyContext2 from './MyContext2';

function C() {
  return (
    <MyContext.Consumer>
      {(value) => (
        <>
          <span>{value}</span>
          <MyContext2.Consumer>
            {(value2) => <span>{value2}</span>}
          </MyContext2.Consumer>
        </>
      )}
    </MyContext.Consumer>
  );
}

export default C;

此时可以通过useContext来重新获取A组件传递的值

js 复制代码
import React from "react";
import MyContext from './MyContext';
import MyContext2 from './MyContext2';

function C() {
  const value = useContext(MyContext);
  const value2 = useContext(MyContext2);
  
  return (
    <>
      <span>{value}</span>
      <span>{value2}</span>
    </>
  );
}

export default C;

4. useReducer:通过 reducer 来管理组件局部状态

  • 基本用法
js 复制代码
const [state, dispatch] = useReducer(reducer, initialState);
// reducer函数: `(state, action) => newState` 的纯函数,用于根据 old state 和 action 返回一个新的 state。
// initialState: 初始状态的值
  • 使用方式
js 复制代码
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:
      return state;
  }
}

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

  const increment = () => {
    dispatch({ type: 'INCREMENT' });
  };

  const decrement = () => {
    dispatch({ type: 'DECREMENT' });
  };

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}
  • 使用useReducer代替复杂的state,如果组件层级比较深,需要子组件触发state,可以同时使用useContext传递dispatch

    以下是一个简化的购物车示例:

    创建一个购物车上下文 CartContext,包含了 cartReducer 和一些自定义 Hook,如 CartProvideruseCartCartProvideruseReducer 的结果放入上下文,useCart 允许组件访问购物车状态。

js 复制代码
// CartContext.js
import React, { createContext, useReducer, useContext } from 'react';

const CartContext = createContext();

function cartReducer(state, action) {
  switch (action.type) {
    case 'ADD_TO_CART':
      return { items: [...state.items, action.payload] };
    case 'REMOVE_FROM_CART':
      return { items: state.items.filter(item => item.id !== action.payload) };
    default:
      return state;
  }
}

function CartProvider({ children }) {
  const [cart, dispatch] = useReducer(cartReducer, { items: [] });

  return (
    <CartContext.Provider value={{ cart, dispatch }}>
      {children}
    </CartContext.Provider>
  );
}

function useCart() {
  const context = useContext(CartContext);
  if (context === undefined) {
    throw new Error('useCart must be used within a CartProvider');
  }
  return context;
}

export { CartProvider, useCart };

然后,可以在应用中的各个组件中使用 useCart Hook 来访问和修改购物车状态,而不需要将购物车状态一层层地传递。

Product 组件中,通过使用 useCart Hook,它可以访问购物车上下文中的 dispatch 函数,以便将商品添加到购物车中。

js 复制代码
// Product.js
import React from 'react';
import { useCart } from './CartContext';

function Product({ product }) {
  const { dispatch } = useCart();

  const addToCart = () => {
    dispatch({ type: 'ADD_TO_CART', payload: product });
  };

  return (
    <div>
      <h2>{product.name}</h2>
      <p>Price: {product.price}</p>
      <button onClick={addToCart}>Add to Cart</button>
    </div>
  );
}

Cart 组件中,同样使用 useCart Hook 来访问购物车状态和 dispatch 函数,以展示购物车内容并允许从购物车中移除商品。

js 复制代码
// Cart.js
import React from 'react';
import { useCart } from './CartContext';

function Cart() {
  const { cart, dispatch } = useCart();

  const removeFromCart = (productId) => {
    dispatch({ type: 'REMOVE_FROM_CART', payload: productId });
  };

  return (
    <div>
      <h2>Shopping Cart</h2>
      <ul>
        {cart.items.map((item) => (
          <li key={item.id}>
            {item.name} - ${item.price}
            <button onClick={() => removeFromCart(item.id)}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

5. useCallback: 用于优化性能,特别是在处理回调函数时,以避免不必要的重新渲染

  • 基本语法
js 复制代码
const memoizedCallback = useCallback(callback, dependencies);
// callback: 是一个函数,需要被缓存的函数。
// dependencies: 回调函数所依赖的值数组,如果数组值发生变化,则生成新的函数
  • 使用方式

假设你有一个父组件,它传递一个回调函数给子组件。如果不使用 useCallback,每次父组件重新渲染时,回调函数都会重新创建,导致子组件不必要地重新渲染。

js 复制代码
import React, { useState } from 'react';

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

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

function ChildComponent({ onClick }) {
  return <button onClick={onClick}>Increment</button>;
}

handleClick 每次父组件重新渲染时都会重新创建,导致子组件重新渲染。为了避免这种情况,你可以使用 useCallback

js 复制代码
import React, { useState, useCallback } from 'react';

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

  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

function ChildComponent({ onClick }) {
  return <button onClick={onClick}>Increment</button>;
}

6. useRef: 获取对 DOM 元素的引用

  • 基本语法
js 复制代码
const myRef = useRef(initialValue);
// initialValue 是可选的,它可以设置初始值。通常情况下,initialValue 在创建 Ref 时设置为 null。
  • 使用方式

    访问 DOM 元素:

js 复制代码
import React, { useRef, useEffect } from 'react';

function MyComponent() {
  const myInputRef = useRef(null);

  useEffect(() => {
    // 通过 myRef.current 访问该元素
    myInputRef.current.focus();
  }, []);

  return <input ref={myInputRef} />;
}

存储可变数据: 使用 useRef 存储可变数据,不会引发组件的重新渲染

js 复制代码
import React, { useRef } from 'react';

function Timer() {
  // count 是一个 useRef 对象,它在多次渲染之间保持不变,并用于计算计时器的值。
  const count = useRef(0);

  const startTimer = () => {
    setInterval(() => {
      count.current += 1;
      console.log(`Timer count: ${count.current}`);
    }, 1000);
  };

  return (
    <div>
      <button onClick={startTimer}>Start Timer</button>
    </div>
  );
}

访问子组件或函数组件的内部状态: 通过将 useRef 传递给子组件,你可以在父组件中访问子组件的状态或操作子组件的方法。

js 复制代码
import React, { useRef } from 'react';

function ParentComponent() {
  const childRef = useRef(null);

  const handleChildClick = () => {
    childRef.current.doSomething();
  };

  return (
    <div>
      <ChildComponent ref={childRef} />
      <button onClick={handleChildClick}>Call Child's Method</button>
    </div>
  );
}

function ChildComponent() {
  const doSomething = () => {
    // 在子组件中执行某些操作
  };

  return <div>Child Component</div>;
}

使用React Hooks需要遵循的规则

  1. 只在函数最外层调用Hooks,不要在循环、条件判断或者子函数中调用。这是为了确保Hooks在每次渲染中都按照同样的顺序被调用,这样React才能正确地保存Hooks的状态。 错误示例:
js 复制代码
// 在条件判断中调用Hook:
function Counter() {
 const [count, setCount] = useState(0);

  if (count > 5) {
    const [highCount, setHighCount] = useState(0); // 错误!在条件判断中调用
  }

  // ...
}
  1. 仅在React函数组件或自定义Hooks中调用其他的Hooks。不要在普通的JavaScript函数中调用

自定义Hooks

创建自定义的 Hook,应该以 "use" 前缀命名

自定义 Hook 处理主题切换逻辑

js 复制代码
// useTheme.js
import { useState } from 'react';

function useTheme() {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };

  return { theme, toggleTheme };
}

export default useTheme;

在组件中使用自定义 Hook :

js 复制代码
// ThemeSwitcher.js
import React from 'react';
import useTheme from './useTheme';

function ThemeSwitcher() {
  const { theme, toggleTheme } = useTheme();

  return (
    <div>
      <p>Current Theme: {theme}</p>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
}

export default ThemeSwitcher;

可以在多个组件中共享同一个自定义 Hook,从而避免重复编写相同的逻辑

js 复制代码
// AnotherComponent.js
import React from 'react';
import useTheme from './useTheme';

function AnotherComponent() {
  const { theme } = useTheme();

  return <p>Theme in AnotherComponent: {theme}</p>;
}
相关推荐
一个处女座的程序猿O(∩_∩)O10 分钟前
Vue 项目打包部署总结
前端·javascript·vue.js
Kika写代码1 小时前
【微信小程序】4|搜索框-历史搜索 | 我的咖啡店-综合实训
前端·微信小程序·小程序·notepad++
egekm_sefg2 小时前
一个基于Rust适用于 Web、桌面、移动设备等的全栈应用程序框架
开发语言·前端·rust
冴羽2 小时前
Solid.js 最新官方文档翻译(13)—— Context(上下文)
前端·javascript·react.js
ObjectX前端实验室3 小时前
交互式md文档渲染实现
前端·github·markdown
励志成为大佬的小杨4 小时前
c语言中的枚举类型
java·c语言·前端
前端熊猫4 小时前
Element Plus 日期时间选择器大于当天时间置灰
前端·javascript·vue.js
傻小胖4 小时前
React 组件通信完整指南 以及 自定义事件发布订阅系统
前端·javascript·react.js
JaxNext4 小时前
开发 AI 应用的无敌配方,半小时手搓学英语利器
前端·javascript·aigc
万亿少女的梦1684 小时前
高校网络安全存在的问题与对策研究
java·开发语言·前端·网络·数据库·python