# @xstate/immer


XState Immer
XState with Immer

@xstate/immer package (opens new window) 包含将 Immer (opens new window)XState (opens new window) 结合使用的实用程序。

¥The @xstate/immer package (opens new window) contains utilities for using Immer (opens new window) with XState (opens new window).

# 快速开始

¥Quick Start

包含在 @xstate/immer 中:

¥Included in @xstate/immer:

  • assign() - Immer 操作允许你以方便的方式一成不变地分配给机器 context

    ¥assign() - an Immer action that allows you to immutably assign to machine context in a convenient way

  • createUpdater() - 一个有用的函数,允许你一致地定义上下文更新事件事件创建者并分配操作。(以下 看一个例子

    ¥createUpdater() - a useful function that allows you to cohesively define a context update event event creator and assign action, all together. (See an example below)

  1. 安装 immerxstate@xstate/immer

    ¥Install immer, xstate, @xstate/immer:

npm install immer xstate @xstate/immer

注意:你不需要从 immerimport 任何内容;它是 @xstate/immer 的对等依赖,因此必须安装。

¥Note: You don't need to import anything from immer; it is a peer-dependency of @xstate/immer, so it must be installed.

  1. 导入 Immer 实用程序:

    ¥Import the Immer utilities:

import { createMachine, interpret } from 'xstate';
import { assign, createUpdater } from '@xstate/immer';

const levelUpdater = createUpdater('UPDATE_LEVEL', (ctx, { input }) => {
  ctx.level = input;
});

const toggleMachine = createMachine({
  id: 'toggle',
  context: {
    count: 0,
    level: 0
  },
  initial: 'inactive',
  states: {
    inactive: {
      on: {
        TOGGLE: {
          target: 'active',
          // Immutably update context the same "mutable"
          // way as you would do with Immer!
          actions: assign((ctx) => ctx.count++)
        }
      }
    },
    active: {
      on: {
        TOGGLE: {
          target: 'inactive'
        },
        // Use the updater for more convenience:
        [levelUpdater.type]: {
          actions: levelUpdater.action
        }
      }
    }
  }
});

const toggleService = interpret(toggleMachine)
  .onTransition((state) => {
    console.log(state.context);
  })
  .start();

toggleService.send({ type: 'TOGGLE' });
// { count: 1, level: 0 }

toggleService.send(levelUpdater.update(9));
// { count: 1, level: 9 }

toggleService.send({ type: 'TOGGLE' });
// { count: 2, level: 9 }

toggleService.send(levelUpdater.update(-100));
// Notice how the level is not updated in 'inactive' state:
// { count: 2, level: 9 }

# API

# assign(recipe)

返回一个 XState 事件对象,该对象将更新计算机的 context 以反映 recipe 函数中对 context 所做的更改 ("mutations")。

¥Returns an XState event object that will update the machine's context to reflect the changes ("mutations") to context made in the recipe function.

recipe 与传递给 Immer 的 produce(val, recipe) 函数 (opens new window) 的函数类似,此外,你还获得与传递给 assign(assigner) 的普通 XState 分配器相同的参数(contexteventmeta)。

¥The recipe is similar to the function that you would pass to Immer's produce(val, recipe) function (opens new window)), with the addition that you get the same arguments as a normal XState assigner passed to assign(assigner) (context, event, meta).

assign 的参数:

¥Arguments for assign:

争论 类型 描述
recipe function 生成 "mutations" 至 context 的函数。参见 Immer 文档 (opens new window)

recipe 的参数:

¥Arguments for recipe:

争论 类型 描述
context any 当前状态的上下文数据
event 事件对象 接收到的事件对象
meta 分配元对象 包含元数据的对象,例如 state、SCXML _event 等。
import { createMachine } from 'xstate';
import { assign } from '@xstate/immer';

const userMachine = createMachine({
  id: 'user',
  context: {
    name: null,
    address: {
      city: null,
      state: null,
      country: null
    }
  },
  initial: 'active',
  states: {
    active: {
      on: {
        CHANGE_COUNTRY: {
          actions: assign((context, event) => {
            context.address.country = event.value;
          })
        }
      }
    }
  }
});

const { initialState } = userMachine;

const nextState = userMachine.transition(initialState, {
  type: 'UPDATE_COUNTRY',
  country: 'USA'
});

nextState.context.address.country;
// => 'USA'

# createUpdater(eventType, recipe)

返回一个对于创建 context 更新程序有用的对象。

¥Returns an object that is useful for creating context updaters.

争论 类型 描述
eventType string Immer 更新事件的事件类型
recipe function 接收 context 和 Immer 将 event 对象更新为 "mutate" 和 context 的函数

Immer 更新 event 对象是包含以下内容的对象:

¥An Immer update event object is an object that contains:

  • type:指定的 eventType

    ¥type: the eventType specified

  • input:更新事件的 "payload"

    ¥input: the "payload" of the update event

createUpdater(...) 返回的对象是一个更新程序对象,包含:

¥The object returned by createUpdater(...) is an updater object containing:

  • typeeventType 传递到 createUpdater(eventType, ...)。这用于指定将发生更新的转换。

    ¥type: the eventType passed into createUpdater(eventType, ...). This is used for specifying transitions in which the update will occur.

  • action:将更新 context 的分配操作对象。

    ¥action: the assign action object that will update the context.

  • update:事件创建者接受 input 并返回 event 对象,其中指定的 eventTypeinput 将传递给 recipe(context, event)

    ¥update: the event creator that takes in the input and returns an event object with the specified eventType and input that will be passed to recipe(context, event).

⚠️ 注意:.update(...) 事件的创造者是纯粹的;它只返回一个分配动作对象,并不直接更新 context

¥⚠️ Note: The .update(...) event creator is pure; it only returns an assign action object, and doesn't directly update context.

import { createMachine } from 'xstate';
import { createUpdater } from '@xstate/immer';

// The second argument is an Immer update event that looks like:
// {
//   type: 'UPDATE_NAME',
//   input: 'David' // or any string
// }
const nameUpdater = createUpdater('UPDATE_NAME', (context, { input }) => {
  context.name = input;
});

const ageUpdater = createUpdater('UPDATE_AGE', (context, { input }) => {
  context.age = input;
});

const formMachine = createMachine({
  initial: 'editing',
  context: {
    name: '',
    age: null
  },
  states: {
    editing: {
      on: {
        // The updater.type can be used directly for transitions
        // where the updater.action function will be applied
        [nameUpdater.type]: { actions: nameUpdater.action },
        [ageUpdater.type]: { actions: ageUpdater.action }
      }
    }
  }
});

const service = interpret(formMachine)
  .onTransition((state) => {
    console.log(state.context);
  })
  .start();

// The event object sent will look like:
// {
//   type: 'UPDATE_NAME',
//   input: 'David'
// }
service.send(nameUpdater.update('David'));
// => { name: 'David', age: null }

// The event object sent will look like:
// {
//   type: 'UPDATE_AGE',
//   input: 100
// }
service.send(ageUpdater.update(100));
// => { name: 'David', age: 100 }

# TypeScript

要正确键入 Immer assign 操作创建者,请将 contextevent 类型作为泛型类型传递:

¥To properly type the Immer assign action creator, pass in the context and event types as generic types:

interface SomeContext {
  name: string;
}

interface SomeEvent {
  type: 'SOME_EVENT';
  value: string;
}

// ...

{
  actions: assign<SomeContext, SomeEvent>((context, event) => {
    context.name = event.value;
    // ... etc.
  });
}

要正确键入 createUpdater,请将 context 和特定 ImmerUpdateEvent<...>(见下文)类型作为泛型类型传递:

¥To properly type createUpdater, pass in the context and the specific ImmerUpdateEvent<...> (see below) types as generic types:

import { createUpdater, ImmerUpdateEvent } from '@xstate/immer';

// This is the same as:
// {
//   type: 'UPDATE_NAME';
//   input: string;
// }
type NameUpdateEvent = ImmerUpdateEvent<'UPDATE_NAME', string>;

const nameUpdater = createUpdater<SomeContext, NameUpdateEvent>(
  'UPDATE_NAME',
  (ctx, { input }) => {
    ctx.name = input;
  }
);

// You should use NameUpdateEvent directly as part of the event type
// in createMachine<SomeContext, SomeEvent>.

这是上一个表单示例的完整类型示例:

¥Here is a fully typed example of the previous form example:

import { createMachine } from 'xstate';
import { createUpdater, ImmerUpdateEvent } from '@xstate/immer';

interface FormContext {
  name: string;
  age: number | undefined;
}

type NameUpdateEvent = ImmerUpdateEvent<'UPDATE_NAME', string>;
type AgeUpdateEvent = ImmerUpdateEvent<'UPDATE_AGE', number>;

const nameUpdater = createUpdater<FormContext, NameUpdateEvent>(
  'UPDATE_NAME',
  (ctx, { input }) => {
    ctx.name = input;
  }
);

const ageUpdater = createUpdater<FormContext, AgeUpdateEvent>(
  'UPDATE_AGE',
  (ctx, { input }) => {
    ctx.age = input;
  }
);

type FormEvent =
  | NameUpdateEvent
  | AgeUpdateEvent
  | {
      type: 'SUBMIT';
    };

const formMachine = createMachine({
  schema: {
    context: {} as FormContext,
    events: {} as FormEvent
  },
  initial: 'editing',
  context: {
    name: '',
    age: undefined
  },
  states: {
    editing: {
      on: {
        [nameUpdater.type]: { actions: nameUpdater.action },
        [ageUpdater.type]: { actions: ageUpdater.action },
        SUBMIT: 'submitting'
      }
    },
    submitting: {
      // ...
    }
  }
});