ReactJS - 自定义 Hook



Hooks 是函数组件不可分割的一部分。它们可以用来增强函数组件的功能。React 提供了一些内置 Hook。尽管内置 Hook 功能强大,可以实现任何功能,但专用 Hook 仍然是必要的,并且需求量很大。

React 了解了开发者的这种需求,并允许通过现有的 Hook 创建新的自定义 Hook。开发者可以从函数组件中提取特殊的功能,并将其创建为一个单独的 Hook,可以在任何函数组件中使用。

让我们在本节学习如何创建自定义 Hook。

创建自定义 Hook

让我们创建一个具有无限滚动功能的新 React 函数组件,然后从函数组件中提取无限滚动功能并创建一个自定义 Hook。创建自定义 Hook 后,我们将尝试更改原始函数组件以使用我们的自定义 Hook。

实现无限滚动功能

组件的基本功能是简单地通过生成虚拟待办事项列表来显示它。当用户滚动时,组件将生成一组新的虚拟待办事项列表并将其附加到现有列表。

首先,创建一个新的 React 应用,并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,在组件文件夹下(src/components/TodoList.js)创建一个 React 组件 TodoList。

function TodoList() {
   return <div>Todo List</div>
}
export default TodoList

接下来,更新根组件 App.js 以使用新创建的 TodoList 组件。

import TodoList from './components/TodoList';
function App() {
   return (
      <div style={{ padding: "5px"}}>
         <TodoList />
      </div>
   );
}
export default App;

接下来,创建一个计数状态变量来维护要生成的待办事项数量。

const [count, setCount] = useState(100)

这里:

  • 初始要生成的项目数量为 100

  • count 是用于维护待办事项数量的状态变量

接下来,创建一个数据数组来维护生成的虚拟待办事项,并使用 useMemo Hook 保留引用。

React.useMemo(() => {
   for (let i = 1; i <= count; i++) {
      data.push({
         id: i,
         todo: "Todo Item " + i.toString()
      })
   }
}, [count]);

这里:

  • 使用 useMemo 来限制每次渲染时待办事项的生成。

  • 使用 count 作为依赖项,以便在计数更改时重新生成待办事项

接下来,将处理程序附加到滚动事件,并在用户移动到页面末尾时更新要生成的待办事项计数。

React.useEffect(() => {
   function handleScroll() {
      const isBottom =
         window.innerHeight + document.documentElement.scrollTop
         === document.documentElement.offsetHeight;
      if (isBottom) {
         setCount(count + 100)
      }
   }
   window.addEventListener("scroll", handleScroll);
   return () => {
      window.removeEventListener("scroll", handleScroll);
   };
}, [])

这里我们有:

  • 使用 useEffect Hook 确保 DOM 已准备好附加事件。

  • 为滚动事件附加了一个事件处理程序 scrollHandler

  • 当组件从应用程序卸载时,删除事件处理程序

  • 在滚动事件处理程序中使用 DOM 属性查找用户是否到达页面底部。

  • 一旦用户到达页面底部,计数就会增加 100

  • 一旦计数状态变量更改,组件就会重新渲染,并且会加载更多待办事项。

最后,按如下所示渲染待办事项:

<div>
   <p>List of Todo list</p>
   <ul>
      {data && data.map((item) =>
         <li key={item.id}>{item.todo}</li>
      )}
   </ul>
</div>

组件的完整源代码如下:

import React, { useState } from "react"
function TodoList() {
   const [count, setCount] = useState(100)
   let data = []
   
   React.useMemo(() => {
      for (let i = 1; i <= count; i++) {
         data.push({
            id: i,
            todo: "Todo Item " + i.toString()
         })
      }
   }, [count]);
   
   React.useEffect(() => {
      function handleScroll() {
         const isBottom =
            window.innerHeight + document.documentElement.scrollTop
            === document.documentElement.offsetHeight;
         
         if (isBottom) {
            setCount(count + 100)
         }
      }
      window.addEventListener("scroll", handleScroll);
      return () => {
         window.removeEventListener("scroll", handleScroll);
      };
   }, [])
   return (
      <div>
         <p>List of Todo list</p>
         <ul>
            {data && data.map((item) =>
               <li key={item.id}>{item.todo}</li>
            )}
         </ul>
      </div>
   )
}
export default TodoList

接下来,打开浏览器并运行应用程序。应用程序将在用户到达页面末尾时附加新的待办事项,并无限期地继续,如下所示:

Implement Infinite Scroll Functionality

实现useInfiniteScroll Hook

接下来,让我们尝试通过从现有组件中提取逻辑来创建新的自定义 Hook,然后在单独的组件中使用它。创建一个新的自定义 Hook,useInfiniteScroll (src/Hooks/useInfiniteScroll.js),如下所示

import React from "react";
export default function useInfiniteScroll(loadDataFn) {
   const [bottom, setBottom] = React.useState(false);
   React.useEffect(() => {
      window.addEventListener("scroll", handleScroll);
      return () => {
         window.removeEventListener("scroll", handleScroll);
      };
   }, []);
   React.useEffect(() => {
      if (!bottom) return;
      loadDataFn();
   }, [bottom]);
   
   function handleScroll() {
      if (window.innerHeight + document.documentElement.scrollTop
         != document.documentElement.offsetHeight) {
         return;
      }
      setBottom(true)
   }
   return [bottom, setBottom];
}

这里我们有:

  • 使用use关键字命名自定义 Hook。这是使用use关键字作为自定义 Hook 名称开头的惯例,它也是 React 的提示,提示该函数是一个自定义 Hook

  • 使用状态变量 bottom 来了解用户是否到达页面底部。

  • 使用 useEffect 在 DOM 可用后注册滚动事件

  • 使用泛型函数 loadDataFn 在组件中创建 Hook 时提供。这将能够为加载数据创建自定义逻辑。

  • 在滚动事件处理程序中使用 DOM 属性来跟踪用户滚动位置。当用户到达页面底部时,bottom 状态变量会发生变化。

  • 返回 bottom 状态变量的当前值 (bottom) 和用于更新 bottom 状态变量的函数 (setBottom)

接下来,创建一个新组件 TodoListUsingCustomHook 来应用新创建的 Hook。

function TodoListUsingCustomHook() {
   return <div>Todo List</div>
}
export default TodoListUsingCustomHook

接下来,更新根组件 App.js 以使用新创建的 TodoListUsingCustomHook 组件。

import TodoListUsingCustomHook from './components/TodoListUsingCustomHook';
function App() {
   return (
      <div style={{ padding: "5px"}}>
         <TodoListUsingCustomHook />
      </div>
   );
}
export default App;

接下来,创建一个状态变量 data 来管理待办事项列表。

const [data, setData] = useState([])

接下来,创建一个状态变量 count 来管理要生成的待办事项列表的数量。

const [data, setData] = useState([])

接下来,应用无限滚动自定义 Hook。

const [bottom, setBottom] = useInfiniteScroll(loadMoreData)

这里,bottomsetBottomuseInfiniteScroll Hook 公开。

接下来,创建用于生成待办事项列表的函数 loadMoreData

function loadMoreData() {
   let data = []
   for (let i = 1; i <= count; i++) {
      data.push({
         id: i,
         todo: "Todo Item " + i.toString()
      })
   }
   setData(data)
   setCount(count + 100)
   setBottom(false)
}

这里:

  • 根据计数状态变量生成待办事项

  • 将计数状态变量增加 100

  • 使用 setData 函数将生成的待办事项列表设置为 data 状态变量

  • 使用 setBottom 函数将 bottom 状态变量设置为 false

接下来,生成初始待办事项,如下所示:

const loadData = () => {
   let data = []
   for (let i = 1; i <= count; i++) {
      data.push({
         id: i,
         todo: "Todo Item " + i.toString()
      })
   }
   setData(data)
}
React.useEffect(() => {
   loadData(count)
}, [])

这里:

  • 初始待办事项是根据初始计数状态变量生成的

  • 使用 setData 函数将生成的待办事项列表设置为 data 状态变量

  • 使用 useEffect 确保在 DOM 可用后生成数据

最后,按如下所示渲染待办事项:

<div>
   <p>List of Todo list</p>
   <ul>
      {data && data.map((item) =>
         <li key={item.id}>{item.todo}</li>
      )}
   </ul>
</div>

组件的完整源代码如下所示:

import React, { useState } from "react"
import useInfiniteScroll from '../Hooks/useInfiniteScroll'

function TodoListUsingCustomHook() {
   const [data, setData] = useState([])
   const [count, setCount] = useState(100)
   const [bottom, setBottom] = useInfiniteScroll(loadMoreData)
   
   const loadData = () => {
      let data = []
      for (let i = 1; i <= count; i++) {
         data.push({
            id: i,
            todo: "Todo Item " + i.toString()
         })
      }
      setData(data)
   }
  
  function loadMoreData() {
      let data = []
      
      for (let i = 1; i <= count; i++) {
         data.push({
            id: i,
            todo: "Todo Item " + i.toString()
         })
      }
      setData(data)
      setCount(count + 100)
      setBottom(false)
   }
   React.useEffect(() => {
      loadData(count)
   }, [])
   return (
      <div>
         <p>List of Todo list</p>
         <ul>
            {data && data.map((item) =>
               <li key={item.id}>{item.todo}</li>
            )}
         </ul>
      </div>
   )
}
export default TodoListUsingCustomHook

最后,打开浏览器并检查输出。应用程序将在用户到达页面末尾时附加新的待办事项,并无限期地继续,如下所示:

Implementing UseInfiniteScroll Hook

行为与 TodoList 组件相同。我们已成功地从组件中提取逻辑并将其用于创建我们的第一个自定义 Hook。现在,可以在任何应用程序中使用自定义 Hook。

总结

自定义 Hook 是一种现有的功能,用于将可重用的逻辑与函数组件分离,并允许在不同的函数组件中使其成为真正可重用的 Hook。

广告