From c8372ed56ac48648c68d4b0387d7c7257b1b0936 Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com> Date: Wed, 19 Jun 2024 09:42:42 +0200 Subject: [PATCH] Interactivity API docs: Add wp-async directives doc (#62663) * Add wp-async directives doc * Changes requested * Add notes to use async and further refine copy * Add links including to async actions section * Add async action example which yields to the main thread --------- Co-authored-by: cbravobernal Co-authored-by: westonruter --- .../interactivity-api/api-reference.md | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/docs/reference-guides/interactivity-api/api-reference.md b/docs/reference-guides/interactivity-api/api-reference.md index dbac92f21f93e..f0a61677bd829 100644 --- a/docs/reference-guides/interactivity-api/api-reference.md +++ b/docs/reference-guides/interactivity-api/api-reference.md @@ -299,6 +299,9 @@ The returned value is used to change the inner content of the element: `
val ### `wp-on` +> [!NOTE] +> Consider using the more performant [`wp-on-async`](#wp-on-async) instead if your directive code does not need synchronous access to the event object. If synchronous access is required, consider implementing an [async action](#async-actions) which yields to the main thread after calling the synchronous API. + This directive runs code on dispatched DOM events like `click` or `keyup`. The syntax is `data-wp-on--[event]` (like `data-wp-on--click` or `data-wp-on--keyup`). ```php @@ -325,8 +328,16 @@ The `wp-on` directive is executed each time the associated event is triggered. The callback passed as the reference receives [the event](https://developer.mozilla.org/en-US/docs/Web/API/Event) (`event`), and the returned value by this callback is ignored. +### `wp-on-async` + +This directive is a more performant approach for `wp-on`. It immediately yields to main to avoid contributing to a long task, allowing other interactions that otherwise would be waiting on the main thread +to run sooner. Use this async version whenever there is no need for synchronous access to the `event` object, in particular the methods `event.preventDefault()`, `event.stopPropagation()`, and `event.stopImmediatePropagation()`. + ### `wp-on-window` +> [!NOTE] +> Consider using the more performant [`wp-on-window-async`](#wp-on-window-async) instead if your directive code does not need synchronous access to the event object. If synchronous access is required, consider implementing an [async action](#async-actions) which yields to the main thread after calling the synchronous API. + This directive allows you to attach global window events like `resize`, `copy`, and `focus` and then execute a defined callback when those happen. [List of supported window events.](https://developer.mozilla.org/en-US/docs/Web/API/Window#events) @@ -354,8 +365,15 @@ store( "myPlugin", { The callback passed as the reference receives [the event](https://developer.mozilla.org/en-US/docs/Web/API/Event) (`event`), and the returned value by this callback is ignored. When the element is removed from the DOM, the event listener is also removed. +### `wp-on-window-async` + +Similar to `wp-on-async`, this is an optimized version of `wp-on-window` that immediately yields to main to avoid contributing to a long task. Use this async version whenever there is no need for synchronous access to the `event` object, in particular the methods `event.preventDefault()`, `event.stopPropagation()`, and `event.stopImmediatePropagation()`. This event listener is also added as [`passive`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#passive). + ### `wp-on-document` +> [!NOTE] +> Consider using the more performant [`wp-on-document-async`](#wp-on-document-async) instead if your directive code does not need synchronous access to the event object. If synchronous access is required, consider implementing an [async action](#async-actions) which yields to the main thread after calling the synchronous API. + This directive allows you to attach global document events like `scroll`, `mousemove`, and `keydown` and then execute a defined callback when those happen. [List of supported document events.](https://developer.mozilla.org/en-US/docs/Web/API/Document#events) @@ -383,6 +401,10 @@ store( "myPlugin", { The callback passed as the reference receives [the event](https://developer.mozilla.org/en-US/docs/Web/API/Event) (`event`), and the returned value by this callback is ignored. When the element is removed from the DOM, the event listener is also removed. +### `wp-on-document-async` + +Similar to `wp-on-async`, this is an optimized version of `wp-on-document` that immediately yields to main to avoid contributing to a long task. Use this async version whenever there is no need for synchronous access to the `event` object, in particular the methods `event.preventDefault()`, `event.stopPropagation()`, and `event.stopImmediatePropagation()`. This event listener is also added as [`passive`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#passive). + ### `wp-watch` It runs a callback **when the node is created and runs it again when the state or context changes**. @@ -772,7 +794,7 @@ We need to be able to know when async actions start awaiting and resume operatio The store will work fine if it is written like this: ```js -store("myPlugin", { +const { state } = store("myPlugin", { state: { get isOpen() { return getContext().isOpen; @@ -788,6 +810,27 @@ store("myPlugin", { }); ``` +As mentioned above with [`wp-on`](#wp-on), [`wp-on-window`](#wp-on-window), and [`wp-on-document`](#wp-on-document), an async action should be used whenever the `async` versions of the aforementioned directives cannot be used due to the action requiring synchronous access to the `event` object. Synchronous access is reqired whenever the action needs to call `event.preventDefault()`, `event.stopPropagation()`, or `event.stopImmediatePropagation()`. To ensure that the action code does not contribute to a long task, you may manually yield to the main thread after calling the synchronous event API. For example: + +```js +function toMainThread() { + return new Promise(resolve => { + setTimeout(resolve, 0); + }); +} + +store("myPlugin", { + actions: { + handleClick: function* (event) { + event.preventDefault(); + yield toMainThread(); + doTheWork(); + }, + }, +}); +``` + +You may want to add multiple such `yield` points in your action if it is doing a lot of work. #### Side Effects