在 React 中创建延迟承诺钩子

2025-06-07

在 React 中创建延迟承诺钩子

各位读者大家好!在这篇文章中,我将演示如何在 React 中创建和使用延迟的 Promise 钩子。欢迎在评论区提问或发表您的意见。
为了更好地理解本文,您需要具备一些关于 Promise 的知识。如果您不了解,请阅读MDN 上的这篇精彩文章
我们开始吧!

原则

jQuery lib 定义的延迟承诺是:

可以将多个回调注册到回调队列、调用回调队列并传递任何同步或异步函数的成功或失败状态的对象。

简单来说,这意味着我们可以存储承诺的回调,例如resolvereject以便稍后使用它们,将操作推迟到完成为止。

用例

让我们想象一下以下场景:

  • 有一个任务列表组件,其中有一个删除按钮
  • 单击删除按钮后,将出现确认对话框
  • 一旦用户确认删除,任务就会被删除,否则不会发生任何事情

以下是这个想法的草稿:

场景草稿打印屏幕显示一个列表,其中包含三个任务以及三个删除按钮

我们可以构建该场景的代码如下:

  • 任务列表组件


type ListProps = {
  allowDelete: () => Promise<boolean>;
};

const data = ['Task 1', 'Task 2', 'Task 3'];

const List = ({ allowDelete }: ListProps) => {
  const [tasks, setTasks] = useState(data);

  const handleRemove = async (task: string) => {
    const canDelete = await allowDelete();
    if (!canDelete) return;

    const newTasks = tasks.filter((innerTask) => innerTask !== task);
    setTasks(newTasks);
  };

  return (
    <ul>
      {tasks.map((task) => (
        <li style={{ marginBottom: 10 }}>
          <span>{task}</span>
          <button style={{ marginLeft: 10 }} onClick={() => handleRemove(task)}>
            Remove
          </button>
        </li>
      ))}
    </ul>
  );
};


Enter fullscreen mode Exit fullscreen mode
  • 确认对话框


type DialogProps = {
  isOpen: boolean;
  handleConfirm: () => void;
  handleClose: () => void;
};

const Dialog = ({ isOpen, handleConfirm, handleClose }: DialogProps) => {
  return (
    <dialog open={isOpen}>
      <div>Do you really want to remove this task?</div>
      <button onClick={handleConfirm}>Yes</button>
      <button onClick={handleClose}>No</button>
    </dialog>
  );
};


Enter fullscreen mode Exit fullscreen mode
  • 应用


const App = () => {
  const [isDialogOpen, setIsDialogOpen] = useState(false);

  const allowDelete = async () => {
    setIsDialogOpen(true);
    return true;
  };

  const handleConfirm = () => {
    setIsDialogOpen(false);
  };

  const handleClose = () => {
    setIsDialogOpen(false);
  };

  return (
    <Fragment>
      <List allowDelete={allowDelete} />
      <Dialog
        isOpen={isDialogOpen}
        handleConfirm={handleConfirm}
        handleClose={handleClose}
      />
    </Fragment>
  );
};


Enter fullscreen mode Exit fullscreen mode

从这个场景来看,很明显列表组件需要等待用户干预才能决定是否可以删除任务。

但是,有一个问题!如果我们运行这段代码,就会遇到一个 bug。一旦用户点击“删除”按钮,任务就会在用户同意之前被删除。

展示代码错误的 Gif。当用户点击某个任务的移除按钮时,该任务会在用户同意之前被立即移除。

延期承诺来拯救

要修复此错误,我们需要告诉代码等待用户同意,这可以通过创建延迟承诺来实现。
我将逐步演示如何创建自定义钩子。

  • 首先,我们将创建一个用于保存defer 对象的类型。该对象必须包含三个属性:一个resolve函数、一个reject函数和一个promise将被执行的 。我们可以在下面注意到,DeferredPromise接收一个泛型类型 ( DeferType),它可以推断出 resolve 的值类型以及 promise 的类型。如果您使用的是纯 JavaScript 而不是 TypeScript,则可以跳过此步骤。


type DeferredPromise<DeferType> = {
  resolve: (value: DeferType) => void;
  reject: (value: unknown) => void;
  promise: Promise<DeferType>;
};


Enter fullscreen mode Exit fullscreen mode
  • 接下来,我们将开始定义钩子函数。这个钩子以一个简单的 ref 开头,用于保存我们的 defer 对象。注意,这个钩子接收的是上面定义的泛型类型。


export function useDeferredPromise<DeferType>() {
  const deferRef = useRef<DeferredPromise<DeferType>>(null);

  return { deferRef: deferRef.current };
}


Enter fullscreen mode Exit fullscreen mode
  • 到目前为止,一切顺利!现在让我们用一个创建延迟对象的函数来增加我们的钩子。首先,我们将构建我们的延迟对象。


// Here is our deferred object that will hold the callbacks and the promise
const deferred = {} as DeferredPromise<DeferType>;

// We then create the main part of our defer object: the promise
// Note that we take the promise's callbacks and inject them into our deferred object
const promise = new Promise<DeferType>((resolve, reject) => {
   deferred.resolve = resolve;
   deferred.reject = reject;
});

// Finally, we inject the whole promise into the deferred object
deferred.promise = promise;


Enter fullscreen mode Exit fullscreen mode
  • 接下来,我们将使用新的延迟对象更新 ref hook。


deferRef.current = deferred;


Enter fullscreen mode Exit fullscreen mode
  • 现在我们有了完整的函数和钩子!来看看吧:


export function useDeferredPromise<DeferType>() {
  const deferRef = useRef<DeferredPromise<DeferType>>(null);

  const defer = () => {
    const deferred = {} as DeferredPromise<DeferType>;

    const promise = new Promise<DeferType>((resolve, reject) => {
      deferred.resolve = resolve;
      deferred.reject = reject;
    });

    deferred.promise = promise;
    deferRef.current = deferred;
    return deferRef.current;
  };

  return { defer, deferRef: deferRef.current };
}


Enter fullscreen mode Exit fullscreen mode
  • 好了!我们的钩子已经完成了。现在让我们用它来解决我们发现的 bug!

使用延迟承诺钩子

让我们修改 Application 组件并添加新的钩子。注意,该allowDelete函数现在返回一个延迟的 Promise,而确认/删除函数会解析这个延迟的 Promise。



const App = () => {
  const [isDialogOpen, setIsDialogOpen] = useState(false);

  // Here we declare the new hook
  // Note that we will resolve this promise using a boolean value (`true` or `false`). This is the generic type that we defined earlier.
  const { defer, deferRef } = useDeferredPromise<boolean>();

  const allowDelete = async () => {
    setIsDialogOpen(true);
    // Now a deferred promise is being returned
    return defer().promise;
  };

  const handleConfirm = () => {
    setIsDialogOpen(false);
    // If the user consents, the deferred promise is resolved with `true`
    deferRef.resolve(true);
  };

  const handleClose = () => {
    setIsDialogOpen(false);
    // If the user declines, the deferred promise is resolved with `false`
    deferRef.resolve(false);
  };

  return (
    <Fragment>
      <List allowDelete={allowDelete} />
      <Dialog
        isOpen={isDialogOpen}
        handleConfirm={handleConfirm}
        handleClose={handleClose}
      />
    </Fragment>
  );
};


Enter fullscreen mode Exit fullscreen mode

现在,如果我们运行这段代码,我们会发现错误已经修复!我们的代码在移除任务之前成功等待了用户同意。如果移除操作被拒绝,则不会发生任何反应,正如预期的那样。

动图显示该错误已修复。如果用户同意,该任务将被移除,否则不发生任何反应。

总结

我们成功地从零开始创建了延迟 Promise 钩子,而且非常简单!
我只是展示了这个钩子可能有用的一个用例,但你可以在任何需要等待某些事情发生后再执行操作的情况下使用它。
这里我还附上了本文中所有代码的链接:https://stackblitz.com/edit/react-ts-sukfgm ?file=index.tsx

只需注意一点:一旦你推迟了一个承诺,永远不要忘记解决或拒绝它,否则你可能会遇到一些内存泄漏问题。

暂时就这些!如有任何疑问,请随时在评论区留言,我会持续关注!

文章来源:https://dev.to/vicnovais/creating-a-deferred-promise-hook-in-react-39jh
PREV
现代 JavaScript 初学者指南 - 第一部分 目录 📦
NEXT
🤝 JavaScript 中的 Promise.allSettled() 与 Promise.all() 🍭