Tarjima qoshish uchun havola boyicha o'tib Pull Request oching (havolaga o'tish).
Standart til uchun tarkibni ko'rsatadi.
TypeScript in effector
Effector provides first-class TypeScript support out of the box, giving you reliable typing and excellent development experience when working with the library. In this section, we’ll look at both basic typing concepts and advanced techniques for working with types in effector.
Typing Events
Events in Effector can be typed by passing a type to the generic function. However, if nothing is passed, the event will have the type EventCallable<void>
:
import { createEvent } from "effector";
// Event without parameters
const clicked = createEvent();
// EventCallable<void>
// Event with parameter
const userNameChanged = createEvent<string>();
// EventCallable<string>
// Event with complex parameter
const formSubmitted = createEvent<{
username: string;
password: string;
}>();
// EventCallable<{ username: string; password: string; }>
Event Types
In Effector, events can have several types, where T
is the stored value type:
EventCallable<T>
- an event that can be called.Event<T>
- a derived event that cannot be called manually.
Typing Event Methods
event.prepend
To add types to events created using event.prepend, you need to add the type either in the prepend function argument or as a generic:
const message = createEvent<string>();
const userMessage = message.prepend((text: string) => text);
// userMessage has type EventCallable<string>
const warningMessage = message.prepend<string>((warnMessage) => warnMessage);
// warningMessage has type EventCallable<string>
Typing Stores
Stores can also be typed by passing a type to the generic function, or by specifying a default value during initialization, then TypeScript will infer the type from this value:
import { createStore } from "effector";
// Basic store with primitive value
// StoreWritable<number>
const $counter = createStore(0);
// Store with complex object type
interface User {
id: number;
name: string;
role: "admin" | "user";
}
// StoreWritable<User>
const $user = createStore<User>({
id: 1,
name: "Bob",
role: "user",
});
// Store<string>
const $userNameAndRole = $user.map((user) => `User name and role: ${user.name} and ${user.role}`);
Store Types
In Effector, there are two types of stores, where T is the stored value type:
Store<T>
- derived store type that cannot have new data written to it.StoreWritable<T>
- store type that can have new data written using on or sample.
Typing Effects
In normal usage, TypeScript will infer types based on the function’s return result and its arguments.
However, createEffect
supports typing of input parameters, return result, and errors through generics:
import { createEffect } from "effector";
// Base effect
// Effect<string, User, Error>
const fetchUserFx = createEffect(async (userId: string) => {
const response = await fetch(`/api/users/${userId}`);
const result = await response.json();
return result as User;
});
import { createEffect } from "effector";
// Base effect
// Effect<string, User, Error>
const fetchUserFx = createEffect<string, User>(async (userId) => {
const response = await fetch(`/api/users/${userId}`);
const result = await response.json();
return result;
});
Typing Handler Function Outside Effect
If the handler function is defined outside the effect, you’ll need to pass that function’s type:
const sendMessage = async (params: { text: string }) => {
// ...
return "ok";
};
const sendMessageFx = createEffect<typeof sendMessage, AxiosError>(sendMessage);
// => Effect<{text: string}, string, AxiosError>
Custom Effect Errors
Some code may only throw certain types of exceptions. In effects, the third generic Fail
is used to describe error types:
// Define API error types
interface ApiError {
code: number;
message: string;
}
// Create typed effect
const fetchUserFx = createEffect<string, User, ApiError>(async (userId) => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw {
code: response.status,
message: "Failed to fetch user",
} as ApiError;
}
return response.json();
});
Typing Methods
sample
Typing filter
If you need to get a specific type, you’ll need to manually specify the expected type, which can be done using type predicates:
type UserMessage = { kind: "user"; text: string };
type WarnMessage = { kind: "warn"; warn: string };
const message = createEvent<UserMessage | WarnMessage>();
const userMessage = createEvent<UserMessage>();
sample({
clock: message,
filter: (msg): msg is UserMessage => msg.kind === "user",
target: userMessage,
});
If you need to check for data existence in filter
, you can simply pass Boolean
:
import { createEvent, createStore, sample } from "effector";
interface User {
id: string;
name: string;
email: string;
}
// Events
const formSubmitted = createEvent();
const userDataSaved = createEvent<User>();
// States
const $currentUser = createStore<User | null>(null);
// On form submit, send data only if user exists
sample({
clock: formSubmitted,
source: $currentUser,
filter: Boolean, // filter out null
target: userDataSaved,
});
// Now userDataSaved will only receive existing user data
Typing filter
and fn
As mentioned above, using type predicates in filter
will work correctly and the correct type will reach the target
.
However, this mechanism won’t work as needed when using filter
and fn
together. In this case, you’ll need to manually specify the data type of filter
parameters and add type predicates. This happens because TypeScript cannot correctly infer the type in fn
after filter
if the type isn’t explicitly specified. This is a limitation of TypeScript’s type system.
type UserMessage = { kind: "user"; text: string };
type WarnMessage = { kind: "warn"; warn: string };
type Message = UserMessage | WarnMessage;
const message = createEvent<Message>();
const userText = createEvent<string>();
sample({
clock: message,
filter: (msg: Message): msg is UserMessage => msg.kind === "user",
fn: (msg) => msg.text,
target: userText,
});
// userMessage has type Event<string>
Starting from TypeScript version >= 5.5, you don’t need to write type predicates, just specify the argument type and TypeScript will understand what needs to be inferred:
filter: (msg: Message) => msg.kind === "user"
attach
To allow TypeScript to infer the types of the created effect, you can add a type to the first argument of mapParams
, which will become the Params
generic of the result:
const sendTextFx = createEffect<{ message: string }, "ok">(() => {
// ...
return "ok";
});
const sendWarningFx = attach({
effect: sendTextFx,
mapParams: (warningMessage: string) => ({ message: warningMessage }),
});
// sendWarningFx has type Effect<{message: string}, 'ok'>
split
type UserMessage = { kind: "user"; text: string };
type WarnMessage = { kind: "warn"; warn: string };
const message = createEvent<UserMessage | WarnMessage>();
const { userMessage, warnMessage } = split(message, {
userMessage: (msg): msg is UserMessage => msg.kind === "user",
warnMessage: (msg): msg is WarnMessage => msg.kind === "warn",
});
// userMessage имеет тип Event<UserMessage>
// warnMessage имеет тип Event<WarnMessage>
type UserMessage = { kind: "user"; text: string };
type WarnMessage = { kind: "warn"; warn: string };
const message = createEvent<UserMessage | WarnMessage>();
const { userMessage, warnMessage } = split(message, {
userMessage: (msg) => msg.kind === "user",
warnMessage: (msg) => msg.kind === "warn",
});
// userMessage имеет тип Event<UserMessage>
// warnMessage имеет тип Event<WarnMessage>
createApi
To allow TypeScript to infer types of created events, adding a type to second argument of given reducers
const $count = createStore(0);
const { add, sub } = createApi($count, {
add: (x, add: number) => x + add,
sub: (x, sub: number) => x - sub,
});
// add has type Event<number>
// sub has type Event<number>
is
is
methods can help to infer a unit type (thereby is
methods acts as TypeScript type guards) which can help to write strongly-typed helper functions
export function getUnitType(unit: unknown) {
if (is.event(unit)) {
// here unit has Event<any> type
return "event";
}
if (is.effect(unit)) {
// here unit has Effect<any, any> type
return "effect";
}
if (is.store(unit)) {
// here unit has Store<any> type
return "store";
}
}
merge
When we wanna merge events we can get their union types:
import { createEvent, merge } from "effector";
const firstEvent = createEvent<string>();
const secondEvent = createEvent<number>();
const merged = merge([firstEvent, secondEvent]);
// Event<string | number>
// You can also combine events with the same types
const buttonClicked = createEvent<MouseEvent>();
const linkClicked = createEvent<MouseEvent>();
const anyClick = merge([buttonClicked, linkClicked]);
// Event<MouseEvent>
merge
accepts generic, where you can use what type do you expect from events:
import { createEvent, merge } from "effector";
const firstEvent = createEvent<string>();
const secondEvent = createEvent<number>();
const merged = merge<number>([firstEvent, secondEvent]);
// ^
// Type 'EventCallable<string>' is not assignable to type 'Unit<number>'.
Type Utilities
Effector provides a set of utility types for working with unit types:
UnitValue
The UnitValue
type is used to extract the data type from units:
import { UnitValue, createEffect, createStore, createEvent } from "effector";
const event = createEvent<{ id: string; name?: string } | { id: string }>();
type UnitEventType = UnitValue<typeof event>;
// {id: string; name?: string | undefined} | {id: string}
const $store = createStore([false, true]);
type UnitStoreType = UnitValue<typeof $store>;
// boolean[]
const effect = createEffect<{ token: string }, any, string>(() => {});
type UnitEffectType = UnitValue<typeof effect>;
// {token: string}
const scope = fork();
type UnitScopeType = UnitValue<typeof scope>;
// any
StoreValue
StoreValue
is essentially similar to UnitValue
, but works only with stores:
import { createStore, StoreValue } from "effector";
const $store = createStore(true);
type StoreValueType = StoreValue<typeof $store>;
// boolean
EventPayload
Extracts the data type from events.
Similar to UnitValue
, but only for events:
import { createEvent, EventPayload } from "effector";
const event = createEvent<{ id: string }>();
type EventPayloadType = EventPayload<typeof event>;
// {id: string}
EffectParams
Takes an effect type as a generic parameter, allows getting the parameter type of an effect.
import { createEffect, EffectParams } from "effector";
const fx = createEffect<
{ id: string },
{ name: string; isAdmin: boolean },
{ statusText: string; status: number }
>(() => {
// ...
return { name: "Alice", isAdmin: false };
});
type EffectParamsType = EffectParams<typeof fx>;
// {id: string}
EffectResult
Takes an effect type as a generic parameter, allows getting the return value type of an effect.
import { createEffect, EffectResult } from "effector";
const fx = createEffect<
{ id: string },
{ name: string; isAdmin: boolean },
{ statusText: string; status: number }
>(() => ({ name: "Alice", isAdmin: false }));
type EffectResultType = EffectResult<typeof fx>;
// {name: string; isAdmin: boolean}
EffectError
Takes an effect type as a generic parameter, allows getting the error type of an effect.
import { createEffect, EffectError } from "effector";
const fx = createEffect<
{ id: string },
{ name: string; isAdmin: boolean },
{ statusText: string; status: number }
>(() => ({ name: "Alice", isAdmin: false }));
type EffectErrorType = EffectError<typeof fx>;
// {statusText: string; status: number}
Ingliz tilidagi hujjatlar eng dolzarb hisoblanadi, chunki u effector guruhi tomonidan yozilgan va yangilanadi. Hujjatlarni boshqa tillarga tarjima qilish jamiyat tomonidan kuch va istaklar mavjud bo'lganda amalga oshiriladi.
Esda tutingki, tarjima qilingan maqolalar yangilanmasligi mumkin, shuning uchun eng aniq va dolzarb ma'lumot uchun hujjatlarning asl inglizcha versiyasidan foydalanishni tavsiya etamiz.