React基础学习-Day04

React基础学习-Day04

常见的钩子函数及基础使用方式

1.useState

useState 是 React 的一个 Hook,用于在函数组件中添加状态。它返回一个状态变量和一个更新该状态的函数。与类组件的 this.statethis.setState 相对应,useState 让函数组件也能拥有和管理状态。

基本用法

以下是 useState 的基本用法示例:

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

const Counter = () => {
  // 声明一个名为 "count" 的状态变量,初始值为 0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};

export default Counter;

在这个示例中,useState(0) 声明了一个名为 count 的状态变量,并将其初始值设为 0setCount 是更新 count 状态的函数。当点击按钮时,调用 setCount(count + 1)count 增加 1,并重新渲染组件。

多个状态

可以在同一个组件中使用多个 useState 声明不同的状态变量:

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

const UserProfile = () => {
  const [name, setName] = useState('Alice');
  const [age, setAge] = useState(25);

  return (
    <div>
      <p>Name: {name}</p>
      <p>Age: {age}</p>
      <button onClick={() => setName('Bob')}>Change Name</button>
      <button onClick={() => setAge(age + 1)}>Increase Age</button>
    </div>
  );
};

export default UserProfile;

在这个示例中,我们使用了两个 useState Hook,一个用于管理 name 状态,另一个用于管理 age 状态。每个状态都有自己独立的更新函数。

初始化状态

useState 可以接受一个函数作为初始状态,这个函数在初始渲染时会被调用,用于计算初始状态。这对于需要复杂计算才能得到初始状态的情况非常有用。

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

const ExpensiveComponent = () => {
  const [value, setValue] = useState(() => {
    // 模拟一个耗时的计算
    const initialValue = computeExpensiveValue();
    return initialValue;
  });

  return (
    <div>
      <p>Value: {value}</p>
      <button onClick={() => setValue(value + 1)}>Increment</button>
    </div>
  );
};

const computeExpensiveValue = () => {
  // 这里模拟一个复杂的计算
  console.log('Computing expensive value...');
  return 42;
};

export default ExpensiveComponent;

在这个示例中,useState 接受一个函数,该函数只在初始渲染时被调用一次,用于计算初始状态值。

注意事项

  • 每次调用 setState 更新状态时,React 会重新渲染组件。
  • 更新状态是异步的,因此在调用 setState 后立即读取状态可能不会得到最新的值。
  • 可以通过函数式更新来确保状态更新基于最新的状态值:
jsx 复制代码
const [count, setCount] = useState(0);

const increment = () => {
  setCount(prevCount => prevCount + 1);
};

在这个示例中,setCount 接受一个函数,该函数的参数是之前的状态值,返回的新状态值将基于之前的状态值进行更新。

完整示例

以下是一个使用多个状态和函数式更新的完整示例:

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

const App = () => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('Hello');

  const increment = () => {
    setCount(prevCount => prevCount + 1);
  };

  const changeText = () => {
    setText(prevText => prevText === 'Hello' ? 'World' : 'Hello');
  };

  return (
    <div>
      <p>Count: {count}</p>
      <p>Text: {text}</p>
      <button onClick={increment}>Increment Count</button>
      <button onClick={changeText}>Change Text</button>
    </div>
  );
};

export default App;

在这个示例中,我们使用了 useState 来管理两个独立的状态变量 counttext,并使用函数式更新确保状态更新基于最新的状态值。这有助于避免状态更新的竞争条件。

ps:useState的第一个参数用函数可以使用异步函数吗

useState 的第一个参数是初始状态,它可以是一个值或者是一个函数,但这个函数必须是同步的。React 不支持在 useState 的初始状态函数中使用异步函数。useState 的初始状态函数只会在组件的初始渲染时被调用一次,并且它是同步的。

如果你需要在组件初始化时从异步操作中获取数据并设置状态,你可以在 useEffect Hook 中执行异步操作,并在操作完成后更新状态。

以下是一个如何使用 useEffectuseState 来处理异步操作的示例:

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

const FetchDataComponent = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('https://api.example.com/data');
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []); // 空数组意味着这个 effect 只会在组件挂载和卸载时执行一次

  if (loading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return (
    <div>
      <h1>Fetched Data:</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
};

export default FetchDataComponent;

在这个示例中:

  1. useState 用于管理组件的状态,包括 dataloadingerror
  2. useEffect 用于执行异步数据获取操作。当组件首次挂载时,这个 effect 会运行,触发 fetchData 异步函数。
  3. fetchData 异步函数使用 fetch 从 API 获取数据。如果成功,数据将被存储在 data 状态中,并且 loading 状态被设置为 false。如果出现错误,error 状态将被设置,并且 loading 状态同样被设置为 false

这样,虽然不能在 useState 的初始状态函数中使用异步函数,但可以通过 useEffect 来实现异步数据获取和状态管理。

2.useEffect

useEffect 是 React 的一个 Hook,用于在函数组件中执行副作用操作。副作用操作包括数据获取、订阅、手动 DOM 操作以及在组件更新或卸载时执行清理任务。useEffect 可以看作是 componentDidMountcomponentDidUpdatecomponentWillUnmount 的组合。

基本用法

以下是 useEffect 的基本用法示例:

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

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

  // useEffect 中的函数会在组件渲染后执行
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]); // 依赖项数组

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};

export default ExampleComponent;

在这个示例中,useEffect 中的函数会在组件每次渲染后执行。依赖项数组 [count] 指定了只有当 count 状态发生变化时,useEffect 才会重新运行。

依赖项数组

useEffect 的第二个参数是依赖项数组,指定了哪些状态或属性的变化会触发 useEffect 的重新运行:

  • 空数组 []:仅在组件挂载和卸载时运行一次。
  • 未指定依赖项数组:在每次组件渲染后都会运行。
  • 特定依赖项:当依赖项发生变化时才会运行。

清理副作用

如果 useEffect 返回一个函数,这个函数会在组件卸载时或在下一次执行副作用前被调用,用于清理副作用。例如,清理订阅或取消定时器:

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

const TimerComponent = () => {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prevSeconds => prevSeconds + 1);
    }, 1000);

    // 返回一个清理函数,在组件卸载时清除定时器
    return () => clearInterval(interval);
  }, []); // 空数组意味着这个 effect 只会在组件挂载和卸载时执行一次

  return (
    <div>
      <p>Seconds: {seconds}</p>
    </div>
  );
};

export default TimerComponent;

在这个示例中,useEffect 设置了一个定时器,每秒更新一次 seconds 状态。在 useEffect 中返回的清理函数会在组件卸载时被调用,以清除定时器。

数据获取示例

以下是一个使用 useEffect 执行数据获取操作的示例:

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

const FetchDataComponent = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('https://api.example.com/data');
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []); // 空数组意味着这个 effect 只会在组件挂载和卸载时执行一次

  if (loading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return (
    <div>
      <h1>Fetched Data:</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
};

export default FetchDataComponent;

在这个示例中,useEffect 用于执行异步数据获取操作。组件挂载时,fetchData 函数会运行,并在数据获取成功后更新 data 状态。如果出现错误,error 状态会被更新。

总结

useEffect 是一个功能强大的 Hook,用于在函数组件中管理副作用操作。通过合理使用依赖项数组和清理函数,useEffect 可以帮助我们在组件的生命周期中执行和管理各种副作用。

3.useContext

useContext 是 React 的一个 Hook,用于在函数组件中读取和使用 React 上下文(Context)。上下文允许您在组件树中传递数据,而不必手动逐层传递 props。

基本用法

以下是 useContext 的基本用法示例:

假设我们有一个上下文对象 ThemeContext

jsx 复制代码
import React, { createContext, useContext, useState } from 'react';

// 创建一个上下文对象
const ThemeContext = createContext();

// 上下文的提供者组件
const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');

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

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

// 使用上下文的消费者组件
const ThemeConsumerComponent = () => {
  const { theme, toggleTheme } = useContext(ThemeContext);

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

// 在组件树中使用 ThemeProvider 包裹所有需要访问 theme 上下文的组件
const App = () => {
  return (
    <ThemeProvider>
      <ThemeConsumerComponent />
    </ThemeProvider>
  );
};

export default App;

在这个示例中:

  1. ThemeContext 是一个上下文对象,通过 createContext() 创建。
  2. ThemeProvider 是一个提供者组件,使用 ThemeContext.Provider 包装其子组件,并通过 value 属性传递 themetoggleTheme 函数。
  3. ThemeConsumerComponent 是一个消费者组件,使用 useContext(ThemeContext) 来订阅 ThemeContext 上下文。它从上下文中读取 themetoggleTheme,并在按钮点击时切换主题。

注意事项

  • 使用 createContext() 创建上下文对象。
  • 使用 Provider 组件包裹子组件,并通过 value 属性传递数据。
  • 在需要访问上下文数据的组件中使用 useContext(ThemeContext)

多个上下文

您可以在应用程序中使用多个上下文。每个上下文对象都应该有自己的提供者和消费者组件。React 会确保正确的数据传递和更新。

总结

useContext Hook 提供了一种轻松地在函数组件中使用 React 上下文的方式。它使得组件可以更简洁地订阅和使用跨组件树的数据。通过 useContext,您可以避免 props drilling,提高组件的可重用性和可维护性。

4.useReducer

一、介绍useReducer的重要性和优势

useReducer是 React 中的一个 Hook,用于管理应用状态。它提供了一种更简洁、更易于理解的方式来处理复杂的状态逻辑。

重要性:

状态管理:在 React 应用中,状态管理是至关重要的。useReducer允许开发者以更清晰和集中的方式管理应用的状态。

复杂状态逻辑:对于涉及多个状态变量和复杂的更新逻辑的场景,使用useReducer可以更好地组织和维护代码。

可预测的状态更新:useReducer使用函数来更新状态,这使得状态更新更加可预测和易于理解。

更好的代码可读性:通过使用useReducer,可以将状态更新逻辑拆分为独立的函数,提高代码的可读性和可维护性。

优势:

简化代码:相比使用多个 useState 钩子来管理复杂的状态,useReducer 可以减少代码的冗余。

更好的性能:useReducer在某些情况下可以提供更好的性能,尤其是在处理大量状态更新时。

状态的合并:useReducer支持合并多个更新操作,从而减少不必要的重新渲染。

清晰的状态更新逻辑:使用useReducer可以将状态更新逻辑放在一个单独的函数中,使其更加清晰和易于理解。

总之,useReducer对于处理复杂的状态逻辑和更好地组织状态更新非常有用。它提供了一种更简洁、可预测和可读性更高的方式来管理应用状态。在需要处理复杂状态的情况下,推荐使用useReducer。

二、useReducer的基本概念

解释useReducer的定义和作用

useReducer是React Hooks中的一个函数,它用于在React应用程序中实现状态管理。useReducer函数接收两个参数:一个是reducer函数,另一个是初始状态。reducer函数接收两个参数:一个是当前状态,另一个是action对象。action对象通常包含一个type属性,表示要进行的操作,以及可能的其他属性。

useReducer函数返回一个数组,数组的第一个元素是当前状态,第二个元素是一个函数,该函数用于更新状态。当组件需要更新状态时,它将调用该函数,并将新状态作为参数传递给它。该函数将使用reducer函数来计算新状态,并将其返回给组件。

下面是一个简单的示例,演示如何使用useReducer来管理计数器状态:

jsx 复制代码
import React, { useReducer } from 'react';

function reducer(state, action) {
 switch (action.type) {
   case 'increment':
     return state + 1;
   case 'decrement':
     return state - 1;
   default:
     throw new Error();
 }
}

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

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

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

 return (

   <div>
     <h1>{state}</h1>
     <button onClick={handleIncrement}>+</button>
     <button onClick={handleDecrement}>-</button>
   </div>

 );
}

在这个示例中,我们定义了一个reducer函数reducer,它接收两个参数:当前状态state和action对象action。action对象包含一个type属性,表示要进行的操作。然后,我们使用useReducer函数将reducer函数和初始状态传递给组件。useReducer函数返回一个数组,数组的第一个元素是当前状态,第二个元素是一个函数,用于更新状态。

我们定义了两个函数handleIncrement和handleDecrement,分别用于处理加1和减1操作。然后,我们将这些函数绑定到按钮的onClick事件上,以便在按钮被点击时调用它们。

最后,我们将当前状态显示在页面上,以便用户可以看到计数器的值在不断变化。当用户点击按钮时,我们将调用dispatch函数,并将相应的action对象传递给它。dispatch函数将调用reducer函数来计算新状态,并将新状态返回给组件。

总之,useReducer函数在React应用程序中提供了一个简单、高效的状态管理解决方案,可以用于管理复杂的应用程序状态。

与其他状态管理方法进行比较

以下是使用表格总结的useReducer与其他状态管理方法的比较:

方法 描述 优点 缺点

useState React内置的状态管理方法,用于管理简单的状态。 简单易用 无法处理复杂的业务逻辑

useEffect 用于在函数组件中添加副作用,如数据获取、订阅等。 灵活性高 需要在组件内手动处理副作用

useContext 用于在不同组件之间共享状态,而不需要显式地传递状态。 共享状态简单易用 无法处理副作用

useReducer 用于管理复杂的状态,如复杂的业务逻辑、表格数据等。 灵活性高,易于处理复杂的业务逻辑 需要手动编写reducer函数

从表格中可以看出,useReducer方法在处理复杂的状态上具有优势,因为它可以方便地使用reducer函数来处理复杂的业务逻辑。同时,它也可以处理副作用,如数据获取、订阅等。但是,它需要手动编写reducer函数,这可能会增加一些复杂性。而useState和useEffect方法则更适合处理简单的状态和管理副作用。useContext方法则更适合在不同组件之间共享状态。

总之,选择哪种状态管理方法取决于具体的需求和组件的结构。在实际开发中,可以根据项目的规模和复杂度来选择合适的状态管理方法。

三、useReducer的使用示例

解释useReducer的参数和返回值

useReducer 是一个 React 状态管理方法,它的参数和返回值如下:

参数:

reducer 函数:这个函数接收两个参数,分别是当前状态(state)和一个 action。该函数的作用是处理传入的状态,并返回一个新的状态。

initialState:状态的初始值。

返回值:

新的 state:由 reducer 函数处理后返回的新状态。

dispatch 函数:用于发送一个对象(action)给 reducer 函数,以更新状态,并触发重新渲染。

展示如何更新状态和触发重新渲染

以下是使用 useReducer 更新状态和触发重新渲染的示例:

jsx 复制代码
import React, { useReducer } from 'react';

const initialState = {
  count: 0
};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {
        ...state,
        count: state.count + 1
      };
    case 'decrement':
      return {
        ...state,
        count: state.count - 1
      };
    default:
      return state;
  }
}

function MyComponent() {
  // 使用 useReducer 来创建状态和更新函数
  const [state, dispatch] = useReducer(reducer, initialState);

  const handleIncrement = () => {
    // 发送一个 increment 类型的 action 来更新状态
    dispatch({ type: 'increment' });
  };

  const handleDecrement = () => {
    // 发送一个 decrement 类型的 action 来更新状态
    dispatch({ type: 'decrement' });
  };

  return (
    <div>
      <h1>{state.count}</h1>
      <button onClick={handleIncrement}>+</button>
      <button onClick={handleDecrement}>-</button>
    </div>
  );
}

export default MyComponent;

在上述示例中,使用 useReducer 创建了一个状态 count,初始值为 0。定义了一个 reducer 函数来处理状态的更新。reducer 函数根据不同的 action.type 来执行相应的状态更新操作。

通过调用 dispatch 函数并传入一个对象作为 action,可以更新状态。在示例中,点击 "+" 按钮会发送一个 increment 类型的 action,点击 "-" 按钮会发送一个 decrement 类型的 action。

当状态更新后,组件会重新渲染,显示最新的状态值。

5.useRef

useRef 是 React 的一个 Hook,用于在函数组件中创建可变的引用对象。与 useState 不同,useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期中保持不变,不会因重新渲染而重新创建。

主要用途

  1. 获取 DOM 元素的引用

    jsx 复制代码
    import React, { useRef, useEffect } from 'react';
    
    const TextInputComponent = () => {
      const inputRef = useRef(null);
    
      useEffect(() => {
        // 在组件加载后聚焦到输入框
        inputRef.current.focus();
      }, []);
    
      return (
        <div>
          <input type="text" ref={inputRef} />
          <button onClick={() => inputRef.current.focus()}>Focus Input</button>
        </div>
      );
    };
    
    export default TextInputComponent;

    在这个示例中,useRef 创建了一个 inputRef 对象,并将其赋值给 <input> 元素的 ref 属性。通过 inputRef.current 可以访问到真实的 DOM 元素,比如调用 .focus() 方法来聚焦输入框。

  2. 保存任意可变值

    jsx 复制代码
    import React, { useRef } from 'react';
    
    const CounterComponent = () => {
      const countRef = useRef(0);
    
      const increment = () => {
        countRef.current++;
        console.log('Current count:', countRef.current);
      };
    
      return (
        <div>
          <p>Count: {countRef.current}</p>
          <button onClick={increment}>Increment</button>
        </div>
      );
    };
    
    export default CounterComponent;

    在这个示例中,useRef 创建了一个 countRef 对象,并初始化为 0。在 increment 函数中,通过修改 countRef.current 来更新计数器的值,而不需要触发组件的重新渲染。

  3. 保存上一个 props 或 state

    jsx 复制代码
    import React, { useRef, useEffect } from 'react';
    
    const PreviousValueComponent = ({ value }) => {
      const prevValueRef = useRef();
    
      useEffect(() => {
        prevValueRef.current = value;
      });
    
      const prevValue = prevValueRef.current;
    
      return (
        <div>
          <p>Current Value: {value}</p>
          <p>Previous Value: {prevValue !== undefined ? prevValue : 'N/A'}</p>
        </div>
      );
    };
    
    export default PreviousValueComponent;

    在这个示例中,通过 useRef 创建了 prevValueRef 对象,用于保存 value 的上一个值。通过在 useEffect 中更新 prevValueRef.current,可以在组件的重新渲染中获取到上一个 value 的值。

注意事项

  • useRef 创建的 ref 对象在组件的整个生命周期中保持不变,不会因重新渲染而重新创建。
  • 修改 useRef 创建的 ref 对象的 .current 属性不会触发组件的重新渲染。
  • 可以通过 useRef 来保存和访问 DOM 元素的引用、保存任意可变值以及保存上一个 props 或 state 的值。

6.useCallback

useCallback 是 React 的一个 Hook,它返回一个记忆化的回调函数,用于优化函数组件的性能,防止不必要的重新渲染和重新创建函数。在依赖项没有发生变化时,useCallback 返回的函数引用保持不变,从而避免子组件因为父组件的函数变化而重新渲染。

基本用法

以下是 useCallback 的基本用法示例:

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

const ChildComponent = React.memo(({ onClick }) => {
  console.log('Rendering ChildComponent');
  return <button onClick={onClick}>Click me</button>;
});

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

  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
      <ChildComponent onClick={handleClick} />
    </div>
  );
};

export default ParentComponent;

在这个示例中,handleClick 函数使用了 useCallback,并且依赖项数组为空([]),这意味着 handleClick 函数在组件的整个生命周期中只会创建一次。这样,ChildComponent 组件不会因为 ParentComponent 重新渲染而重新渲染,因为 onClick 属性的引用没有变化。

依赖项数组

useCallback 的第二个参数是依赖项数组,只有当依赖项数组中的某个依赖项发生变化时,才会重新创建回调函数。例如:

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

const ChildComponent = React.memo(({ onClick }) => {
  console.log('Rendering ChildComponent');
  return <button onClick={onClick}>Click me</button>;
});

const ParentComponent = () => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('Hello');

  const handleClick = useCallback(() => {
    console.log('Button clicked with text:', text);
  }, [text]);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Text: {text}</p>
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
      <button onClick={() => setText(text === 'Hello' ? 'World' : 'Hello')}>Change Text</button>
      <ChildComponent onClick={handleClick} />
    </div>
  );
};

export default ParentComponent;

在这个示例中,handleClick 函数依赖于 text 状态。每当 text 状态发生变化时,handleClick 函数都会重新创建。这确保了 handleClick 始终使用最新的 text 值。

何时使用 useCallback

useCallback 在以下情况下特别有用:

  • 传递回调函数给子组件,且子组件依赖于回调函数的引用。
  • 回调函数在组件重新渲染时不需要重新创建。
  • 优化性能,防止因函数引用变化导致的子组件不必要的重新渲染。

注意事项

  • useCallback 仅用于记忆化回调函数,避免不必要的函数重新创建和子组件重新渲染。
  • 过度使用 useCallback 可能导致代码复杂性增加,应根据实际需要使用。
  • 记忆化函数会占用内存,特别是当依赖项变化频繁时,可能会影响性能。

示例总结

以下是一个使用 useCallback 优化回调函数的完整示例:

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

const ChildComponent = React.memo(({ onClick }) => {
  console.log('Rendering ChildComponent');
  return <button onClick={onClick}>Click me</button>;
});

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

  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
      <ChildComponent onClick={handleClick} />
    </div>
  );
};

export default ParentComponent;

在这个示例中,handleClick 函数使用了 useCallback,确保在父组件重新渲染时不会重新创建,从而避免了子组件的重新渲染,提高了性能。

7.useMemo

useMemo定义

useMemo是 React 框架中的一个重要 Hook,它的核心目的是通过缓存计算结果,避免在组件渲染时进行不必要的重复计算,从而优化性能。这意味着只有当其依赖项发生变化时,useMemo才会重新计算这个值,否则它将重用之前的结果。

它的基本使用格式如下:

js 复制代码
const cachedValue = useMemo(calculateValue, dependencies)
  • calculateValue:这是一个用于计算我们想要缓存的值的函数。为了确保结果的稳定性和预测性,这个函数应该是一个纯函数。这意味着,它在相同的输入下总是返回相同的输出,并且没有任何副作用。
  • dependencies:这是一个数组,包含useMemo所依赖的变量或值。当数组中的任何值发生变化时,calculateValue函数将被重新执行。

useMemo基础用法

useMemo 接受两个参数:一个函数和一个依赖项数组。

js 复制代码
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

在上面的例子中,computeExpensiveValue是一个可能需要很长时间来计算的函数。我们只有当ab改变时,才重新调用这个函数。否则,我们会使用之前缓存的值。

用一个例子来看 useMemo 的执行时机:

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

function filterUsers(users, searchTerm) {
    return users.filter((user) => user.name.includes(searchTerm));
}

function useMemoDemo() {
    const [searchTerm, setSearchTerm] = useState("");
    const [isDark, setIsDark] = useState(false);

    const allUsers = useMemo(() => {
        let list = [];
        for (let i = 1; i <= 500; i++) {
            list.push({ id: i, name: `User${i}` });
        }
        return list;
    }, []);

    const useMemoCurrentUsers = useMemo(() => {
        console.log('with useMemo')
        return filterUsers(allUsers, searchTerm);
    }, [allUsers, searchTerm]);

    return (
        <div>
            {/* 每一次更改查询框内容,都会触发useMemo */}
            <input
                value={searchTerm}
                onChange={(e) => setSearchTerm(e.target.value)}
                placeholder="Search by name..."
            />

            {/* 每一次更改背景色,都不会触发useMemo */}
            <button onClick={() => setIsDark((pre) => !pre)}>
                {isDark ? "Dark mode" : "Light mode"}
            </button>

            <div>
                <div>
                    <h2>With useMemo</h2>
                    <div style={{ background: isDark ? "#000" : "" }}>
                        {useMemoCurrentUsers.map((user) => (
                            <div key={user.id}>
                                {user.name}
                            </div>
                        ))}
                    </div>
                </div>
            </div>
        </div>
    );
}

export default useMemoDemo;

在这里简单的示例中,每次修改查询框的内容,都会触发searchTerm的变化,进而触发useMemo重新计算;而点击切换背景色的按钮,因为useMemo的依赖项没有更新,所以不会触发useMemo重新计算,而是直接使用上一次计算的返回值。

React.memo

React.memo 是 React 中的一个高阶组件(HOC),它通过记忆化函数组件的结果来优化性能,防止不必要的重新渲染。它的工作原理是仅在组件的 props 发生变化时才重新渲染,这对依赖大量 props 且不需要每次父组件重新渲染时都重新渲染的组件特别有用。

使用方法

以下是 React.memo 的基本用法示例:

jsx 复制代码
import React from 'react';

// 一个接收 props 的函数组件
const MyComponent = (props) => {
  console.log('Rendering MyComponent');
  return (
    <div>
      {props.text}
    </div>
  );
};

// 使用 React.memo 包装组件
const MemoizedComponent = React.memo(MyComponent);

export default MemoizedComponent;

在这个示例中,MyComponent 只有在 text prop 发生变化时才会重新渲染。如果父组件重新渲染,但 text prop 保持不变,MyComponent 不会重新渲染。

自定义比较函数

默认情况下,React.memo 进行的是浅比较。如果你需要进行更深层次的比较或有复杂的 props,可以提供自定义的比较函数作为 React.memo 的第二个参数:

jsx 复制代码
import React from 'react';

const MyComponent = (props) => {
  console.log('Rendering MyComponent');
  return (
    <div>
      {props.text}
    </div>
  );
};

const areEqual = (prevProps, nextProps) => {
  // 在这里进行自定义比较
  return prevProps.text === nextProps.text;
};

const MemoizedComponent = React.memo(MyComponent, areEqual);

export default MemoizedComponent;

在这个示例中,areEqual 是一个函数,它接收前一个和下一个 props 并返回 true 表示它们相等,从而防止重新渲染。如果返回 false,组件将会重新渲染。

何时使用 React.memo

React.memo 在以下情况下特别有用:

  • 组件是纯函数组件,并且其输出完全由 props 决定。
  • 组件频繁重新渲染,并且 props 保持不变。
  • 你想要优化性能,避免不必要的重新渲染。

注意事项

  • React.memo 只适用于函数组件。
  • 默认情况下,它进行浅比较,对于复杂的 props 可能不够。
  • 过度使用 React.memo 可能会导致不必要的复杂性。应根据实际情况使用,重点放在那些实际受益于记忆化的组件上。

实例

以下是一个在父组件中使用 React.memo 的实际示例:

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

const ChildComponent = React.memo(({ text }) => {
  console.log('Rendering ChildComponent');
  return <div>{text}</div>;
});

const ParentComponent = () => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('Hello');

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
      <button onClick={() => setText(text === 'Hello' ? 'World' : 'Hello')}>Change Text</button>
      <ChildComponent text={text} />
    </div>
  );
};

export default ParentComponent;

在这个示例中,ChildComponent 只有在 text prop 发生变化时才会重新渲染,即使 ParentComponent 因为 count 状态变化而重新渲染。这可以帮助在大型应用中提高性能。

8.useDeferredValue

useDeferredValue 是 React 18 引入的一个 Hook,用于延迟更新状态值,以提升用户界面的响应速度。在处理高优先级任务(如输入或交互)时,可以使用 useDeferredValue 将低优先级的状态更新推迟,以保持界面的流畅性。

基本用法

以下是 useDeferredValue 的基本用法示例:

jsx 复制代码
import React, { useState, useDeferredValue, useEffect } from 'react';

const SearchComponent = () => {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);

  useEffect(() => {
    // 模拟一个数据获取函数
    const fetchData = async () => {
      console.log('Fetching data for:', deferredQuery);
      // 模拟数据获取延迟
      await new Promise(resolve => setTimeout(resolve, 1000));
      console.log('Data fetched for:', deferredQuery);
    };

    if (deferredQuery) {
      fetchData();
    }
  }, [deferredQuery]);

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Type your search query..."
      />
      <p>Searching for: {deferredQuery}</p>
    </div>
  );
};

export default SearchComponent;

在这个示例中:

  1. useState 用于创建一个 query 状态,表示用户输入的查询。
  2. useDeferredValue 用于创建一个 deferredQuery,它是 query 的延迟副本。
  3. 当用户输入内容时,query 状态立即更新,而 deferredQuery 会稍后更新。
  4. useEffect 钩子监听 deferredQuery 的变化,并在其变化时执行数据获取操作。

延迟更新的好处

使用 useDeferredValue 可以提高用户界面的响应速度,特别是在处理大量数据或复杂计算时。通过将低优先级的状态更新推迟,React 可以优先处理用户输入和交互,从而保持界面的流畅性。

注意事项

  • useDeferredValue 仅在 React 18 及更高版本中可用。
  • useDeferredValue 适用于那些不需要立即更新的状态值,例如搜索查询、过滤条件等。
  • useDeferredValue 不会阻止状态的最终更新,只是将其延迟到更适当的时机。

示例:过滤列表

以下是一个使用 useDeferredValue 进行列表过滤的示例:

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

const ListComponent = ({ items }) => {
  const [filter, setFilter] = useState('');
  const deferredFilter = useDeferredValue(filter);

  const filteredItems = items.filter(item =>
    item.toLowerCase().includes(deferredFilter.toLowerCase())
  );

  return (
    <div>
      <input
        type="text"
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder="Filter items..."
      />
      <ul>
        {filteredItems.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
};

export default ListComponent;

在这个示例中:

  1. useState 用于创建一个 filter 状态,表示用户输入的过滤条件。
  2. useDeferredValue 用于创建一个 deferredFilter,它是 filter 的延迟副本。
  3. filteredItems 列表根据 deferredFilter 进行过滤,以提升用户输入的响应速度。

通过这种方式,可以在处理较大数据集时保持界面的流畅性,同时确保最终的过滤结果是准确的。

9.useTransition

useTransition 是 React 18 引入的一个 Hook,用于管理状态更新的优先级。它允许你将某些状态更新标记为"过渡",这样 React 就可以优先处理更高优先级的任务(如用户输入),而将过渡状态的更新推迟到更适合的时间点。这对于保持用户界面的响应速度非常有用。

基本用法

以下是 useTransition 的基本用法示例:

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

const TransitionComponent = () => {
  const [isPending, startTransition] = useTransition();
  const [input, setInput] = useState('');
  const [list, setList] = useState([]);

  const handleChange = (e) => {
    const value = e.target.value;
    setInput(value);

    // 将状态更新标记为过渡
    startTransition(() => {
      const newList = Array.from({ length: 20000 }, (_, index) => `${value} ${index}`);
      setList(newList);
    });
  };

  return (
    <div>
      <input type="text" value={input} onChange={handleChange} />
      {isPending && <p>Loading...</p>}
      <ul>
        {list.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
};

export default TransitionComponent;

在这个示例中:

  1. useTransition 返回一个布尔值 isPending 和一个函数 startTransition
  2. isPending 表示过渡状态是否正在进行中,可以用来显示加载指示器。
  3. startTransition 用于将某些状态更新标记为过渡。

用法说明

  • useTransition 的语法如下:

    javascript 复制代码
    const [isPending, startTransition] = useTransition();
  • isPending 是一个布尔值,指示过渡是否正在进行。

  • startTransition 是一个函数,用于包裹过渡状态更新的逻辑。

处理大量数据

以下是一个处理大量数据的示例,通过 useTransition 保持界面的响应速度:

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

const BigDataComponent = () => {
  const [isPending, startTransition] = useTransition();
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  const handleSearch = (e) => {
    const value = e.target.value;
    setQuery(value);

    startTransition(() => {
      // 模拟数据处理
      const filteredResults = mockData.filter(item => item.includes(value));
      setResults(filteredResults);
    });
  };

  return (
    <div>
      <input type="text" value={query} onChange={handleSearch} placeholder="Search..." />
      {isPending && <p>Loading results...</p>}
      <ul>
        {results.map((result, index) => (
          <li key={index}>{result}</li>
        ))}
      </ul>
    </div>
  );
};

const mockData = Array.from({ length: 10000 }, (_, index) => `Item ${index}`);

export default BigDataComponent;

在这个示例中:

  1. 当用户输入搜索查询时,handleSearch 函数会立即更新 query 状态。
  2. startTransition 用于标记数据过滤操作为过渡状态,从而使得 React 可以优先处理用户输入,并推迟执行较重的数据处理任务。
  3. 如果过渡状态正在进行,isPendingtrue,显示加载指示器。

注意事项

  • useTransition 适用于那些可以延迟更新的状态,例如数据过滤、分页加载等。
  • startTransition 包裹的状态更新不会阻塞用户输入或其他高优先级任务,从而提升用户体验。

10.useImperativeHandle

在 React 中,useImperativeHandle 是一个自定义 Hook,用于自定义暴露给父组件的实例值或者方法。它通常与 forwardRef 结合使用,用于提供更精细的控制和优化。

基本用法

useImperativeHandle 允许你在函数组件中指定对外暴露的实例值或者方法,通常用于封装底层 DOM 元素或者其他组件实例,从而提供更直接的访问接口给父组件。

jsx 复制代码
import React, { useRef, useImperativeHandle, forwardRef } from 'react';

// 子组件
const ChildComponent = forwardRef((props, ref) => {
  const childRef = useRef(null);

  // 通过 useImperativeHandle 暴露给父组件的实例值或者方法
  useImperativeHandle(ref, () => ({
    // 在这里定义暴露给父组件的实例值或者方法
    focus: () => {
      childRef.current.focus();
    },
    // 可选的清理函数
    // cleanup: () => {
    //   // 可选的清理逻辑
    // }
  }));

  return <input ref={childRef} />;
});

// 父组件
const ParentComponent = () => {
  const childRef = useRef(null);

  const handleClick = () => {
    // 调用子组件暴露的方法
    childRef.current.focus();
  };

  return (
    <div>
      <ChildComponent ref={childRef} />
      <button onClick={handleClick}>Focus Child Input</button>
    </div>
  );
};

export default ParentComponent;

使用说明

  • forwardRef 和 useRef: 使用 forwardRefuseRef 来创建一个能够获取子组件实例的 ref。
  • useImperativeHandle Hook: 使用 useImperativeHandle 来定义在父组件中通过 ref 可以访问到的值或者方法。
  • 实例值或者方法:useImperativeHandle 的回调函数中,可以定义需要暴露给父组件的实例值或者方法。这些值或者方法可以是直接操作 DOM 元素的方法、状态管理等。
  • 清理函数(可选): useImperativeHandle 的第二个参数可以是一个可选的清理函数,用于在组件卸载或者 ref 变化时执行一些清理逻辑。

注意事项

  • useImperativeHandle 通常用于需要在父组件中操作子组件实例的情况,例如直接访问子组件的 DOM 元素或者调用子组件的方法。
  • 合理使用 useImperativeHandle 可以提高代码的可读性和灵活性,但要避免滥用,过多暴露子组件的内部实现可能会导致组件耦合性增加,不利于组件复用和维护。

总结

useImperativeHandle 是一个用于自定义暴露给父组件的实例值或者方法的自定义 Hook,在处理需要直接访问子组件实例的情况时非常有用。它提供了一种灵活的方式来管理和操作组件实例,使得组件之间的交互更加直接和高效。

相关推荐
饮长安千年月2 小时前
Linksys WRT54G路由器溢出漏洞分析–运行环境修复
网络·物联网·学习·安全·机器学习
红花与香菇2____2 小时前
【学习笔记】Cadence电子设计全流程(二)原理图库的创建与设计(上)
笔记·嵌入式硬件·学习·pcb设计·cadence·pcb工艺
天宇&嘘月2 小时前
web第三次作业
前端·javascript·css
小王不会写code2 小时前
axios
前端·javascript·axios
luckyext4 小时前
HBuilderX中,VUE生成随机数字,vue调用随机数函数
前端·javascript·vue.js·微信小程序·小程序
小小码农(找工作版)4 小时前
JavaScript 前端面试 4(作用域链、this)
前端·javascript·面试
一天八小时4 小时前
Docker学习进阶
学习·docker·容器
前端没钱4 小时前
前端需要学习 Docker 吗?
前端·学习·docker
拥有一颗学徒的心4 小时前
鸿蒙第三方库MMKV源码学习笔记
笔记·学习·性能优化·harmonyos
车端域控测试工程师4 小时前
【ISO 14229-1:2023 UDS诊断(ECU复位0x11服务)测试用例CAPL代码全解析⑰】
经验分享·学习·汽车·测试用例·capl