-
-
Notifications
You must be signed in to change notification settings - Fork 0
Define And Components
define({ name?, props?, layers?, script, template })Status: Current.
define creates an Effuse component blueprint. script runs when the component
state is created. Its returned object becomes the template context together with
reactive props and children.
const Greeting = define({
name: 'Greeting',
props: defineProps<{ name: string }>(),
script({ props, computed }) {
const label = computed(() => `Hello, ${props.name}`);
return { label };
},
template: ({ label }) => <h1>{label}</h1>,
});Props are reactive and are updated in place during reconciliation. Do not copy a prop into an ordinary variable when later prop changes should remain visible.
Use defineProps(schema) when props need defaults, runtime validation, nested
structures, or input transforms. The schema produces two synchronized types:
- Caller input: required fields must be supplied; optional and defaulted fields may be omitted; transformed fields use their encoded input type.
-
Resolved props:
scriptandtemplatereceive decoded values with defaults applied.
const userProps = PropSchema.struct({
count: PropSchema.required(PropSchema.NumberFromString),
name: PropSchema.required(PropSchema.String),
role: PropSchema.optional(PropSchema.String, 'member'),
});
const UserLabel = define({
props: defineProps(userProps),
script: ({ props }) => ({
// count is number and role is string after schema resolution.
label: `${props.name}:${props.role}:${props.count}`,
}),
template: ({ label }) => <span>{label}</span>,
});
UserLabel({ name: 'Ada', count: '3' });Prop schemas are an Effuse API. Applications do not import Effect or depend on
its schema vocabulary. Use Effuse value builders such as String, Number,
Boolean, NumberFromString, BooleanFromString, DateFromString, literal,
union, array, and object; the validation engine remains internal.
Nested builders retain their own presence rules. An optional nested object may
be omitted, while required fields inside it remain required when the object is
provided. Invalid initial props and updates throw PropsValidationError; a
failed update leaves the previous reactive props unchanged.
The generic declaration remains supported:
props: defineProps<{ name: string }>()Existing components may continue pairing that form with propsSchema. New
components should pass the schema directly to defineProps to avoid duplicate
type declarations. Supplying a different schema through both
defineProps(schema) and propsSchema throws PropsSchemaConflictError.
Every template has explicit namespaces for its two value owners:
-
ctx.propscontains the component's reactive, resolved props. -
ctx.exposedcontains values returned fromscriptor registered withexpose. -
ctx.childrencontains rendered child content.
Values with unique names also remain available directly on ctx for concise
templates and source compatibility:
const Greeting = define({
props: defineProps<{ name: string }>(),
script: ({ props, computed }) => ({
label: computed(() => `Hello, ${props.name}`),
}),
template: (ctx) => (
<h1 data-name={ctx.props.name}>{ctx.label}</h1>
),
});Flat access never chooses between owners. If a prop and exposed value use the same key, that key is removed from the flat context and both values remain available through their namespace:
const Status = define({
props: defineProps<{ status: string }>(),
script: () => ({ status: 'locally-computed' }),
template: (ctx) => (
<output>{ctx.props.status}:{ctx.exposed.status}</output>
),
});props, exposed, and children are reserved top-level context keys. A prop or
exposed value with one of those names remains reachable through its owner, such
as ctx.props.props or ctx.exposed.children; it is not flattened. Ordinary
component children continue to use ctx.children.
In development, Effuse warns once per component instance and key when a value
cannot be flattened. Migrate the ambiguous read to ctx.props.key or
ctx.exposed.key. Production behavior uses the same collision-safe context but
does not emit development diagnostics.
The current script context includes:
| Group | Members |
|---|---|
| Inputs |
props, layers, router
|
| Reactivity |
signal, computed, watch, watchMultiple, watchEffect
|
| Lifecycle |
onBeforeMount, onMount, onBeforeUnmount, onUnmount
|
| Capabilities |
useLayer, useService, useComponent, store, useStore
|
| Composition |
provide, inject, expose, useCallback, useMemo
|
Prefer alias layer records or typed direct layer helpers. String store/service lookups remain escape hatches and compatibility surfaces.
Children are available in the template context:
const Panel = define({
script: () => ({}),
template: ({ children }) => <section class="panel">{children}</section>,
});const Clock = define({
script({ signal, onMount, onUnmount }) {
const now = signal(Date.now());
let timer: ReturnType<typeof setInterval> | undefined;
onMount(() => {
timer = setInterval(() => (now.value = Date.now()), 1000);
});
onUnmount(() => {
if (timer) clearInterval(timer);
});
return { now };
},
template: ({ now }) => <time>{now}</time>,
});Watchers created through the script context are scoped to component unmount.
Lifecycle callback failures are collected after all sibling callbacks and
cleanups run. Configure createApp(root, { onError }) to receive typed
LifecycleError aggregates; without a handler, Effuse reports them to the
console instead of hiding them.
Effuse exports For, Show, Switch, Dynamic, Repeat, Await,
Suspense, and ErrorBoundary. Use these for explicit control flow and async
boundaries. JSX expressions may also contain signals and functions that resolve
to Effuse children.
A component can define blueprint error/loading behavior through lower-level
APIs. Dynamic render failures without a custom boundary produce a visible
data-effuse-render-error alert rather than silently dropping the subtree.