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>;
}
相关推荐
喵叔哟12 分钟前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特1 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解1 小时前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
Mr.咕咕1 小时前
Django 搭建数据管理web——商品管理
前端·python·django