第三章 ReactHooks详解

42 阅读19分钟

React Hooks 是 React 库的一项功能,它允许你在函数组件中使用状态管理、副作用处理等功能,以前只能在类组件中使用。React Hooks 的目标是使函数组件更具可复用性、可测试性和可维护性。

1.1 React内置Hooks

React内置了一些常用的Hooks,可以帮助你在函数组件中管理状态、副作用等。以下是React内置Hooks的主要内容:

  • useStateuseState Hook 允许你在函数组件中添加状态。这个状态可以是基本类型(如字符串、数字)或对象。商业案例:假设你正在开发一个待办事项列表应用程序,你可以使用useState来管理用户添加的任务列表。
import React, { useState } from 'react';
​
function TodoApp() {
  const [tasks, setTasks] = useState([]);
  const [newTask, setNewTask] = useState('');
​
  const addTask = () => {
    setTasks([...tasks, newTask]);
    setNewTask('');
  };
​
  return (
    <div>
      <input
        type="text"
        value={newTask}
        onChange={(e) => setNewTask(e.target.value)}
      />
      <button onClick={addTask}>Add Task</button>
      <ul>
        {tasks.map((task, index) => (
          <li key={index}>{task}</li>
        ))}
      </ul>
    </div>
  );
}
​
export default TodoApp;
  • useEffectuseEffect Hook 用于处理副作用,如数据获取、DOM操作等。商业案例:如果你需要从服务器获取数据,并在组件渲染后进行处理,可以使用useEffect
import React, { useState, useEffect } from 'react';
​
function UserProfile() {
  const [user, setUser] = useState(null);
​
  useEffect(() => {
    // 模拟异步数据获取
    fetch('https://api.example.com/user')
      .then((response) => response.json())
      .then((data) => setUser(data));
  }, []); // 空数组表示仅在组件加载时执行return (
    <div>
      {user ? (
        <div>
          <h1>{user.name}</h1>
          <p>{user.email}</p>
        </div>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
}
​
export default UserProfile;
  • useContextuseContext Hook 允许你在组件之间共享全局状态。商业案例:如果你有一个需要在多个组件之间共享用户身份验证状态的应用程序,可以使用useContext
import React, { useContext } from 'react';
​
const AuthContext = React.createContext();
​
function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
​
  const login = (userData) => {
    setUser(userData);
  };
​
  const logout = () => {
    setUser(null);
  };
​
  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}
​
function NavBar() {
  const { user, logout } = useContext(AuthContext);
​
  return (
    <div>
      {user ? (
        <div>
          <p>Welcome, {user.name}!</p>
          <button onClick={logout}>Logout</button>
        </div>
      ) : (
        <p>Not logged in</p>
      )}
    </div>
  );
}
​
export { AuthProvider, NavBar };

1.2 state的特点

在React中,state 是管理组件内部状态的重要概念。以下是state 的特点的详细说明:

  1. 可变性(Mutable)state 是组件内部的可变数据。你可以使用setState来改变state 的值。这种可变性使得你可以动态地更新组件的呈现,以响应用户交互、数据获取或其他事件。

    import React, { useState } from 'react';
    ​
    function MutableStateExample() {
      // 使用useState定义一个名为count的state变量,初始值为0
      const [count, setCount] = useState(0);
    ​
      // 定义一个名为increment的函数,用于增加count的值
      const increment = () => {
        // 使用setCount来改变state的值,将count增加1
        setCount(count + 1);
      };
    ​
      return (
        <div>
          {/* 显示当前的count值 */}
          <p>Count: {count}</p>
          {/* 当按钮被点击时,调用increment函数 */}
          <button onClick={increment}>Increment</button>
        </div>
      );
    }
    ​
    export default MutableStateExample;
    

    在这个例子中,我们使用了useState来创建一个名为count的可变状态变量,并在组件中显示它。当用户点击按钮时,会调用increment函数,该函数使用setCount来更新count的值,实现了可变性,以便动态地更新组件的呈现。注释帮助解释了每一行代码的作用。

  2. 组件私有(Component-specific) :每个组件都有自己的state,并且state 只能在组件内部访问。这意味着一个组件的state 对其他组件是不可见的,从而确保了组件的封装性和隔离性。

    import React, { useState } from 'react';
    ​
    // 创建一个名为ComponentPrivateState的函数组件
    function ComponentPrivateState() {
      // 使用useState定义一个名为message的state变量,初始值为'Hello from the component!'
      const [message, setMessage] = useState('Hello from the component!');
    ​
      return (
        <div>
          {/* 在组件内部显示message的值 */}
          <p>{message}</p>
        </div>
      );
    }
    ​
    export default ComponentPrivateState;
    

    在这个示例中,我们创建了一个名为ComponentPrivateState的函数组件,该组件拥有自己的state变量messagemessage的值只能在组件内部访问,对其他组件是不可见的。这确保了message的封装性和隔离性,使其成为组件私有的状态。注释帮助解释了每一行代码的作用。

  3. 状态管理(State Management)state 通常用于存储组件的数据、状态和配置。它是管理组件行为和呈现的核心机制之一。通过在state 中存储数据,组件可以根据state 的变化来更新UI,以反映当前的状态。

    import React, { useState } from 'react';
    ​
    // 创建一个名为StateManagementExample的函数组件
    function StateManagementExample() {
      // 使用useState定义一个名为user的state变量,初始值为一个包含name和age属性的对象
      const [user, setUser] = useState({ name: 'John', age: 30 });
    ​
      // 定义一个名为updateAge的函数,用于增加user对象的age属性值
      const updateAge = () => {
        // 使用setUser来改变state的值,通过展开操作符创建一个新的user对象,只修改age属性
        setUser({ ...user, age: user.age + 1 });
      };
    ​
      return (
        <div>
          {/* 显示user对象的name和age属性 */}
          <p>Name: {user.name}</p>
          <p>Age: {user.age}</p>
          {/* 当按钮被点击时,调用updateAge函数 */}
          <button onClick={updateAge}>Increase Age</button>
        </div>
      );
    }
    ​
    export default StateManagementExample;
    

    在这个示例中,我们创建了一个名为StateManagementExample的函数组件,该组件使用useState来管理user对象的状态。user对象包含nameage属性,它通常用于存储组件的数据、状态和配置。当用户点击按钮时,会调用updateAge函数,该函数使用setUser来更新user对象的age属性,实现了状态管理的核心机制,以便动态地更新UI以反映当前的状态。注释帮助解释了每一行代码的作用。

  4. 生命周期管理(Lifecycle Management)state 的值可以随着组件的生命周期而变化。你可以在组件的不同生命周期阶段(例如componentDidMountcomponentDidUpdate)中更新state,以执行不同的逻辑。

    import React, { useState, useEffect } from 'react';
    ​
    // 创建一个名为LifecycleManagementExample的函数组件
    function LifecycleManagementExample() {
      // 使用useState定义一个名为count的state变量,初始值为0
      const [count, setCount] = useState(0);
    ​
      // 使用useEffect钩子来模拟componentDidUpdate生命周期
      useEffect(() => {
        // 当count更新后,打印更新后的值
        console.log('Count updated: ', count);
      }, [count]); // 传递[count]作为依赖项,以便在count更新时执行useEffect// 定义一个名为increment的函数,用于增加count的值
      const increment = () => {
        // 使用setCount来改变state的值,将count增加1
        setCount(count + 1);
      };
    ​
      return (
        <div>
          {/* 显示当前的count值 */}
          <p>Count: {count}</p>
          {/* 当按钮被点击时,调用increment函数 */}
          <button onClick={increment}>Increment</button>
        </div>
      );
    }
    ​
    export default LifecycleManagementExample;
    

    在这个示例中,我们使用useState来创建一个名为count的状态变量,并使用useEffect来模拟componentDidUpdate生命周期。useEffect的回调函数会在count更新后执行,并打印更新后的值。这演示了state的值可以随着组件的生命周期而变化,以及如何在不同的生命周期阶段中更新state并执行不同的逻辑。注释帮助解释了每一行代码的作用。

  5. 事件驱动(Event-Driven) :通常,state 的更新是由事件触发的,例如用户点击按钮、输入表单或收到网络请求的响应。state 变化后,React会重新渲染组件,以反映新的state。\

    import React, { useState } from 'react';
    ​
    // 创建一个名为EventDrivenExample的函数组件
    function EventDrivenExample() {
      // 使用useState定义一个名为clickCount的state变量,初始值为0
      const [clickCount, setClickCount] = useState(0);
    ​
      // 定义一个名为handleButtonClick的事件处理函数,用于增加clickCount的值
      const handleButtonClick = () => {
        // 使用setClickCount来改变state的值,将clickCount增加1
        setClickCount(clickCount + 1);
      };
    ​
      return (
        <div>
          {/* 显示当前的clickCount值 */}
          <p>Button Clicks: {clickCount}</p>
          {/* 当按钮被点击时,调用handleButtonClick函数 */}
          <button onClick={handleButtonClick}>Click me</button>
        </div>
      );
    }
    ​
    export default EventDrivenExample;
    

    在这个示例中,我们创建了一个名为EventDrivenExample的函数组件,该组件使用useState来创建一个名为clickCount的状态变量。当用户点击按钮时,会调用handleButtonClick函数,该函数使用setClickCount来更新clickCount的值,实现了事件驱动的state更新。注释帮助解释了每一行代码的作用。

  6. 可传递性(Passing State) :虽然state 是局部的,但你可以将state 通过props 传递给子组件,从而实现数据在组件之间的共享和传递。

    import React, { useState } from 'react';
    ​
    // 创建一个名为ParentComponent的父组件
    function ParentComponent() {
      // 使用useState定义一个名为message的state变量,初始值为'Hello from Parent'
      const [message, setMessage] = useState('Hello from Parent');
    ​
      return (
        <div>
          {/* 渲染ChildComponent,并通过props将message传递给子组件 */}
          <ChildComponent message={message} />
        </div>
      );
    }
    ​
    // 创建一个名为ChildComponent的子组件,接收props作为参数
    function ChildComponent({ message }) {
      return (
        <div>
          {/* 显示从父组件传递过来的message */}
          <p>Message from Parent: {message}</p>
        </div>
      );
    }
    ​
    export default ParentComponent;
    

    在这个示例中,我们创建了一个名为ParentComponent的父组件,它拥有一个messagestate变量。然后,我们渲染了一个名为ChildComponent的子组件,并通过propsmessage传递给子组件。子组件接收到props后,可以在其内部使用传递过来的message值。这演示了如何通过props实现state在父组件和子组件之间的共享和传递。注释帮助解释了每一行代码的作用。

  7. 不可变性(Immutability) :虽然state 是可变的,但在React中,通常不建议直接修改state 的值,而是通过setState 方法来创建新的state。这是为了确保React能够正确地检测state 的变化并进行重新渲染。

    import React, { useState } from 'react';
    ​
    // 创建一个名为ImmutabilityExample的函数组件
    function ImmutabilityExample() {
      // 使用useState定义一个名为user的state变量,初始值为一个包含name属性的对象
      const [user, setUser] = useState({ name: 'Alice' });
    ​
      // 定义一个名为changeName的函数,用于修改user对象的name属性值
      const changeName = () => {
        // 使用setUser来改变state的值,创建一个新的user对象,并修改name属性
        setUser({ ...user, name: 'Bob' });
      };
    ​
      return (
        <div>
          {/* 显示user对象的name属性 */}
          <p>User Name: {user.name}</p>
          {/* 当按钮被点击时,调用changeName函数 */}
          <button onClick={changeName}>Change Name</button>
        </div>
      );
    }
    ​
    export default ImmutabilityExample;
    

    在这个示例中,我们创建了一个名为ImmutabilityExample的函数组件,该组件使用useState来创建一个名为user的状态变量。当用户点击按钮时,会调用changeName函数,该函数使用setUser来更新user对象的name属性。需要注意的是,我们不直接修改user对象的属性值,而是通过创建一个新的user对象并修改其name属性,以确保不可变性。这是为了确保React能够正确地检测state的变化并进行重新渲染。注释帮助解释了每一行代码的作用。

  8. 异步更新

    • React会将多个setState调用合并为一个更新,从而提高性能。这意味着React会尽量减少重新渲染组件的次数,只在需要时进行一次更新,而不是在每次setState调用时都重新渲染。

    • 由于setState是异步的,你不能立即依赖于state的值在更新后立即改变。这可以防止出现不一致的状态,因为React会在内部优化渲染顺序以提高性能。

    • 因为setState是异步的,所以在某些生命周期方法中访问state可能不会得到最新的state值。通常,你应该在componentDidUpdate生命周期方法中执行与state相关的操作,以确保获取到最新的state

    • setState方法可以接受一个回调函数作为参数,在state更新完成后执行。这个回调函数可以用来处理依赖于state`的逻辑,因为它在更新后执行。

    • 下面是一个示例,演示了异步更新的影响:

      import React, { Component } from 'react';
      ​
      class Counter extends Component {
        constructor(props) {
          super(props);
          this.state = {
            count: 0,
          };
        }
      ​
        increment = () => {
          this.setState({ count: this.state.count + 1 }, () => {
            // 回调函数,在state更新后执行
            console.log('Count after increment: ', this.state.count);
          });
          console.log('Count before increment: ', this.state.count);
        };
      ​
        render() {
          return (
            <div>
              <p>Count: {this.state.count}</p>
              <button onClick={this.increment}>Increment</button>
            </div>
          );
        }
      }
      ​
      export default Counter;
      

      在上面的示例中,尽管在setState后有一个回调函数,但在increment方法中的console.log会显示旧的state值,而在回调函数中的console.log会显示更新后的state值。这是异步更新的结果。

      要总结,异步更新是React中state的一个特点,它有助于提高性能和可预测性,但也需要开发者注意如何处理和访问state以避免潜在的问题。

1.3 useState综合案例

react编写一个问卷列表,包含下面内容

  1. 添加问卷,初始状态为未发布
  2. 发布问卷,状态变为已发布
  3. 删除问卷

首先,我们创建一个名为QuestionnaireDetail的新组件,用于显示单个问卷的详细信息,并处理发布和删除操作:

import React from 'react';
​
function QuestionnaireDetail({ questionnaire, onPublish, onDelete }) {
  return (
    <li>
      {questionnaire.title} - {questionnaire.status}
      <button onClick={() => onPublish(questionnaire.id)}>发布</button>
      <button onClick={() => onDelete(questionnaire.id)}>删除</button>
    </li>
  );
}
​
export default QuestionnaireDetail;

然后,我们更新QuestionnaireList组件,引入QuestionnaireDetail组件,并将每个问卷的详细信息传递给它:

import React, { useState } from 'react';
import QuestionnaireDetail from './QuestionnaireDetail'; // 引入QuestionnaireDetail组件function QuestionnaireList() {
  const [questionnaires, setQuestionnaires] = useState([]);
​
  const addQuestionnaire = () => {
    const newQuestionnaire = {
      id: Date.now(),
      title: 'New Questionnaire',
      status: '未发布',
    };
    setQuestionnaires([...questionnaires, newQuestionnaire]);
  };
​
  const publishQuestionnaire = (id) => {
    const updatedQuestionnaires = questionnaires.map((q) => {
      if (q.id === id) {
        return { ...q, status: '已发布' };
      }
      return q;
    });
    setQuestionnaires(updatedQuestionnaires);
  };
​
  const deleteQuestionnaire = (id) => {
    const updatedQuestionnaires = questionnaires.filter((q) => q.id !== id);
    setQuestionnaires(updatedQuestionnaires);
  };
​
  return (
    <div>
      <h1>问卷列表</h1>
      <button onClick={addQuestionnaire}>添加问卷</button>
      <ul>
        {questionnaires.map((questionnaire) => (
          // 将问卷详细信息传递给QuestionnaireDetail组件
          <QuestionnaireDetail
            key={questionnaire.id}
            questionnaire={questionnaire}
            onPublish={publishQuestionnaire}
            onDelete={deleteQuestionnaire}
          />
        ))}
      </ul>
    </div>
  );
}
​
export default QuestionnaireList;

1.4. immer详解

Immer是一个用于管理不可变(immutable)数据的JavaScript库,它可以使不可变数据的操作更加简单和直观。Immer的主要目标是使数据修改操作更具可读性,减少手动深层次克隆对象的复杂性。它在React应用程序中非常有用,尤其是在处理组件的状态更新时。

下面是一个关于Immer的详细案例,以展示如何使用它来更新不可变的数据。

首先,确保你的项目中已经安装了Immer库,你可以使用以下命令进行安装:

npm install immer
# 或者
yarn add immer

接下来,让我们创建一个简单的React组件来演示Immer的用法。假设我们有一个状态对象,表示一个待办事项列表,其中包含了多个待办事项对象。

import React, { useState } from 'react';
import produce from 'immer';
​
function TodoApp() {
  const [todos, setTodos] = useState([
    { id: 1, text: '学习React', completed: false },
    { id: 2, text: '编写示例代码', completed: false },
    { id: 3, text: '测试Immer', completed: false },
  ]);
​
  const toggleTodo = (todoId) => {
    setTodos(
      // 使用Immer的produce函数来处理状态更新
      produce(todos, (draft) => {
        const todo = draft.find((t) => t.id === todoId);
        if (todo) {
          todo.completed = !todo.completed;
        }
      })
    );
  };
​
  return (
    <div>
      <h1>待办事项列表</h1>
      <ul>
        {todos.map((todo) => (
          <li
            key={todo.id}
            onClick={() => toggleTodo(todo.id)}
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          >
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
}
​
export default TodoApp;

在上面的代码中,我们使用了Immer的produce函数来更新todos状态。produce函数接受当前的状态(todos数组)和一个修改函数(回调函数),然后返回一个新的状态。在修改函数中,我们可以像直接修改可变状态一样修改draft(草稿)对象,而不必担心不可变性。Immer会处理底层的不可变性更新。

toggleTodo函数中,我们使用produce来更新todos状态,以切换待办事项的completed属性。这样,我们可以以更直观的方式修改状态,而不必手动进行深层次的对象克隆。

总结一下,Immer是一个用于处理不可变数据的强大工具,特别适用于React中的状态管理。它可以帮助你编写更加简洁和可读的代码,减少手动操作不可变数据的繁琐性。

1.5 useEffect

useEffect 是 React 中的一个重要钩子,用于处理副作用和副作用的清理。副作用通常包括数据获取、订阅、手动DOM操作、设置定时器等。useEffect 允许你在函数组件中执行副作用,并且可以在组件挂载、更新或卸载时触发这些副作用。

useEffect 的基本用法

useEffect 的基本语法如下:

import { useEffect } from 'react';
​
useEffect(() => {
  // 副作用代码
  return () => {
    // 清理副作用(可选)
  };
}, [dependencies]);
  • 副作用代码:在这个函数中,你可以执行任何需要的副作用操作,比如数据获取、订阅、设置定时器等。
  • returnuseEffect 函数可以返回一个清理函数,用于处理副作用的清理工作。清理函数在组件卸载时执行,或者在下一次副作用执行前执行。
  • dependencies:这是一个数组,包含了影响副作用执行的依赖项。如果依赖项数组为空 [],副作用代码只在组件挂载时执行一次。如果不传递依赖项数组,副作用代码在每次组件渲染后都会执行。如果有依赖项,副作用代码会在依赖项发生变化时执行。

商业案例示例

以下是一个实际的商业案例示例,使用 useEffect 来模拟用户订阅一个新闻通知服务并清理订阅:

import React, { useState, useEffect } from 'react';
​
// 定义 newArticle 的类型
interface Article {
  id: number;
  title: string;
}
​
// 创建 NewsSubscription 组件
const NewsSubscription: React.FC = () => {
  const [news, setNews] = useState<Article[]>([]);
  const [subscribed, setSubscribed] = useState<boolean>(false);
​
  useEffect(() => {
    // 模拟订阅新闻通知服务
    const subscription = subscribeToNews((newArticle) => {
      setNews((prevNews) => [...prevNews, newArticle]);
    });
​
    // 更新订阅状态
    setSubscribed(true);
​
    // 清理函数:在组件卸载时取消订阅
    return () => {
      unsubscribeFromNews(subscription);
      setSubscribed(false);
    };
  }, []);
​
  return (
    <div>
      <h1>News Subscription</h1>
      {subscribed ? (
        <div>
          <p>You are subscribed to news updates.</p>
          <ul>
            {news.map((article) => (
              <li key={article.id}>{article.title}</li>
            ))}
          </ul>
        </div>
      ) : (
        <p>Subscribing...</p>
      )}
    </div>
  );
};
​
// 模拟订阅新闻通知的函数
function subscribeToNews(callback: (article: Article) => void) {
  const intervalId = setInterval(() => {
    const newArticle: Article = {
      id: Date.now(),
      title: 'New Article',
    };
    callback(newArticle);
  }, 2000);
  return intervalId;
}
​
// 模拟取消订阅新闻通知的函数
function unsubscribeFromNews(subscription: number) {
  clearInterval(subscription);
}
​
export default NewsSubscription;

在这个案例中,我们创建了一个 NewsSubscription 组件,用于模拟用户订阅新闻通知服务。在 useEffect 中,我们模拟了订阅和清理订阅的过程。

  • 在组件挂载时,我们使用 subscribeToNews 函数模拟订阅新闻通知服务,并在每隔 2 秒钟生成一篇新文章。通过 setNews 更新新闻列表,并设置 subscribed 状态为 true
  • 在组件卸载时,我们使用 unsubscribeFromNews 函数取消订阅,并清理订阅定时器。清理函数会在下一次副作用执行前执行。

这个案例演示了 useEffect 在模拟用户订阅和清理订阅时的实际应用。useEffect 可以帮助管理组件中的副作用,并确保在合适的时机进行清理,以避免内存泄漏和不一致性。

1.6 useRef

React 的 useRef 是一个用于创建 ref 对象的 Hook,它在函数组件中常用于获取或操作 DOM 元素,以及在组件渲染之间存储持久性数据。在本文中,我将详细解释 useRef 的用法,并提供一个实际的商业案例,附带详细注释的代码。

了解 useRef

useRef 返回一个 ref 对象,它有一个 .current 属性,这个属性可以存储任何可变值。ref 对象的特点是,当你修改 .current 属性的值时,组件不会重新渲染。

商业案例:输入框自动聚焦

假设你正在开发一个在线商城的搜索功能。当用户进入搜索页面时,你希望搜索框自动聚焦,以提供更好的用户体验。你可以使用 useRef 来实现这个功能。

以下是一个 React 组件的示例代码,展示了如何使用 useRef 来实现自动聚焦功能,并添加了详细的注释:

import React, { useEffect, useRef } from 'react';
​
function SearchBar() {
  // 创建一个 ref 对象,用于存储输入框的 DOM 元素
  const inputRef = useRef(null);
​
  // 在组件渲染后,自动聚焦输入框
  useEffect(() => {
    // 使用 inputRef.current 获取 DOM 元素
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, []); // 空依赖数组确保只在组件挂载时执行一次return (
    <div>
      <label htmlFor="search">搜索商品:</label>
      {/* 将 ref 对象关联到输入框 */}
      <input id="search" type="text" ref={inputRef} />
      <button>搜索</button>
    </div>
  );
}
​
export default SearchBar;

在这个示例中,我们首先创建了一个 inputRef,并将其初始化为 null。然后,我们使用 useEffect 钩子,在组件挂载后自动聚焦输入框。在 useEffect 中,我们使用 inputRef.current 来获取输入框的 DOM 元素,并调用 focus 方法来使其聚焦。

这是一个简单的商业案例,使用 useRef 来实现了自动聚焦输入框的功能,提高了用户的交互体验。

1.7 useMemo

useMemo 是 React 的一个 Hook,它用于性能优化。它能够在渲染期间缓存计算结果,以避免不必要的重复计算。在本文中,我将详细解释 useMemo 的用法,并提供一个实际的商业案例,附带详细注释的代码。

了解 useMemo

useMemo 接受两个参数:一个函数和一个依赖数组。它会在每次渲染时执行传入的函数,并将结果缓存起来。如果依赖数组中的任何一个值发生变化,那么 useMemo 将重新计算结果。否则,它将返回上一次缓存的结果。

商业案例:计算购物车总价

假设你正在开发一个在线商城的购物车功能。你需要计算购物车中所有商品的总价,并在购物车中显示。为了提高性能,你可以使用 useMemo 来缓存总价的计算结果,只在购物车内容发生变化时重新计算。

以下是一个 React 组件的示例代码,展示了如何使用 useMemo 来计算购物车总价,并添加了详细的注释:

import React, { useState, useMemo } from 'react';
​
// 定义购物车内容
const initialCartItems = [
  { id: 1, name: '商品A', price: 20 },
  { id: 2, name: '商品B', price: 30 },
  { id: 3, name: '商品C', price: 15 },
];
​
function ShoppingCart() {
  // 使用 useState 来管理购物车内容
  const [items, setItems] = useState(initialCartItems);
​
  // 使用 useMemo 缓存计算结果,只有当购物车内容变化时才重新计算
  const totalPrice = useMemo(() => {
    console.log('计算总价'); // 用于演示计算是否被缓存
    return items.reduce((total, item) => total + item.price, 0);
  }, [items]); // 依赖数组包含购物车内容return (
    <div>
      <h2>购物车</h2>
      <ul>
        {items.map((item) => (
          <li key={item.id}>
            {item.name} - ¥{item.price}
          </li>
        ))}
      </ul>
      <p>总价:¥{totalPrice}</p>
    </div>
  );
}
​
export default ShoppingCart;

在这个示例中,我们首先使用 useState 来管理购物车内容。然后,我们使用 useMemo 来缓存计算购物车总价的结果。在 useMemo 的依赖数组中,我们传入了 items,这意味着只有当购物车内容发生变化时,才会重新计算 totalPrice。这可以有效减少不必要的计算,提高性能。

请注意,我在代码中添加了一个 console.log 语句,用于演示计算是否被缓存。你会发现,只有在购物车内容发生变化时,"计算总价" 才会被打印。

这是一个简单的商业案例,使用 useMemo 来优化购物车总价的计算,提高了性能。希望这个示例对你有所帮助,让你更好地理解了 useMemo 的用法和其在实际应用中的作用。如果你有更多问题或需要进一步的解释,请随时提问。

1.8 自定义Hooks

你可以创建一个自定义 Hook 来获取鼠标位置。这个自定义 Hook 将使用 useState 和事件监听来跟踪鼠标的位置,并提供最新的鼠标坐标。

自定义 Hook useMousePosition

import {useState, useEffect} from 'react'
// 获取鼠标位置
function useMousePosition () {
    const [x, setX] = useState(0)
    const [y, setY] = useState(0)
    function updateMousePosition(event: MouseEvent) {
        setX(event.x)
        setY(event.y)
    }
    useEffect(()=> {
        // 监听鼠标事件
        window.addEventListener('mousemove', updateMousePosition)
        return () => {
            window.removeEventListener('mousemove', updateMousePosition)
        }
    },[])
    return {x, y}
}
export default useMousePosition

提供的自定义 Hook useMousePosition 的正确用法示例:

import React from 'react';
import useMousePosition from './useMousePosition'; // 导入自定义 Hookfunction MousePositionDisplay() {
  const { x, y } = useMousePosition();
​
  return (
    <div>
      <h2>鼠标位置</h2>
      <p>X坐标: {x}</p>
      <p>Y坐标: {y}</p>
    </div>
  );
}
​
function App() {
  return (
    <div>
      <h1>鼠标位置示例</h1>
      <MousePositionDisplay />
    </div>
  );
}
​
export default App;

在这个示例中,我们导入了 useMousePosition 自定义 Hook 并在 MousePositionDisplay 组件中使用它,以获取鼠标的 xy 坐标。然后,我们在 App 组件中渲染了 MousePositionDisplay 组件,以展示鼠标位置。