Tarjima qoshish uchun havola boyicha o'tib Pull Request oching (havolaga o'tish).
Standart til uchun tarkibni ko'rsatadi.
Unit composition
If we consider each unit as a building block of our application, then for full functionality we need to somehow glue these blocks together. For example, when a form submission event occurs, validate the form data, if everything is correct, call an effect to send the data, and also update our store. In other words, build connections between units. To implement this, you need to use the sample operator or createAction.
Conceptually, both operators perform the same work, however, they have a difference: sample is a declarative operator, while createAction is more imperative, allowing you to describe logic in a more familiar style.
// sampleimport { sample, createEvent } from "effector";
const sendButtonClicked = createEvent();
sample({ clock: sendButtonClicked, source: $formData, filter: (form) => form.username.length > 0 && form.age >= 18, fn: (form) => ({ ...form, timestamp: Date.now(), }), target: [sendFormFx, formSubmitted],});// createActionimport { createAction } from "effector-action";import { createEvent } from "effector";
const sendButtonClicked = createEvent();
createAction(sendButtonClicked, { source: $formData, target: { sendForm: sendFormFx, formSubmitted, }, fn: (target, form) => { if (form.username.length > 0 && form.age >= 18) { const updatedForm = { ...form, timestamp: Date.now(), }; target.sendForm(updatedForm); target.formSubmitted(); } },});Both operators trigger when the sendButtonClicked event is called, then take data from source, and then:
- In
sample, separate parameters are used:filterfor checking conditions,fnfor data transformation, andtargetfor calling units. - In
createAction, all logic is in a singlefn, where you can use regularifstatements for conditions and explicitly call the neededtarget.
createAction is an operator from the external package effector-action, which will move to the effector core package in the nearest major release. Additionally, you need to install the patronum package.
npm install effector-action patronumyarn install effector-action patronumpnpm install effector-action patronumBasic usage
Let’s look at a basic example from the beginning of the article: we want to validate form data when a form submission event occurs, call an effect to send the data if everything is correct, and also update our store. Let’s first look at what units we need:
- We need an event
submitFormfor form submission - Several stores -
$formDatato store form data and$formSubmittedfor the form submission status - And an effect
sendFormFxto send data to the server
On the How to think in the effector paradigm page, we explain why you should create events rather than just calling effects directly from UI.
import { createEvent, createStore, sample, createEffect } from "effector";
const submitForm = createEvent();
const $formData = createStore({ username: "", age: 0 });const $formSubmitted = createStore(false);
const sendFormFx = createEffect((formData: { username: string; age: number }) => { // some logic to send data to the server});In the UI, we will call the submitForm event when the user clicks the submit button. It remains to build connections between units:
import { createEvent, createStore, sample, createEffect } from "effector";
const submitForm = createEvent();
const $formData = createStore({ username: "", age: 0 });const $formSubmitted = createStore(false);
const sendFormFx = createEffect((formData: { username: string; age: number }) => { // some logic to send data to the server});
sample({ clock: submitForm, source: $formData, filter: (form) => form.age >= 18 && form.username.length > 0, target: sendFormFx,});
sample({ clock: submitForm, fn: () => true, target: $formSubmitted,});import { createEvent, createStore, sample, createEffect } from "effector";
const submitForm = createEvent();
const $formData = createStore({ username: "", age: 0 });const $formSubmitted = createStore(false);
const sendFormFx = createEffect((formData: { username: string; age: number }) => { // some logic to send data to the server});
createAction(submitForm, { source: $formData, target: { sendForm: sendFormFx, formSubmitted: $formSubmitted, }, fn: (target, form) => { if (form.age >= 18 && form.username.length > 0) { target.sendForm(form); }
target.formSubmitted(true); },});Usage capabilities
As mentioned, both operators are conceptually similar to each other, so you don’t need to choose one over the other - you can use both in your application. However, there are some cases when createAction will be preferable over sample:
- Conditional execution logic. When using
sample, you may encounter difficulty in narrowing types afterfilter, which is not the case when usingcreateActiondue to the use of native language constructs that TypeScript understands well -if. - Grouping by trigger. It’s also more convenient to use
createActionwhen we have one common trigger, but different calculations are required for eachtarget.
Let’s now look at the main operator usage capabilities:
import { createEvent, createStore, sample } from "effector";
const $query = createStore("");
const queryChanged = createEvent<string>();
sample({ clock: queryChanged, target: $query,});import { createStore, createEvent } from "effector";import { createAction } from "effector-action";
const $query = createStore("");
const queryChanged = createEvent<string>();
createAction(queryChanged, { target: $query, fn: (target, query) => { target(query); },});- You can control the
targetcall by condition, read more about this on the API page for sample:
import { createEvent, createStore, sample } from "effector";
const $query = createStore("");const $shouldUpdate = createStore(false);
const queryChanged = createEvent<string>();
sample({ clock: queryChanged, filter: $shouldUpdate, target: $query,});import { createStore, createEvent } from "effector";import { createAction } from "effector-action";
const $query = createStore("");const $shouldUpdate = createStore(false);
const queryChanged = createEvent<string>();
createAction(queryChanged, { source: { $shouldUpdate, }, target: $query, fn: (target, { shouldUpdate }, query) => { if (shouldUpdate) { target(query); } },});- You can also perform calculations in the
fnfunction, but keep in mind that it must be a pure function and synchronous.
Limitations of createAction
The createAction operator has an important restriction: when calling the same target multiple times, only the last one will be invoked:
import { createStore, createEvent } from "effector";import { createAction } from "effector-action";
const $counter = createStore(0);
const increase = createEvent<number>();
createAction(increase, { target: $counter, fn: (target, delta) => { target(delta); // only the last target call will be executed target(delta + 5); },});How to use these operators
Using these operators involves building atomic connections instead of one large block of code. For example, let’s consider another application scenario - a search form with parameters. Let’s first look at how we would write this in vanilla JavaScript: Suppose we have some state in a UI framework:
const state = { query: "", category: "all", results: [], isLoading: false, error: null,};Functions to change state:
function handleQueryChanged(payload) { // here can be any state change from React/Vue/Solid and other frameworks state.query = payload;}
function handleCategoryChanged(payload) { // here can be any state change from React/Vue/Solid and other frameworks state.category = payload;}And the main function for requesting data:
async function handleSearchClick() { state.error = null; state.results = [];
state.isLoading = true;
try { const currentQuery = state.query; const currentCategory = state.category; // some API call const data = await apiCall(currentQuery, currentCategory); state.results = data; } catch (e) { state.error = e.message; } finally { state.isLoading = false; }}All that’s left is to call these functions in the UI at the right moment. With the sample or createAction operators, things work a bit differently - we will create atomic independent connections between units. First, let’s rewrite the previous code using units:
const state = { query: "", category: "all", results: [], isLoading: false, error: null,};
const $query = createStore("");const $category = createStore("all");const $results = createStore([]);const $error = createStore(null);const $isLoading = createStore(false);We need events to change stores and also add logic to change these stores:
function handleQueryChanged(payload) { state.query = payload;}
function handleCategoryChanged(payload) { state.category = payload;}
const queryChanged = createEvent<string>();const categoryChanged = createEvent<string>();
sample({ clock: queryChanged, target: $query,});
sample({ clock: categoryChanged, target: $category,});And now we need to implement the main search logic:
async function handleSearchClick() { state.error = null; state.results = [];
state.isLoading = true;
try { const currentQuery = state.query; const currentCategory = state.category; // some API call const data = await apiCall(currentQuery, currentCategory); state.results = data; } catch (e) { state.error = e.message; } finally { state.isLoading = false; }}
const searchClicked = createEvent();
const searchFx = createEffect(async ({ query, category }) => { const data = await apiCall(query, category); return data;});
sample({ clock: searchClicked, source: { query: $query, category: $category, }, target: searchFx,});
sample({ clock: searchFx.$pending, target: $isLoading,});
sample({ clock: searchFx.failData, fn: (error) => error.message, target: $error,});
sample({ clock: searchFx.doneData, target: $results,});In the final form, we will have the following data model:
import { createStore, createEvent, createEffect, sample } from "effector";
const $query = createStore("");const $category = createStore("all");const $results = createStore([]);const $error = createStore(null);const $isLoading = createStore(false);
const queryChanged = createEvent<string>();const categoryChanged = createEvent<string>();const searchClicked = createEvent();
const searchFx = createEffect(async ({ query, category }) => { const data = await apiCall(query, category); return data;});
sample({ clock: queryChanged, target: $query,});
sample({ clock: categoryChanged, target: $category,});
sample({ clock: searchClicked, source: { query: $query, category: $category, }, target: searchFx,});
sample({ clock: searchFx.$pending, target: $isLoading,});
sample({ clock: searchFx.failData, fn: (error) => error.message, target: $error,});
sample({ clock: searchFx.doneData, target: $results,});Related API and articles
- API
- Articles
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.