rvx
Version:
A signal based rendering library
208 lines (154 loc) • 4.46 kB
Markdown
# Event System
A tiny synchronous event system separate from DOM events.
=== "JSX"
```jsx
import { Emitter } from "rvx";
const emitter = new Emitter<[address: string, port: number]>();
// Subscribe until the current lifecycle is disposed:
emitter.event((address, port) => {
console.log("Connected:", address, port);
});
// Synchronously call listeners in subscription order:
emitter.emit("127.0.0.1", 12345);
```
=== "No Build"
```jsx
import { Emitter } from "./rvx.js";
/** @type {Emitter<[address: string, port: number]>} */
const emitter = new Emitter();
// Subscribe until the current lifecycle is disposed:
emitter.event((address, port) => {
console.log("Connected:", address, port);
});
// Synchronously call listeners in subscription order:
emitter.emit("127.0.0.1", 12345);
```
## Context Separation
!!! warning
Event listeners are in no way separated from the context where the event is emitted in.
### Lifecycle
Event listeners running while capturing [teardown hooks](./lifecycle.md#capture) will be able to register hooks in that lifecycle:
=== "JSX"
```jsx
import { capture, Emitter } from "rvx";
const emitter = new Emitter<[]>();
emitter.event(() => {
teardown(() => { ... });
});
capture(() => {
emitter.emit();
});
```
=== "No Build"
```jsx
import { capture, Emitter } from "./rvx.js";
const emitter = new Emitter();
emitter.event(() => {
teardown(() => { ... });
});
capture(() => {
emitter.emit();
});
```
To prevent listeners from registering teardown hooks, use [`nocapture`](./lifecycle.md#nocapture):
```jsx
nocapture(() => emitter.emit());
```
### Signal Accesses
Event listeners running while signal accesses are tracked will be able to access additional signals:
=== "JSX"
```jsx
import { effect, Emitter } from "rvx";
const emitter = new Emitter<[]>();
const signal = $(42);
emitter.event(() => {
// This access is tracked inside the effect below:
console.log("Value:", signal.value);
});
effect(() => {
emitter.emit();
});
```
=== "No Build"
```jsx
import { effect, Emitter } from "./rvx.js";
const emitter = new Emitter();
const signal = $(42);
emitter.event(() => {
// This access is tracked inside the effect below:
console.log("Value:", signal.value);
});
effect(() => {
emitter.emit();
});
```
To prevent this, you can use [`untrack`](./signals.md#track-untrack):
```jsx
untrack(() => emitter.emit());
```
Note, that this can still be circumvented by event listeners using [`track`](./signals.md#track-untrack).
### Contexts
Event listeners running while a value for a context is injected also have access to that value.
=== "JSX"
```jsx
import { Context, Emitter } from "rvx";
const MESSAGE = new Context<string | undefined>();
const emitter = new Emitter<[]>();
emitter.event(() => {
MESSAGE.current; // "Hello World!"
});
MESSAGE.inject("Hello World!", () => {
emitter.emit();
});
```
=== "No Build"
```jsx
import { Context, Emitter } from "./rvx.js";
const MESSAGE = new Context();
const emitter = new Emitter();
emitter.event(() => {
MESSAGE.current; // "Hello World!"
});
MESSAGE.inject("Hello World!", () => {
emitter.emit();
});
```
You can prevent listeners from accessing any current values from the emitter side:
```jsx
Context.window([], () => emitter.emit());
```
You can wrap event listeners to always run with the current values:
```jsx
emitter.event(Context.wrap(() => { ... }));
```
## Error Handling
The event system has no dedicated error handling. Any errors thrown from listeners will prevent further listeners from running.
## Components
To allow components to listen to events, it's enough to pass the `event` as a property:
=== "JSX"
```jsx
import { Event, Emitter } from "rvx";
function SomeComponent(props: { onMessage: Event<[message: string]> }) {
props.onMessage(message => {
console.log(message);
});
}
const messages = new Emitter<[message: string]>();
<SomeComponent onMessage={messages.event} />
```
=== "No Build"
```jsx
import { Emitter } from "./rvx.js";
/**
* @param {object} props
* @param {import("./rvx.event.js").Event<[message: string]>} props.onMessage
*/
function SomeComponent(props) {
props.onMessage(message => {
console.log(message);
});
}
const messages = new Emitter();
SomeComponent({ onMessage: messages.event })
```
To allow components to also emit events, pass the emitter instead.