This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
import { Context, Effect, type Equivalence, ExecutionStrategy, Function, pipe, Pipeable, Predicate, Runtime, Scope, String, Tracer, type Utils } from "effect"
|
import { Context, Effect, ExecutionStrategy, Function, pipe, Pipeable, Predicate, Runtime, Scope, String, Tracer, type Utils } from "effect"
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as Hook from "./Hook.js"
|
import * as Hook from "./Hook.js"
|
||||||
import type { ExcludeKeys } from "./utils.js"
|
import * as Memoized from "./Memoized.js"
|
||||||
|
import * as Suspense from "./Suspense.js"
|
||||||
|
|
||||||
|
|
||||||
export const TypeId: unique symbol = Symbol.for("effect-fc/Component")
|
export const TypeId: unique symbol = Symbol.for("effect-fc/Component")
|
||||||
@@ -339,93 +340,15 @@ export const withOptions: {
|
|||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
export interface Memoized<P> {
|
|
||||||
readonly memo: true
|
|
||||||
readonly memoOptions: Memoized.Options<P>
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace Memoized {
|
|
||||||
export interface Options<P> {
|
|
||||||
readonly propsAreEqual?: Equivalence.Equivalence<P>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const memo = <T extends Component<any, any, any> | Component<any, any, any> & Memoized<any>>(
|
|
||||||
self: T
|
|
||||||
): T & Memoized<Component.Props<T>> => Object.setPrototypeOf({
|
|
||||||
...self,
|
|
||||||
memo: true,
|
|
||||||
memoOptions: Predicate.hasProperty(self, "memo") ? { ...self.memoOptions } : {},
|
|
||||||
}, Object.getPrototypeOf(self))
|
|
||||||
|
|
||||||
export const withMemoOptions: {
|
|
||||||
<T extends Component<any, any, any> & Memoized<any>>(
|
|
||||||
memoOptions: Partial<Memoized.Options<Component.Props<T>>>
|
|
||||||
): (self: T) => T
|
|
||||||
<T extends Component<any, any, any> & Memoized<any>>(
|
|
||||||
self: T,
|
|
||||||
memoOptions: Partial<Memoized.Options<Component.Props<T>>>,
|
|
||||||
): T
|
|
||||||
} = Function.dual(2, <T extends Component<any, any, any> & Memoized<any>>(
|
|
||||||
self: T,
|
|
||||||
memoOptions: Partial<Memoized.Options<Component.Props<T>>>,
|
|
||||||
): T => Object.setPrototypeOf(
|
|
||||||
{ ...self, memoOptions: { ...self.memoOptions, ...memoOptions } },
|
|
||||||
Object.getPrototypeOf(self),
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
export interface Suspense {
|
|
||||||
readonly suspense: true
|
|
||||||
readonly suspenseOptions: Suspense.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace Suspense {
|
|
||||||
export interface Options {
|
|
||||||
readonly defaultFallback?: React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Props = Omit<React.SuspenseProps, "children">
|
|
||||||
}
|
|
||||||
|
|
||||||
export const suspense = <T extends Component<any, any, P> | Component<any, any, P> & Suspense, P extends {}>(
|
|
||||||
self: T & Component<any, any, ExcludeKeys<P, keyof Suspense.Props>>
|
|
||||||
): (
|
|
||||||
& T
|
|
||||||
& Component<Component.Error<T>, Component.Context<T>, P & Suspense.Props>
|
|
||||||
& Suspense
|
|
||||||
) => Object.setPrototypeOf({
|
|
||||||
...self,
|
|
||||||
suspense: true,
|
|
||||||
suspenseOptions: Predicate.hasProperty(self, "suspense") ? { ...self.suspenseOptions } : {},
|
|
||||||
}, Object.getPrototypeOf(self))
|
|
||||||
|
|
||||||
export const withSuspenseOptions: {
|
|
||||||
<T extends Component<any, any, any> & Suspense>(
|
|
||||||
suspenseOptions: Partial<Suspense.Options>
|
|
||||||
): (self: T) => T
|
|
||||||
<T extends Component<any, any, any> & Suspense>(
|
|
||||||
self: T,
|
|
||||||
suspenseOptions: Partial<Suspense.Options>,
|
|
||||||
): T
|
|
||||||
} = Function.dual(2, <T extends Component<any, any, any> & Suspense>(
|
|
||||||
self: T,
|
|
||||||
suspenseOptions: Partial<Suspense.Options>,
|
|
||||||
): T => Object.setPrototypeOf(
|
|
||||||
{ ...self, suspense: true, suspenseOptions: { ...self.suspenseOptions, ...suspenseOptions } },
|
|
||||||
Object.getPrototypeOf(self),
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
export const useFC: {
|
export const useFC: {
|
||||||
<E, R, P extends {}>(
|
<E, R, P extends {}>(
|
||||||
self: Component<E, R, P> & Suspense
|
self: Component<E, R, P> & Suspense.Suspense
|
||||||
): Effect.Effect<React.FC<P & Suspense.Props>, never, Exclude<R, Scope.Scope>>
|
): Effect.Effect<React.FC<P & Suspense.Suspense.Props>, never, Exclude<R, Scope.Scope>>
|
||||||
<E, R, P extends {}>(
|
<E, R, P extends {}>(
|
||||||
self: Component<E, R, P>
|
self: Component<E, R, P>
|
||||||
): Effect.Effect<React.FC<P>, never, Exclude<R, Scope.Scope>>
|
): Effect.Effect<React.FC<P>, never, Exclude<R, Scope.Scope>>
|
||||||
} = Effect.fn("useFC")(function* <E, R, P extends {}>(
|
} = Effect.fn("useFC")(function* <E, R, P extends {}>(
|
||||||
self: Component<E, R, P> & (Memoized<P> | Suspense | {})
|
self: Component<E, R, P> & (Memoized.Memoized<P> | Suspense.Suspense | {})
|
||||||
) {
|
) {
|
||||||
const runtimeRef = React.useRef<Runtime.Runtime<Exclude<R, Scope.Scope>>>(null!)
|
const runtimeRef = React.useRef<Runtime.Runtime<Exclude<R, Scope.Scope>>>(null!)
|
||||||
runtimeRef.current = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
|
runtimeRef.current = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
|
||||||
@@ -439,20 +362,20 @@ export const useFC: {
|
|||||||
))
|
))
|
||||||
|
|
||||||
const FC = React.useMemo(() => {
|
const FC = React.useMemo(() => {
|
||||||
const f: React.FC<P> = Predicate.hasProperty(self, "suspense")
|
const f: React.FC<P> = Suspense.isSuspense(self)
|
||||||
? pipe(
|
? pipe(
|
||||||
function SuspenseInner(props: { readonly promise: Promise<React.ReactNode> }) {
|
function SuspenseInner(props: { readonly promise: Promise<React.ReactNode> }) {
|
||||||
return React.use(props.promise)
|
return React.use(props.promise)
|
||||||
},
|
},
|
||||||
|
|
||||||
SuspenseInner => ({ fallback, name, ...props }: P & Suspense.Props) => {
|
SuspenseInner => ({ fallback, name, ...props }: P & Suspense.Suspense.Props) => {
|
||||||
const promise = Runtime.runPromise(runtimeRef.current)(
|
const promise = Runtime.runPromise(runtimeRef.current)(
|
||||||
Effect.provideService(self.body(props as P), Scope.Scope, scope)
|
Effect.provideService(self.body(props as P), Scope.Scope, scope)
|
||||||
)
|
)
|
||||||
|
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
React.Suspense,
|
React.Suspense,
|
||||||
{ fallback: fallback ?? self.suspenseOptions.defaultFallback, name },
|
{ fallback: fallback ?? self.defaultFallback, name },
|
||||||
React.createElement(SuspenseInner, { promise }),
|
React.createElement(SuspenseInner, { promise }),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -462,8 +385,8 @@ export const useFC: {
|
|||||||
)
|
)
|
||||||
|
|
||||||
f.displayName = self.displayName ?? "Anonymous"
|
f.displayName = self.displayName ?? "Anonymous"
|
||||||
return Predicate.hasProperty(self, "memo")
|
return Memoized.isMemoized(self)
|
||||||
? React.memo(f, self.memoOptions.propsAreEqual)
|
? React.memo(f, self.propsAreEqual)
|
||||||
: f
|
: f
|
||||||
}, [scope])
|
}, [scope])
|
||||||
|
|
||||||
@@ -473,8 +396,8 @@ export const useFC: {
|
|||||||
|
|
||||||
export const use: {
|
export const use: {
|
||||||
<E, R, P extends {}>(
|
<E, R, P extends {}>(
|
||||||
self: Component<E, R, P> & Suspense,
|
self: Component<E, R, P> & Suspense.Suspense,
|
||||||
fn: (Component: React.FC<P & Suspense.Props>) => React.ReactNode,
|
fn: (Component: React.FC<P & Suspense.Suspense.Props>) => React.ReactNode,
|
||||||
): Effect.Effect<React.ReactNode, never, Exclude<R, Scope.Scope>>
|
): Effect.Effect<React.ReactNode, never, Exclude<R, Scope.Scope>>
|
||||||
<E, R, P extends {}>(
|
<E, R, P extends {}>(
|
||||||
self: Component<E, R, P>,
|
self: Component<E, R, P>,
|
||||||
@@ -487,14 +410,14 @@ export const use: {
|
|||||||
export const withRuntime: {
|
export const withRuntime: {
|
||||||
<T extends Component<any, R, any>, R>(
|
<T extends Component<any, R, any>, R>(
|
||||||
context: React.Context<Runtime.Runtime<R>>,
|
context: React.Context<Runtime.Runtime<R>>,
|
||||||
): (self: T) => React.FC<T extends Suspense
|
): (self: T) => React.FC<T extends Suspense.Suspense
|
||||||
? Component.Props<T> & Suspense.Props
|
? Component.Props<T> & Suspense.Suspense.Props
|
||||||
: Component.Props<T>
|
: Component.Props<T>
|
||||||
>
|
>
|
||||||
<E, R, P extends {}>(
|
<E, R, P extends {}>(
|
||||||
self: Component<E, R, P> & Suspense,
|
self: Component<E, R, P> & Suspense.Suspense,
|
||||||
context: React.Context<Runtime.Runtime<R>>,
|
context: React.Context<Runtime.Runtime<R>>,
|
||||||
): React.FC<P & Suspense.Props>
|
): React.FC<P & Suspense.Suspense.Props>
|
||||||
<E, R, P extends {}>(
|
<E, R, P extends {}>(
|
||||||
self: Component<E, R, P>,
|
self: Component<E, R, P>,
|
||||||
context: React.Context<Runtime.Runtime<R>>,
|
context: React.Context<Runtime.Runtime<R>>,
|
||||||
|
|||||||
42
packages/effect-fc/src/Memoized.ts
Normal file
42
packages/effect-fc/src/Memoized.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { type Equivalence, Function, Predicate } from "effect"
|
||||||
|
import type * as Component from "./Component.js"
|
||||||
|
|
||||||
|
|
||||||
|
export const TypeId: unique symbol = Symbol.for("effect-fc/Memoized")
|
||||||
|
export type TypeId = typeof TypeId
|
||||||
|
|
||||||
|
export interface Memoized<P> extends Memoized.Options<P> {
|
||||||
|
readonly [TypeId]: true
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Memoized {
|
||||||
|
export interface Options<P> {
|
||||||
|
readonly propsAreEqual?: Equivalence.Equivalence<P>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const isMemoized = (u: unknown): u is Memoized<any> => Predicate.hasProperty(u, TypeId)
|
||||||
|
|
||||||
|
export const memo = <T extends Component.Component<any, any, any>>(
|
||||||
|
self: T
|
||||||
|
): T & Memoized<Component.Component.Props<T>> => Object.setPrototypeOf(
|
||||||
|
{ ...self, [TypeId]: true },
|
||||||
|
Object.getPrototypeOf(self),
|
||||||
|
)
|
||||||
|
|
||||||
|
export const withOptions: {
|
||||||
|
<T extends Component.Component<any, any, any> & Memoized<any>>(
|
||||||
|
options: Partial<Memoized.Options<Component.Component.Props<T>>>
|
||||||
|
): (self: T) => T
|
||||||
|
<T extends Component.Component<any, any, any> & Memoized<any>>(
|
||||||
|
self: T,
|
||||||
|
options: Partial<Memoized.Options<Component.Component.Props<T>>>,
|
||||||
|
): T
|
||||||
|
} = Function.dual(2, <T extends Component.Component<any, any, any> & Memoized<any>>(
|
||||||
|
self: T,
|
||||||
|
options: Partial<Memoized.Options<Component.Component.Props<T>>>,
|
||||||
|
): T => Object.setPrototypeOf(
|
||||||
|
{ ...self, ...options },
|
||||||
|
Object.getPrototypeOf(self),
|
||||||
|
))
|
||||||
49
packages/effect-fc/src/Suspense.ts
Normal file
49
packages/effect-fc/src/Suspense.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { Function, Predicate } from "effect"
|
||||||
|
import type * as Component from "./Component.js"
|
||||||
|
import type { ExcludeKeys } from "./utils.js"
|
||||||
|
|
||||||
|
|
||||||
|
export const TypeId: unique symbol = Symbol.for("effect-fc/Suspense")
|
||||||
|
export type TypeId = typeof TypeId
|
||||||
|
|
||||||
|
export interface Suspense extends Suspense.Options {
|
||||||
|
readonly [TypeId]: true
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Suspense {
|
||||||
|
export interface Options {
|
||||||
|
readonly defaultFallback?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Props = Omit<React.SuspenseProps, "children">
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const isSuspense = (u: unknown): u is Suspense => Predicate.hasProperty(u, TypeId)
|
||||||
|
|
||||||
|
export const suspense = <T extends Component.Component<any, any, P>, P extends {}>(
|
||||||
|
self: T & Component.Component<any, any, ExcludeKeys<P, keyof Suspense.Props>>
|
||||||
|
): (
|
||||||
|
& T
|
||||||
|
& Component.Component<Component.Component.Error<T>, Component.Component.Context<T>, P & Suspense.Props>
|
||||||
|
& Suspense
|
||||||
|
) => Object.setPrototypeOf(
|
||||||
|
{ ...self, [TypeId]: true },
|
||||||
|
Object.getPrototypeOf(self),
|
||||||
|
)
|
||||||
|
|
||||||
|
export const withOptions: {
|
||||||
|
<T extends Component.Component<any, any, any> & Suspense>(
|
||||||
|
options: Partial<Suspense.Options>
|
||||||
|
): (self: T) => T
|
||||||
|
<T extends Component.Component<any, any, any> & Suspense>(
|
||||||
|
self: T,
|
||||||
|
options: Partial<Suspense.Options>,
|
||||||
|
): T
|
||||||
|
} = Function.dual(2, <T extends Component.Component<any, any, any> & Suspense>(
|
||||||
|
self: T,
|
||||||
|
options: Partial<Suspense.Options>,
|
||||||
|
): T => Object.setPrototypeOf(
|
||||||
|
{ ...self, ...options },
|
||||||
|
Object.getPrototypeOf(self),
|
||||||
|
))
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
export * as Component from "./Component.js"
|
export * as Component from "./Component.js"
|
||||||
export * as Hook from "./Hook.js"
|
export * as Hook from "./Hook.js"
|
||||||
|
export * as Memoized from "./Memoized.js"
|
||||||
export * as ReactManagedRuntime from "./ReactManagedRuntime.js"
|
export * as ReactManagedRuntime from "./ReactManagedRuntime.js"
|
||||||
|
export * as Suspense from "./Suspense.js"
|
||||||
|
|||||||
Reference in New Issue
Block a user