Add Effect v4 support, Fast Refresh tooling, and revamped docs #56
@@ -271,20 +271,76 @@ Effect View hooks are still React hooks internally. Call them unconditionally
|
|||||||
at the top level of the component body, in a consistent order, and never inside
|
at the top level of the component body, in a consistent order, and never inside
|
||||||
branches, loops, event handlers, or nested callbacks.
|
branches, loops, event handlers, or nested callbacks.
|
||||||
|
|
||||||
|
### The component root scope
|
||||||
|
|
||||||
|
Every rendered Effect View component gets a root `Scope.Scope`. Effect View
|
||||||
|
creates it when the component instance is rendered, provides it to the entire
|
||||||
|
component body, and closes it when that React component unmounts.
|
||||||
|
|
||||||
|
Effects yielded directly from the body therefore see the root scope. This also
|
||||||
|
means that `Effect.addFinalizer`, `Effect.acquireRelease`, and
|
||||||
|
`Effect.forkScoped` can be used naturally inside component setup:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Effect } from "effect"
|
||||||
|
import { Component } from "effect-view"
|
||||||
|
|
||||||
|
const ResourceView = Component.make("Resource")(function* () {
|
||||||
|
const resource = yield* Component.useOnMount(() =>
|
||||||
|
Effect.acquireRelease(
|
||||||
|
openResource,
|
||||||
|
(resource) => closeResource(resource),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return <p>{resource.name}</p>
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
`useOnMount` does **not** create or provide another scope. It runs its Effect
|
||||||
|
with the context already available in the component body, so the resource above
|
||||||
|
is owned by the component's root scope.
|
||||||
|
|
||||||
|
Other lifecycle hooks need a cleanup boundary narrower than the whole
|
||||||
|
component, so they create a new scope and provide it to their setup Effect:
|
||||||
|
|
||||||
|
```text
|
||||||
|
React component instance
|
||||||
|
└── Component root scope
|
||||||
|
├── Component body uses the root scope
|
||||||
|
├── useOnMount setup uses the root scope
|
||||||
|
├── useOnChange setup for dependencies A gets its own scope
|
||||||
|
├── useReactEffect setup gets its own scope
|
||||||
|
└── useReactLayoutEffect setup gets its own scope
|
||||||
|
```
|
||||||
|
|
||||||
|
The narrower scopes are managed by their hooks rather than being implicit
|
||||||
|
Effect child scopes. React unmounting closes all of them; dependency changes
|
||||||
|
can close and replace a hook scope without closing the component root scope.
|
||||||
|
|
||||||
The main lifecycle choices are:
|
The main lifecycle choices are:
|
||||||
|
|
||||||
| Need | Hook | When setup runs |
|
| Hook | Scope seen by setup | Scope closes when |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Compute and cache a value for this component instance | `useOnMount` | During the initial render |
|
| Component body | Component root scope | The component unmounts |
|
||||||
| Recompute a value when dependencies change | `useOnChange` | During render when dependencies change |
|
| `useOnMount` | The same component root scope | The component unmounts |
|
||||||
| Run a post-commit side effect | `useReactEffect` | After React commits |
|
| `useOnChange` | A new dependency scope | Dependencies change or the component unmounts |
|
||||||
| Measure or update layout before paint | `useReactLayoutEffect` | After DOM mutation, before paint |
|
| `useReactEffect` | A new effect scope | Dependencies change or the component unmounts |
|
||||||
|
| `useReactLayoutEffect` | A new layout-effect scope | Dependencies change or the component unmounts |
|
||||||
|
| `useLayer` | A dependency scope created through `useOnChange` | The layer reference changes or the component unmounts |
|
||||||
|
|
||||||
|
`useRunSync`, `useRunPromise`, `useCallbackSync`, and `useCallbackPromise` do
|
||||||
|
not create lifecycle scopes either. They capture the component context, so an
|
||||||
|
Effect invoked through them sees the component root scope unless it explicitly
|
||||||
|
provides another one.
|
||||||
|
|
||||||
### useOnMount
|
### useOnMount
|
||||||
|
|
||||||
`Component.useOnMount` computes a value during the component's initial render
|
`Component.useOnMount` computes a value during the component's initial render
|
||||||
and returns the cached value on later renders. It receives the component scope,
|
and returns the cached value on later renders. It does not install a scope of
|
||||||
so acquired resources and forked fibers can be tied to the component lifetime:
|
its own: its Effect inherits the root scope provided to the component body.
|
||||||
|
Acquired resources and forked fibers are therefore tied to the full component
|
||||||
|
lifetime:
|
||||||
|
|
||||||
```tsx title="src/LocalStateView.tsx"
|
```tsx title="src/LocalStateView.tsx"
|
||||||
import { Effect, SubscriptionRef } from "effect"
|
import { Effect, SubscriptionRef } from "effect"
|
||||||
@@ -317,8 +373,9 @@ asynchronous and will suspend the component.
|
|||||||
### useOnChange
|
### useOnChange
|
||||||
|
|
||||||
`Component.useOnChange` has the same render-time constraint, but owns a scope
|
`Component.useOnChange` has the same render-time constraint, but owns a scope
|
||||||
for one dependency window. When a dependency changes, the previous scope is
|
for one dependency window. It creates that scope and provides it to the setup
|
||||||
closed and a new value is computed:
|
Effect instead of the component root scope. When a dependency changes, the
|
||||||
|
previous dependency scope is closed and a new value is computed:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
const label = yield* Component.useOnChange(
|
const label = yield* Component.useOnChange(
|
||||||
@@ -340,8 +397,9 @@ read by the setup Effect.
|
|||||||
### useReactEffect and useReactLayoutEffect
|
### useReactEffect and useReactLayoutEffect
|
||||||
|
|
||||||
Use `Component.useReactEffect` for work that should start only after React has
|
Use `Component.useReactEffect` for work that should start only after React has
|
||||||
committed the render. Setup must complete synchronously, but it can fork
|
committed the render. The hook creates a fresh scope, provides it to the setup
|
||||||
asynchronous work into the provided scope:
|
Effect, and closes it through React effect cleanup. Setup must complete
|
||||||
|
synchronously, but it can fork asynchronous work into that scope:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
yield* Component.useReactEffect(
|
yield* Component.useReactEffect(
|
||||||
@@ -355,11 +413,15 @@ and runs registered finalizers. Use `Component.useReactLayoutEffect` for the
|
|||||||
same ownership model when setup must happen before the browser paints, such as
|
same ownership model when setup must happen before the browser paints, such as
|
||||||
DOM measurement.
|
DOM measurement.
|
||||||
|
|
||||||
Component and dependency scopes debounce finalizer execution by 100
|
The component root scope and scopes created by `useOnChange` debounce finalizer
|
||||||
milliseconds by default. This reduces unnecessary teardown during rapid
|
execution by 100 milliseconds by default. This reduces unnecessary teardown
|
||||||
unmount/remount cycles, including development behavior. Configure
|
during rapid unmount/remount cycles, including development behavior. Configure
|
||||||
`finalizerExecutionDebounce` through `Component.withOptions` or the relevant
|
the root through `Component.withOptions({ finalizerExecutionDebounce: ... })`,
|
||||||
hook options when cleanup timing must differ.
|
or pass `finalizerExecutionDebounce` in the `useOnChange` options.
|
||||||
|
|
||||||
|
`useReactEffect` and `useReactLayoutEffect` instead close their scopes directly
|
||||||
|
from React cleanup. Their `finalizerExecutionMode` option controls whether that
|
||||||
|
close runs synchronously or is forked; it defaults to `"fork"`.
|
||||||
|
|
||||||
React Strict Mode may intentionally repeat development-only render and effect
|
React Strict Mode may intentionally repeat development-only render and effect
|
||||||
setup. Initializers and resource acquisition should therefore be safe to run
|
setup. Initializers and resource acquisition should therefore be safe to run
|
||||||
|
|||||||
Reference in New Issue
Block a user