资料库
前端开发
React.js
React Hooks 高级技巧

React Hooks 提供了一种更加函数式的方式来处理组件逻辑,这使得开发者可以探索许多高级技巧和模式。以下是一些常见的高级技巧:

1. 使用 useImperativeHandleforwardRef

import React, { useRef, useImperativeHandle, forwardRef } from 'react';
 
const FancyInput = forwardRef((props, ref) => {
  const inputRef = useRef();
  
  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>
  );
}

2. 使用 useLayoutEffect 进行同步副作用

import React, { useLayoutEffect, useState } from 'react';
 
function App() {
  const [color, setColor] = useState('red');
 
  useLayoutEffect(() => {
    document.title = `The color is ${color}`;
  }, [color]);
 
  return (
    <div>
      <button onClick={() => setColor('blue')}>Change to Blue</button>
    </div>
  );
}

3. 使用 useDebugValue 在 React DevTools 中显示自定义 Hook 的值

import React, { useState, useDebugValue } from 'react';
 
function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);
 
  // Just a mockup, in a real-world scenario, you'd fetch the friend's status
  useDebugValue(isOnline ? 'Online' : 'Offline');
 
  return isOnline;
}
 
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);
 
  return (
    <div>
      {isOnline ? 'Online' : 'Offline'}
    </div>
  );
}

4. 管理副作用的取消和清理

import React, { useState, useEffect } from 'react';
 
function SearchResults(props) {
  const [data, setData] = useState([]);
  const [query, setQuery] = useState('');
 
  useEffect(() => {
    let isCancelled = false;
 
    fetch(`/search?query=${query}`)
      .then(response => response.json())
      .then(result => {
        if (!isCancelled) {
          setData(result);
        }
      });
 
    return () => {
      isCancelled = true;
    };
  }, [query]);
 
  return (
    <div>
      {/* ... */}
    </div>
  );
}

5. 自定义 Hooks

  • 通过组合多个基本的 Hooks(如 useStateuseEffect),你可以创建自定义的 Hooks,从而复用组件逻辑。

  • 例如,你可以创建一个 useLocalStorage Hook,用于将状态同步到本地存储。

    function useLocalStorage(key, initialValue) {
      const [storedValue, setStoredValue] = useState(() => {
        try {
          const item = window.localStorage.getItem(key);
          return item ? JSON.parse(item) : initialValue;
        } catch (error) {
          return initialValue;
        }
      });
     
      const setValue = value => {
        try {
          setStoredValue(value);
          window.localStorage.setItem(key, JSON.stringify(value));
        } catch (error) {
          console.error('Failed to set value in localStorage:', error);
        }
      };
     
      return [storedValue, setValue];
    }

6. 使用 useRef 保存状态

  • useRef 不仅可以用于引用 DOM 元素,还可以用于保存不触发重新渲染的任何可变值。

    function Timer() {
      const count = useRef(0);
     
      useEffect(() => {
        const interval = setInterval(() => {
          count.current += 1;
          console.log(`Elapsed time: ${count.current} seconds`);
        }, 1000);
     
        return () => clearInterval(interval);
      }, []);
     
      return <div>Check the console to see the elapsed time.</div>;
    }

7. 使用 useReducer 进行复杂状态管理

  • 对于复杂的状态逻辑,useState 可能不够用。在这种情况下,useReducer 提供了一种更加详细和可预测的方式来管理状态。

  • 它允许你将状态逻辑组织成一个 reducer 函数,这在管理复杂的状态转换或处理副作用时非常有用。

    function todoReducer(state, action) {
      switch (action.type) {
        case 'ADD_TODO':
          return [...state, action.payload];
        case 'REMOVE_TODO':
          return state.filter(todo => todo.id !== action.payload.id);
        default:
          return state;
      }
    }
     
    function TodoApp() {
      const [todos, dispatch] = useReducer(todoReducer, []);
     
      const addTodo = (todo) => {
        dispatch({ type: 'ADD_TODO', payload: todo });
      };
     
      // ... 其他逻辑
    }

8. 与 Context API 结合

  • useContext Hook 可以与 React 的 Context API 结合,使得全局或深层嵌套的状态管理变得简单。这在创建如主题切换、语言切换等功能时非常有用。
  1. 创建一个 context
import React, { createContext, useContext, useState } from 'react';
 
// 创建一个 Context 对象
const ThemeContext = createContext();
 
// 创建一个 Provider 组件
export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light'); // 默认主题为 'light'
 
  const toggleTheme = () => {
    setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
  };
 
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};
  1. 在组件中使用 useContext
function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);
 
  return (
    <button
      onClick={toggleTheme}
      style={{
        backgroundColor: theme === 'light' ? '#eee' : '#333',
        color: theme === 'light' ? '#333' : '#eee'
      }}
    >
      Toggle Theme
    </button>
  );
}
  1. 在应用中使用 ThemeProvider
function App() {
  return (
    <ThemeProvider>
      <div>
        <ThemedButton />
        {/* 其他组件 */}
      </div>
    </ThemeProvider>
  );
}
 
export default App;

在上述示例中,我们首先创建了一个 ThemeContext。然后,我们创建了一个 ThemeProvider 组件,它使用 ThemeContext.Provider 来提供一个 theme 和一个 toggleTheme 函数给其子组件。

ThemedButton 组件中,我们使用 useContext Hook 来访问这些值,并根据当前的主题来设置按钮的样式。当按钮被点击时,它会切换主题。

这种模式可以轻松地扩展到更复杂的应用中,例如进行语言切换、用户认证状态管理等。

9. 测试自定义 Hooks

  • 使用如 @testing-library/react-hooks 这样的库,可以帮助你轻松地测试自定义 Hooks,确保它们的逻辑正确。

10. 依赖项数组的技巧

  • useEffectuseMemouseCallback 中,你可以使用 ESLint 插件 eslint-plugin-react-hooks 来自动检测依赖项数组。
  • 使用空的依赖项数组 [] 可以模拟 componentDidMountcomponentWillUnmount 的行为。