React Hooks 详细使用介绍

useState 状态管理

useState 是 React 中的一个基础 Hook,允许你在不使用 class 组件的情况下管理组件状态。

参数

初始值

你可以直接传递状态的初始值给 useState

const [name, setName] = useState('John');

使用函数设置初始值

当初始化状态代价较大时,你可以传递一个函数:

const [state, setState] = useState(() => {
  const initialState = calculateInitialState(); // 一些复杂的操作
  return initialState;
});

返回值

useState 返回一个数组,其中包括当前状态值和一个更新状态的函数。

示例

基础计数器示例

import React, { useState } from 'react';

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

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

使用对象作为状态

function UserProfile() {
  const [profile, setProfile] = useState({ name: 'John', age: 30 });

  const updateName = (name) => {
    setProfile((prevProfile) => ({ ...prevProfile, name }));
  };

  return (
    <div>
      <p>Name: {profile.name}</p>
      <p>Age: {profile.age}</p>
      <button onClick={() => updateName('Doe')}>Update Name</button>
    </div>
  );
}

使用多个 useState

你可以在一个组件中使用多个 useState 来管理不同的状态片段:

function MultiCounter() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  return (
    <div>
      <p>Counter 1: {count1}</p>
      <button onClick={() => setCount1(count1 + 1)}>Increment Counter 1</button>

      <p>Counter 2: {count2}</p>
      <button onClick={() => setCount2(count2 + 1)}>Increment Counter 2</button>
    </div>
  );
}

注意事项

  • 调用位置:在 React 函数的最顶层调用 useState

  • 依赖之前的状态更新:使用函数式更新,如:setCount(prevCount => prevCount + 1)

  • 不会自动合并对象:手动合并对象,如:setState(prevState => ({ ...prevState, key: 'value' }))

useEffect 副作用操作

React 函数组件的副作用利器

什么是副作用?

在编程中,副作用是指代码对外部世界产生的影响。比如说,你想在用户点击按钮后改变网页的标题。这个改变就是一个副作用。

为什么需要 useEffect

React 组件通常用于渲染 UI,但有时你还需要执行一些额外的操作,如网络请求、操作 DOM 或订阅事件等。这些操作就是所谓的副作用,而 useEffect 就是用来处理这些副作用的工具。

如何使用 useEffect

useEffect 的使用很简单。它接受两个参数:一个副作用函数和一个依赖数组。

  • 副作用函数:放置你想执行的副作用代码。
  • 依赖数组:决定副作用何时执行。数组中的值发生变化时,副作用会重新执行。

例子

改变网页标题

假设你想在组件挂载后改变网页标题。你可以这样做:

useEffect(() => {
  document.title = '新的标题';
}, []); // 空数组表示只在组件挂载后执行一次
获取用户数据

如果你想根据用户 ID 获取用户数据,你可以这样做:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/user/${userId}`).then(response => response.json()).then(setUser);
  }, [userId]); // 当 userId 改变时,重新获取数据
}

注意事项

  • 清理副作用:如果你的副作用涉及需要清理的操作(例如取消订阅事件),你可以在副作用函数中返回一个清理函数。

    useEffect(() => {
      const timerId = setInterval(() => {
        console.log('Tick');
      }, 1000);
    
      return () => {
        clearInterval(timerId); // 清理定时器
      };
    }, []);
    
  • 避免无限循环:不小心使用 useEffect 可能导致无限循环。务必注意你的依赖数组,确保副作用按预期执行。

useMemo 记忆计算值

useMemo 钩子用于在组件渲染期间记住复杂计算的结果。这可以提高性能,尤其是当有大量计算和重新计算时。

为什么使用 useMemo

当组件重新渲染时,可能会涉及到一些复杂的计算。如果这些计算的依赖项没有改变,那么重新进行这些计算就是浪费。useMemo 允许你记住这些计算的结果,只有当依赖项改变时才重新计算。

如何使用 useMemo

useMemo 接受两个参数:一个函数和一个依赖项数组。它会返回该函数的返回值。

  • 函数:包含要记住的计算。
  • 依赖项数组:当数组中的任何值发生变化时,函数将被重新执行。

TypeScript 示例代码

下面的示例展示了如何使用 useMemo 来过滤大于10的数字。只有当数字数组改变时,才会重新计算过滤的结果。

import React, { useMemo, useState } from 'react';

interface Props {
  items: number[];
}

const ExpensiveComponent: React.FC<Props> = ({ items }) => {
  const filteredItems = useMemo(() => items.filter(item => item > 10), [items]);

  return (
    <div>
      <h3>Filtered Items (Greater than 10):</h3>
      {filteredItems.map(item => (
        <div key={item}>{item}</div>
      ))}
    </div>
  );
};

const App: React.FC = () => {
  const [items, setItems] = useState<number[]>([5, 12, 8, 20, 33]);
  const addItem = () => setItems([...items, Math.floor(Math.random() * 40)]);

  return (
    <div>
      <button onClick={addItem}>Add Item</button>
      <h3>All Items:</h3>
      {items.map(item => (
        <div key={item}>{item}</div>
      ))}
      <ExpensiveComponent items={items} />
    </div>
  );
};

export default App;

注意事项

  • 不要将 useMemo 作为性能优化的首要工具。除非你确实遇到性能问题,并且确定了 useMemo 可以解决问题,否则不要过早优化。
  • 不应该在 useMemo 的函数内部执行有副作用的操作。使用 useEffect 处理副作用。

总结

useMemo 是一个非常强大的工具,可以提高组件的性能,尤其是在复杂的计算和频繁的重新渲染中。但是,也要谨慎使用它,确保只在确实需要时使用它,并始终确保你的代码的正确性和可维护性。

useCallback :记忆化函数

什么是 useCallback

useCallback 用于在组件的连续渲染之间记忆化(或缓存)函数。它帮助避免因父组件重新渲染而导致的不必要的子组件渲染。

如何使用?

useCallback 接受两个参数:一个函数和一个依赖数组。只有当依赖数组中的值发生变化时,函数才会被重新创建。

示例

让我们看一个完整的示例,展示如何使用 useCallback 来优化组件的渲染。

import React, { useState, useCallback } from 'react';

const ChildComponent = React.memo(({ onClick }) => {
  console.log('ChildComponent re-rendered!');
  return <button onClick={onClick}>Click Me!</button>;
});

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

  // 使用 useCallback 记忆化 handleClick 函数
  const handleClick = useCallback(() => {
    console.log('Button was clicked');
  }, []); // 空依赖数组,表示该函数不会重新创建

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment Counter</button>
      {/* 传递记忆化的 handleClick 到 ChildComponent */}
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

export default ParentComponent;

在上面的示例中,ChildComponent 接受一个 onClick 属性,并且使用 React.memo 进行了优化,所以只有当 onClick 属性发生变化时才会重新渲染。由于 handleClick 使用 useCallback 被记忆化了,因此即使父组件重新渲染,handleClick 函数也不会更改,从而避免了子组件的不必要渲染。

注意事项

  1. 过度优化的风险:不是所有的函数都需要使用 useCallback。如果一个函数没有传递给子组件或其他记忆化操作,使用 useCallback 可能会带来更多的复杂性和性能开销。
  2. 依赖项的准确性:确保依赖数组中的所有值都是必需的,并且当它们发生变化时,你确实希望函数被重新创建。

总结

useCallback 是一个强大的优化工具,可以在适当的情况下提高 React 组件的性能。通过使用此 Hook,你可以控制函数的重新创建,从而避免因父组件重新渲染而导致的不必要的子组件渲染。

该示例展示了如何在实际组件中使用 useCallback 进行优化,希望这有助于你更深入地理解这个 Hook 的用途和工作方式。如果你有任何问题或需要进一步的解释,请随时提问!

useCallback 和 useMemo 区别

  • 用途useCallback 用于记忆化函数,而 useMemo 用于记忆化计算值。
  • 返回值useCallback 返回一个记忆化的函数;useMemo 返回一个记忆化的计算结果。
  • 最佳实践
    • 当你需要传递给子组件的函数并希望避免不必要的子组件渲染时,使用 useCallback
    • 当你有昂贵的计算,并希望在依赖项未更改时避免重新计算时,使用 useMemo

useRef 访问 Ref

用于访问和操作 DOM 元素及保持可变的引用值

什么是 useRef

useRef Hook 用于访问和操作 DOM 元素,并在组件的整个生命周期中保持不变的引用。除了用于直接操作 DOM,useRef 还可用于在组件的不同渲染之间保留可变的引用值,而不触发重新渲染。

如何使用 useRef

要使用 useRef,你首先需要调用它,并将初始值作为参数传递(如果有)。这将返回一个 ref 对象,该对象具有一个名为 current 的属性,该属性将引用传递给 useRef 的初始值或 DOM 元素。

使用 useRef 访问 DOM 元素

你可以使用 useRef 直接访问和操作 DOM 元素。下面是一个示例,显示了如何使用 useRef 控制输入元素的焦点。

import React, { useRef } from 'react';

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

  const handleFocus = () => {
    inputRef.current.focus(); // 使用 ref 对象的 current 属性访问输入元素
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleFocus}>Focus the input</button>
    </div>
  );
}

在上述示例中,我们创建了一个 ref 对象并将其分配给输入元素。然后,我们可以使用该引用在按钮点击时将焦点设置到输入元素上。

使用 useRef 保留可变的引用值

除了用于访问 DOM 元素外,useRef 还可以用于在组件的连续渲染之间保留可变的引用值,而不触发重新渲染。

import React, { useRef, useEffect } from 'react';

function Timer() {
  const countRef = useRef(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      countRef.current += 1; // 更新 ref 的 current 值
      console.log('Timer:', countRef.current);
    }, 1000);

    return () => clearInterval(intervalId); // 清除计时器
  }, []); // 空依赖数组表示该效果只在挂载和卸载时运行

  return <div>Check the console to see the timer count!</div>;
}

在这个示例中,我们使用 useRef 来保持计时器的计数值。因为 ref 对象的更改不会触发组件的重新渲染,所以它是一个非常有用的工具,用于在重新渲染之间保留值。

总结

useRef 是一个多功能的 React Hook,可以用于多种用途:

  • 访问和操作 DOM 元素:使用 ref 对象的 current 属性直接访问和操作 DOM 元素。
  • 保留可变引用值:在组件的不同渲染之间保留值,而不触发重新渲染。

自定义组件使用Ref

在React中,你可以使用引用(ref)与自定义组件进行交互,访问组件的实例。这是一个非常有用的特性,可以用于获取组件内部的信息,调用组件内部的方法,或者与组件进行更复杂的交互。

使用 forwardRef 与自定义组件

对于自定义组件,你通常需要使用 forwardRef 来转发ref。下面是一个简单的示例,显示如何使用 forwardRef 创建自定义组件,并从父组件中访问该组件的DOM元素:

import React, { useRef, forwardRef } from 'react';

const CustomComponent = forwardRef((props, ref) => {
  return <div ref={ref}>Custom Component</div>;
});

function App() {
  const customComponentRef = useRef(null);

  const handleClick = () => {
    customComponentRef.current.style.backgroundColor = 'lightblue';
  };

  return (
    <div>
      <CustomComponent ref={customComponentRef} />
      <button onClick={handleClick}>Change Background Color</button>
    </div>
  );
}

export default App;

使用 useImperativeHandle 暴露自定义实例属性

有时,你可能希望自定义组件能够暴露特定的实例方法或属性给父组件。在这种情况下,你可以使用 useImperativeHandle 进行更精细的控制。下面的示例显示了如何使用 useImperativeHandle 暴露组件的特定方法:

import React, { useRef, forwardRef, useImperativeHandle } from 'react';

const CustomComponent = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    sayHello: () => {
      alert('Hello from CustomComponent!');
    }
  }));

  return <div>Custom Component</div>;
});

function App() {
  const customComponentRef = useRef(null);

  const handleClick = () => {
    customComponentRef.current.sayHello(); // 调用自定义组件的方法
  };

  return (
    <div>
      <CustomComponent ref={customComponentRef} />
      <button onClick={handleClick}>Say Hello</button>
    </div>
  );
}

export default App;

总结

与自定义组件一起使用ref可以实现许多强大的功能,从访问组件内部的DOM元素,到调用组件的特定实例方法。通过使用 forwardRefuseImperativeHandle,你可以更灵活地控制自定义组件的行为,并与之进行更复杂的交互。

请注意,直接操作DOM和组件实例通常应该作为最后的手段,因为它可能会导致代码难以理解和维护。在可能的情况下,尽量使用正常的React数据流来管理组件的状态和行为。

useContext 访问上下文

useContext 钩子是React中一个非常强大的工具,用于在组件树中跨层级共享状态。它消除了通过多层组件手动传递props的需要,使得状态管理变得更加清晰和高效。

为什么使用 useContext

在复杂的React应用程序中,状态需要在多个组件之间共享。传统方法是将状态作为props从顶层组件一层一层传递下去,但这会导致代码混乱且难以维护。useContext 允许我们跨越组件层级直接共享状态,无需手动传递。

如何使用 useContext

  1. 创建上下文: 使用 React 的 createContext 方法创建一个上下文。

  2. 提供上下文: 使用 Context.Provider 组件在组件树中提供上下文的值。

  3. 消费上下文: 在任何子组件中使用 useContext 钩子访问上下文的值。

TypeScript 示例代码

下面的示例展示了如何使用 useContext 来共享主题设置。

import React, { useContext, useState } from 'react';

// 创建上下文
const ThemeContext = React.createContext<{ theme: string, toggleTheme: () => void } | undefined>(undefined);

const App: React.FC = () => {
  const [theme, setTheme] = useState('light');

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

  // 提供上下文
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      <Header />
      <MainContent />
    </ThemeContext.Provider>
  );
};

const Header: React.FC = () => {
  // 消费上下文
  const themeContext = useContext(ThemeContext);

  if (!themeContext) {
    return <h1>Error: Theme not found</h1>;
  }

  return (
    <div>
      <h1>{themeContext.theme} theme is active</h1>
      <button onClick={themeContext.toggleTheme}>Toggle Theme</button>
    </div>
  );
};

const MainContent: React.FC = () => {
  // ...
  return <div>Main Content</div>;
};

export default App;

注意事项

  • 确保在使用 useContext 之前已经在组件树中提供了上下文。
  • 不要在渲染过程中更改上下文的值,这可能会导致组件的不必要重新渲染。

总结

useContext 钩子是一个强大的工具,用于组件之间的跨层级状态共享。通过消除手动传递props的需要,它可以使你的代码更加清晰、简洁,同时也提高了代码的可维护性。

useReducer 复杂状态逻辑

useReducer 是React中用于处理组件状态逻辑的钩子,尤其适用于更复杂或包括多个子值的状态。它的工作原理类似于Redux,但是更加精简,不需要引入额外的库。

为什么使用 useReducer

当状态逻辑复杂或者下一状态依赖于之前的状态时,useReducer 非常有用。相较于 useState,它提供了更可预测的状态更新方式,并且更容易测试和维护。

如何使用 useReducer

useReducer 接收两个参数:一个reducer函数和初始状态,返回当前状态和一个dispatch函数。

  1. Reducer 函数: 这个函数接收两个参数:当前的状态和一个动作。根据动作类型,它返回一个新的状态。

  2. 初始状态: 初始状态是reducer的第一个参数的初始值。

  3. Dispatch 函数: 这个函数用于分派动作,触发reducer函数,并更新状态。

TypeScript 示例代码

下面的示例演示了如何使用 useReducer 来管理一个计数器的状态。

import React, { useReducer } from 'react';

// 定义动作类型
type Action = { type: 'increment' } | { type: 'decrement' };

// 定义reducer函数
const counterReducer = (state: number, action: Action) => {
  switch (action.type) {
    case 'increment':
      return state + 1;
    case 'decrement':
      return state - 1;
    default:
      return state;
  }
};

const Counter: React.FC = () => {
  // 使用useReducer
  const [state, dispatch] = useReducer(counterReducer, 0);

  return (
    <div>
      <h1>Count: {state}</h1>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    </div>
  );
};

export default Counter;

注意事项

  • Reducer 必须是一个纯函数,这意味着同样的输入必须产生相同的输出,不应有副作用。
  • 尽量使reducer函数保持简洁明了,可以通过拆分成多个子reducer来处理更复杂的状态逻辑。

总结

useReducer 钩子提供了一种更灵活、可维护的方式来处理复杂的状态逻辑。通过结合纯净的reducer函数和动作分派,它为开发人员提供了对组件内部状态更细粒度的控制。对于需要管理复杂状态或者希望与Redux保持一致的项目,useReducer 是一个极好的选择。

useImperativeHandle 自定义 ref 暴露

useImperativeHandle 是一种特殊的钩子,允许你在父组件中直接操作子组件中的某些实例方法。通常来说,在 React 中,组件应该遵循数据自上而下的流动,并通过属性和状态进行通信。但有时,你可能需要在父组件中直接调用子组件的某些方法。这就是 useImperativeHandle 发挥作用的地方。

这个钩子不常用,因为它打破了 React 的一些核心原则,但在某些特定场景下可能很有用。

参数

useImperativeHandle 接受三个参数:

  1. ref: 父组件传递给子组件的 ref 对象。
  2. createHandle: 一个返回包含暴露给父组件的实例方法的对象的函数。
  3. deps: 一个依赖数组,类似于 useEffectuseMemo 等钩子。如果提供了此参数,则只有当依赖项更改时,才会更新实例方法。

示例

假设你有一个 TextInput 组件,并且你想暴露一个方法来清除输入。

import React, { useImperativeHandle, forwardRef, useRef } from 'react';

type TextInputHandles = {
  clear: () => void;
};

const TextInput = forwardRef<TextInputHandles, {}>((props, ref) => {
  const inputRef = useRef<HTMLInputElement>(null);

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

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

const ParentComponent = () => {
  const inputRef = useRef<TextInputHandles>(null);

  const clearInput = () => {
    if (inputRef.current) {
      inputRef.current.clear();
    }
  };

  return (
    <div>
      <TextInput ref={inputRef} />
      <button onClick={clearInput}>Clear Input</button>
    </div>
  );
};

export default ParentComponent;

注意事项

  • 谨慎使用: 尽量不要过度使用 useImperativeHandle,因为它可能导致代码更难理解和维护。
  • forwardRef 配合使用: 通常,你需要将子组件与 forwardRef 一起使用,以将 ref 传递给子组件。

总结

虽然 useImperativeHandle 不是常用的钩子,但在需要在父组件中直接操作子组件的特定场景下,它可能是必要的。通过允许你精确地控制父组件可以访问的子组件方法,它提供了一种强大但易于滥用的工具。在使用它时要小心,并确保这确实是解决问题的最佳方式。

原文链接:https://juejin.cn/post/7264791359838879759 作者:咫尺天涯_飞

(0)
上一篇 2023年8月9日 上午11:02
下一篇 2023年8月9日 上午11:13

相关推荐

发表回复

登录后才能评论