Перейти к содержанию

Шпаргалка XState v5

Используйте эту шпаргалку, чтобы быстро найти синтаксис XState v5.

Установка XState

1
npm install xstate
1
pnpm install xstate
1
yarn add xstate

Подробнее об установке XState.

Создание конечного автомата

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { setup, createActor, assign } from 'xstate';

const machine = setup({
    /* ... */
}).createMachine({
    id: 'toggle',
    initial: 'active',
    context: { count: 0 },
    states: {
        active: {
            entry: assign({
                count: ({ context }) => context.count + 1,
            }),
            on: {
                toggle: { target: 'inactive' },
            },
        },
        inactive: {
            on: {
                toggle: { target: 'active' },
            },
        },
    },
});

const actor = createActor(machine);
actor.subscribe((snapshot) => {
    console.log(snapshot.value);
});

actor.start();
// logs 'active' with context { count: 1 }

actor.send({ type: 'toggle' });
// logs 'inactive' with context { count: 1 }
actor.send({ type: 'toggle' });
// logs 'active' with context { count: 2 }
actor.send({ type: 'toggle' });
// logs 'inactive' with context { count: 2 }

Подробнее о модели актера.

Создание логики обещаний

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import { fromPromise, createActor } from 'xstate';

const promiseLogic = fromPromise(async () => {
    const response = await fetch(
        'https://dog.ceo/api/breeds/image/random'
    );
    const dog = await response.json();
    return dog;
});

const actor = createActor(promiseLogic);

actor.subscribe((snapshot) => {
    console.log(snapshot);
});

actor.start();
// logs: {
//   message: "https://images.dog.ceo/breeds/kuvasz/n02104029_110.jpg",
//   status: "success"
// }

Подробнее о логике промиса.

Создание логики перехода

Функция перехода похожа на редуктор.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { fromTransition, createActor } from 'xstate';

const transitionLogic = fromTransition(
    (state, event) => {
        switch (event.type) {
            case 'inc':
                return {
                    ...state,
                    count: state.count + 1,
                };
            default:
                return state;
        }
    },
    { count: 0 } // initial state
);

const actor = createActor(transitionLogic);

actor.subscribe((snapshot) => {
    console.log(snapshot);
});

actor.start();
// logs { count: 0 }

actor.send({ type: 'inc' });
// logs { count: 1 }
actor.send({ type: 'inc' });
// logs { count: 2 }

Подробнее о transition-логике.

Создание наблюдаемой логики

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { fromObservable, createActor } from 'xstate';
import { interval } from 'rxjs';

const observableLogic = fromObservable(() =>
    interval(1000)
);

const actor = createActor(observableLogic);

actor.subscribe((snapshot) => {
    console.log(snapshot);
});

actor.start();
// logs 0, 1, 2, 3, 4, 5, ...
// every second

Подробнее о наблюдаемых актерах.

Создание логики обратного вызова

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { fromCallback, createActor } from 'xstate';

const callbackLogic = fromCallback(
    ({ sendBack, receive }) => {
        const i = setTimeout(() => {
            sendBack({ type: 'timeout' });
        }, 1000);

        receive((event) => {
            if (event.type === 'cancel') {
                console.log('canceled');
                clearTimeout(i);
            }
        });

        return () => {
            clearTimeout(i);
        };
    }
);

const actor = createActor(callbackLogic);

actor.start();

actor.send({ type: 'cancel' });
// logs 'canceled'

Подробнее об актерах обратного вызова.

Родительские состояния

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { setup, createActor } from 'xstate';

const machine = setup({
    /* ... */
}).createMachine({
    id: 'parent',
    initial: 'active',
    states: {
        active: {
            initial: 'one',
            states: {
                one: {
                    on: {
                        NEXT: { target: 'two' },
                    },
                },
                two: {},
            },
            on: {
                NEXT: { target: 'inactive' },
            },
        },
        inactive: {},
    },
});

const actor = createActor(machine);

actor.subscribe((snapshot) => {
    console.log(snapshot.value);
});

actor.start();
// logs { active: 'one' }

actor.send({ type: 'NEXT' });
// logs { active: 'two' }

actor.send({ type: 'NEXT' });
// logs 'inactive'

Подробнее о родительских состояниях.

Действия

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import { setup, createActor } from 'xstate';

const machine = setup({
    actions: {
        activate: () => {
            /* ... */
        },
        deactivate: () => {
            /* ... */
        },
        notify: (_, params: { message: string }) => {
            /* ... */
        },
    },
}).createMachine({
    id: 'toggle',
    initial: 'active',
    states: {
        active: {
            entry: { type: 'activate' },
            exit: { type: 'deactivate' },
            on: {
                toggle: {
                    target: 'inactive',
                    actions: [{ type: 'notify' }],
                },
            },
        },
        inactive: {
            on: {
                toggle: {
                    target: 'active',
                    actions: [
                        // action with params
                        {
                            type: 'notify',
                            params: {
                                message:
                                    'Some notification',
                            },
                        },
                    ],
                },
            },
        },
    },
});

const actor = createActor(
    machine.provide({
        actions: {
            notify: (_, params) => {
                console.log(
                    params.message ?? 'Default message'
                );
            },
            activate: () => {
                console.log('Activating');
            },
            deactivate: () => {
                console.log('Deactivating');
            },
        },
    })
);

actor.start();
// logs 'Activating'

actor.send({ type: 'toggle' });
// logs 'Deactivating'
// logs 'Default message'

actor.send({ type: 'toggle' });
// logs 'Some notification'
// logs 'Activating'

Подробнее о действиях.

Условия (guards)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import { setup, createActor } from 'xstate';

const machine = setup({
    guards: {
        canBeToggled: ({ context }) => context.canActivate,
        isAfterTime: (_, params) => {
            const { time } = params;
            const [hour, minute] = time.split(':');
            const now = new Date();
            return (
                now.getHours() > hour &&
                now.getMinutes() > minute
            );
        },
    },
    actions: {
        notifyNotAllowed: () => {
            console.log('Cannot be toggled');
        },
    },
}).createMachine({
    id: 'toggle',
    initial: 'active',
    context: {
        canActivate: false,
    },
    states: {
        inactive: {
            on: {
                toggle: [
                    {
                        target: 'active',
                        guard: 'canBeToggled',
                    },
                    {
                        actions: 'notifyNotAllowed',
                    },
                ],
            },
        },
        active: {
            on: {
                toggle: {
                    // Guard with params
                    guard: {
                        type: 'isAfterTime',
                        params: { time: '16:00' },
                    },
                    target: 'inactive',
                },
            },
            // ...
        },
    },
});

const actor = createActor(machine);

actor.start();
// logs 'Cannot be toggled'

Подробнее об условиях.

Вызов актеров

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import {
    setup,
    fromPromise,
    createActor,
    assign,
} from 'xstate';

const loadUserLogic = fromPromise(async () => {
    const response = await fetch(
        'https://jsonplaceholder.typicode.com/users/1'
    );
    const user = await response.json();
    return user;
});

const machine = setup({
    actors: { loadUserLogic },
}).createMachine({
    id: 'toggle',
    initial: 'loading',
    context: {
        user: undefined,
    },
    states: {
        loading: {
            invoke: {
                id: 'loadUser',
                src: 'loadUserLogic',
                onDone: {
                    target: 'doSomethingWithUser',
                    actions: assign({
                        user: ({ event }) => event.output,
                    }),
                },
                onError: {
                    target: 'failure',
                    actions: ({ event }) => {
                        console.log(event.error);
                    },
                },
            },
        },
        doSomethingWithUser: {
            // ...
        },
        failure: {
            // ...
        },
    },
});

const actor = createActor(machine);

actor.subscribe((snapshot) => {
    console.log(snapshot.context.user);
});

actor.start();
// eventually logs:
// { id: 1, name: 'Leanne Graham', ... }

Подробнее о вызове актеров.

Порождение акторов

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import {
    setup,
    fromPromise,
    createActor,
    assign,
} from 'xstate';

const loadUserLogic = fromPromise(async () => {
    const response = await fetch(
        'https://jsonplaceholder.typicode.com/users/1'
    );
    const user = await response.json();
    return user;
});

const machine = setup({
    actors: {
        loadUserLogic,
    },
}).createMachine({
    context: {
        userRef: undefined,
    },
    on: {
        loadUser: {
            actions: assign({
                userRef: ({ spawn }) =>
                    spawn('loadUserLogic'),
            }),
        },
    },
});

const actor = createActor(machine);
actor.subscribe((snapshot) => {
    const { userRef } = snapshot.context;
    console.log(userRef?.getSnapshot());
});
actor.start();

actor.send({ type: 'loadUser' });
// eventually logs:
// { id: 1, name: 'Leanne Graham', ... }

Подробнее о порождении акторов.

Input и output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { setup, createActor } from 'xstate';

const greetMachine = setup({
    types: {
        context: {} as { message: string },
        input: {} as { name: string },
    },
}).createMachine({
    context: ({ input }) => ({
        message: `Hello, ${input.name}`,
    }),
    entry: ({ context }) => {
        console.log(context.message);
    },
});

const actor = createActor(greetMachine, {
    input: {
        name: 'David',
    },
});

actor.start();
// logs 'Hello, David'

Подробнее о input.

Вызов акторов с input

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { setup, createActor, fromPromise } from 'xstate';

const loadUserLogic = fromPromise(async ({ input }) => {
    const response = await fetch(
        `https://jsonplaceholder.typicode.com/users/${input.id}`
    );
    const user = await response.json();
    return user;
});

const machine = setup({
    actors: {
        loadUserLogic,
    },
}).createMachine({
    initial: 'loading user',
    states: {
        'loading user': {
            invoke: {
                id: 'loadUser',
                src: 'loadUserLogic',
                input: {
                    id: 3,
                },
                onDone: {
                    actions: ({ event }) => {
                        console.log(event.output);
                    },
                },
            },
        },
    },
});

const actor = createActor(machine);

actor.start();
// eventually logs:
// { id: 3, name: 'Clementine Bauch', ... }

Подробнее о вызове акторов с input.

Типы

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { setup, fromPromise } from 'xstate';

const promiseLogic = fromPromise(async () => {
  /* ... */
});

const machine = setup({
  types: {
    context: {} as {
      count: number;
    };
    events: {} as
      | { type: 'inc'; }
      | { type: 'dec' }
      | { type: 'incBy'; amount: number };
    actions: {} as
      | { type: 'notify'; params: { message: string } }
      | { type: 'handleChange' };
    guards: {} as
      | { type: 'canBeToggled' }
      | { type: 'isAfterTime'; params: { time: string } };
    children: {} as {
      promise1: 'someSrc';
      promise2: 'someSrc';
    };
    delays: 'shortTimeout' | 'longTimeout';
    tags: 'tag1' | 'tag2';
    input: number;
    output: string;
  },
  actors: {
    promiseLogic
  }
}).createMachine({
  // ...
});

Комментарии