# 转场

¥Transitions

转换定义机器如何对 events 做出反应。要了解更多信息,请参阅 状态图简介 中的部分。

¥Transitions define how the machine reacts to events. To learn more, see the section in our introduction to statecharts.

# API

状态转换在状态节点上的 on 属性中定义:

¥State transitions are defined on state nodes, in the on property:











 


 
 
 





















import { createMachine } from 'xstate';

const promiseMachine = createMachine({
  id: 'promise',
  initial: 'pending',
  states: {
    pending: {
      on: {
        // state transition (shorthand)
        // this is equivalent to { target: 'resolved' }
        RESOLVE: 'resolved',

        // state transition (object)
        REJECT: {
          target: 'rejected'
        }
      }
    },
    resolved: {
      type: 'final'
    },
    rejected: {
      type: 'final'
    }
  }
});

const { initialState } = promiseMachine;

console.log(initialState.value);
// => 'pending'

const nextState = promiseMachine.transition(initialState, { type: 'RESOLVE' });

console.log(nextState.value);
// => 'resolved'

在上面的例子中,当机器处于 pending 状态并且接收到 RESOLVE 事件时,它将转换到 resolved 状态。

¥In the above example, when the machine is in the pending state and it receives a RESOLVE event, it will transition to the resolved state.

状态转换可以定义为:

¥A state transition can be defined as:

  • 一个字符串,例如 RESOLVE: 'resolved',相当于...

    ¥a string, e.g., RESOLVE: 'resolved', which is equivalent to...

  • 具有 target 属性的对象,例如 RESOLVE: { target: 'resolved' }

    ¥an object with a target property, e.g., RESOLVE: { target: 'resolved' },

  • 转换对象数组,用于条件转换(参见 guards

    ¥an array of transition objects, which are used for conditional transitions (see guards)

# 机器 .transition 方法

¥Machine .transition Method

如上所示,machine.transition(...) 方法是一个带有两个参数的纯函数:

¥As seen above, the machine.transition(...) method is a pure function that takes two arguments:

  • state - 从 状态 转场

    ¥state - the State to transition from

  • event - 导致转换的 event

    ¥event - the event that causes the transition

它返回一个新的 State 实例,这是当前状态和事件启用的所有转换的结果。

¥It returns a new State instance, which is the result of taking all the transitions enabled by the current state and event.








 




const lightMachine = createMachine({
  /* ... */
});

const greenState = lightMachine.initialState;

// determine next state based on current state and event
const yellowState = lightMachine.transition(greenState, { type: 'TIMER' });

console.log(yellowState.value);
// => 'yellow'

# 选择启用的转换

¥Selecting Enabled Transitions

启用的转换是根据当前状态和事件有条件地进行的转换。当且仅当以下情况时才会采取:

¥An enabled transition is a transition that will be taken conditionally, based upon the current state and event. It will be taken if and only if:

  • 它定义在与当前状态值匹配的 状态节点

    ¥it is defined on a state node that matches the current state value

  • 满足转换 guardcond 属性)(评估为 true

    ¥the transition guard (cond property) is satisfied (evaluates to true)

  • 它不会被更具体的转场所取代。

    ¥it is not superseded by a more specific transition.

分层机 中,转换的优先级取决于它们在树中的深度;更深层次的转变更加具体,因此具有更高的优先级。这与 DOM 事件的工作方式类似:如果单击按钮,则直接在按钮上的单击事件处理程序比 window 上的单击事件处理程序更具体。

¥In a hierarchical machine, transitions are prioritized by how deep they are in the tree; deeper transitions are more specific and thus have higher priority. This works similar to how DOM events work: if you click a button, the click event handler directly on the button is more specific than a click event handler on the window.










 










 
 




 

























const wizardMachine = createMachine({
  id: 'wizard',
  initial: 'open',
  states: {
    open: {
      initial: 'step1',
      states: {
        step1: {
          on: {
            NEXT: { target: 'step2' }
          }
        },
        step2: {
          /* ... */
        },
        step3: {
          /* ... */
        }
      },
      on: {
        NEXT: { target: 'goodbye' },
        CLOSE: { target: 'closed' }
      }
    },
    goodbye: {
      on: {
        CLOSE: { target: 'closed' }
      }
    },
    closed: {
      type: 'final'
    }
  }
});

// { open: 'step1' }
const { initialState } = wizardMachine;

// the NEXT transition defined on 'open.step1'
// supersedes the NEXT transition defined
// on the parent 'open' state
const nextStepState = wizardMachine.transition(initialState, { type: 'NEXT' });
console.log(nextStepState.value);
// => { open: 'step2' }

// there is no CLOSE transition on 'open.step1'
// so the event is passed up to the parent
// 'open' state, where it is defined
const closedState = wizardMachine.transition(initialState, { type: 'CLOSE' });
console.log(closedState.value);
// => 'closed'

# 事件描述符

¥Event Descriptors

事件描述符是描述转换将匹配的事件类型的字符串。通常,这相当于发送到计算机的 event 对象上的 event.type 属性:

¥An event descriptor is a string describing the event type that the transition will match. Often, this is equivalent to the event.type property on the event object sent to the machine:

// ...
{
  on: {
    // "CLICK" is the event descriptor.
    // This transition matches events with { type: 'CLICK' }
    CLICK: 'someState',
    // "SUBMIT" is the event descriptor.
    // This transition matches events with { type: 'SUBMIT' }
    SUBMIT: 'anotherState'
  }
}
// ...

其他事件描述符包括:

¥Other event descriptors include:

  • 空事件描述符 (""),不匹配任何事件(即 "null" 事件),表示进入状态后立即进行的转换

    ¥Null event descriptors (""), which match no events (i.e., "null" events) and represent transitions taken immediately after the state is entered

  • 通配符事件描述符 ("*") 4.7+,如果事件未与状态中的任何其他转换显式匹配,则匹配任何事件

    ¥Wildcard event descriptors ("*") 4.7+, which match any event if the event is not matched explicitly by any other transition in the state

# 自我转变

¥Self Transitions

自转换是指状态转换到自身,状态可能会退出然后重新进入自身。自转换可以是内部转换或外部转换:

¥A self-transition is when a state transitions to itself, in which it may exit and then reenter itself. Self-transitions can either be an internal or external transition:

  • 内部转换既不会退出也不会重新进入自身,但可能会进入不同的子状态。

    ¥An internal transition will neither exit nor re-enter itself, but may enter different child states.

  • 外部转换将退出并重新进入自身,也可能退出/进入子状态。

    ¥An external transition will exit and re-enter itself, and may also exit/enter child states.

默认情况下,具有指定目标的所有转换都是外部的。

¥By default, all transitions with a specified target are external.

有关如何在自转换上执行进入/退出操作的更多详细信息,请参阅 自我转变行动

¥See actions on self-transitions for more details on how entry/exit actions are executed on self-transitions.

# 内部转变

¥Internal Transitions

内部转换是一种不退出其状态节点的转换。内部转换是通过指定 相对目标(例如 '.left')或通过在转换上显式设置 { internal: true } 来创建的。例如,考虑一台将文本段落设置为对齐 'left''right''center''justify' 的机器:

¥An internal transition is one that does not exit its state node. Internal transitions are created by specifying a relative target (e.g., '.left') or by explicitly setting { internal: true } on the transition. For example, consider a machine that sets a paragraph of text to align 'left', 'right', 'center', or 'justify':














 
 
 
 



import { createMachine } from 'xstate';

const wordMachine = createMachine({
  id: 'word',
  initial: 'left',
  states: {
    left: {},
    right: {},
    center: {},
    justify: {}
  },
  on: {
    // internal transitions
    LEFT_CLICK: '.left',
    RIGHT_CLICK: { target: '.right' }, // same as '.right'
    CENTER_CLICK: { target: '.center', internal: true }, // same as '.center'
    JUSTIFY_CLICK: { target: '.justify', internal: true } // same as '.justify'
  }
});

上述机器将以 'left' 状态启动,并根据单击的内容在内部转换到其他子状态。此外,由于转换是内部的,因此父状态节点上定义的 entryexit 或任何 actions 都不会再次执行。

¥The above machine will start in the 'left' state, and based on what is clicked, will internally transition to its other child states. Also, since the transitions are internal, entry, exit or any of the actions defined on the parent state node are not executed again.

具有 { target: undefined }(或没有 target)的转换也是内部转换:

¥Transitions that have { target: undefined } (or no target) are also internal transitions:











 
 
 





const buttonMachine = createMachine({
  id: 'button',
  initial: 'inactive',
  states: {
    inactive: {
      on: { PUSH: 'active' }
    },
    active: {
      on: {
        // No target - internal transition
        PUSH: {
          actions: 'logPushed'
        }
      }
    }
  }
});

内部转变摘要:

¥Summary of internal transitions:

  • EVENT: '.foo' - 向子级的内部转场

    ¥EVENT: '.foo' - internal transition to child

  • EVENT: { target: '.foo' } - 到子级的内部转换(从 '.' 开始)

    ¥EVENT: { target: '.foo' } - internal transition to child (starts with '.')

  • EVENT: undefined - 禁止转变

    ¥EVENT: undefined - forbidden transition

  • EVENT: { actions: [ ... ] } - 内部自转变

    ¥EVENT: { actions: [ ... ] } - internal self-transition

  • EVENT: { actions: [ ... ], internal: true } - 内部自转变,同上

    ¥EVENT: { actions: [ ... ], internal: true } - internal self-transition, same as above

  • EVENT: { target: undefined, actions: [ ... ] } - 内部自转变,同上

    ¥EVENT: { target: undefined, actions: [ ... ] } - internal self-transition, same as above

# 外部转换

¥External Transitions

外部转换将退出并重新进入定义转换的状态节点。在上面的示例中,父 word 状态节点(根状态节点)将在其转换时执行 exitentry 操作。

¥External transitions will exit and reenter the state node in which the transition is defined. In the above example, the parent word state node (the root state node) will have its exit and entry actions executed on its transitions.

默认情况下,转换是外部的,但可以通过在转换上显式设置 { internal: false } 来将任何转换设为外部转换。

¥By default, transitions are external, but any transition can be made external by explicitly setting { internal: false } on the transition.




 
 
 
 



// ...
on: {
  // external transitions
  LEFT_CLICK: 'word.left',
  RIGHT_CLICK: 'word.right',
  CENTER_CLICK: { target: '.center', internal: false }, // same as 'word.center'
  JUSTIFY_CLICK: { target: 'word.justify', internal: false } // same as 'word.justify'
}
// ...

上面的每个转换都是外部的,并且将执行其父状态的 exitentry 操作。

¥Every transition above is external and will have its exit and entry actions of the parent state executed.

外部转换摘要:

¥Summary of external transitions:

  • EVENT: { target: 'foo' } - 所有到兄弟姐妹的转换都是外部转换

    ¥EVENT: { target: 'foo' } - all transitions to siblings are external transitions

  • EVENT: { target: '#someTarget' } - 所有到其他节点的转换都是外部转换

    ¥EVENT: { target: '#someTarget' } - all transitions to other nodes are external transitions

  • EVENT: { target: 'same.foo' } - 外部转移到自己的子节点(相当于 { target: '.foo', internal: false }

    ¥EVENT: { target: 'same.foo' } - external transition to own child node (equivalent to { target: '.foo', internal: false })

  • EVENT: { target: '.foo', internal: false } - 外部转移到子节点

    ¥EVENT: { target: '.foo', internal: false } - external transition to child node

    • 否则这将是一个内部转换

      ¥This would otherwise be an internal transition

  • EVENT: { actions: [ ... ], internal: false } - 外部自转变

    ¥EVENT: { actions: [ ... ], internal: false } - external self-transition

  • EVENT: { target: undefined, actions: [ ... ], internal: false } - 外部自转变,同上

    ¥EVENT: { target: undefined, actions: [ ... ], internal: false } - external self-transition, same as above

# 瞬态转变

¥Transient Transitions

警告

空字符串语法 ({ on: { '': ... } }) 将在版本 5 中弃用。4.11+ 版本中的新 always 语法应该是首选。请参阅下面有关 无事件转变 的部分,它与瞬态转换相同。

¥The empty string syntax ({ on: { '': ... } }) will be deprecated in version 5. The new always syntax in version 4.11+ should be preferred. See below section on eventless transitions, which are the same as transient transitions.

瞬态转换是由 空事件 启用的转换。换句话说,只要满足任何条件,就会立即进行转换(即没有触发事件):

¥A transient transition is a transition that is enabled by a null event. In other words, it is a transition that is immediately taken (i.e., without a triggering event) as long as any conditions are met:














 
 
 
 








































const gameMachine = createMachine(
  {
    id: 'game',
    initial: 'playing',
    context: {
      points: 0
    },
    states: {
      playing: {
        on: {
          // Transient transition
          // Will transition to either 'win' or 'lose' immediately upon
          // (re)entering 'playing' state if the condition is met.
          '': [
            { target: 'win', cond: 'didPlayerWin' },
            { target: 'lose', cond: 'didPlayerLose' }
          ],
          // Self-transition
          AWARD_POINTS: {
            actions: assign({
              points: 100
            })
          }
        }
      },
      win: { type: 'final' },
      lose: { type: 'final' }
    }
  },
  {
    guards: {
      didPlayerWin: (context, event) => {
        // check if player won
        return context.points > 99;
      },
      didPlayerLose: (context, event) => {
        // check if player lost
        return context.points < 0;
      }
    }
  }
);

const gameService = interpret(gameMachine)
  .onTransition((state) => console.log(state.value))
  .start();

// Still in 'playing' state because no conditions of
// transient transition were met
// => 'playing'

// When 'AWARD_POINTS' is sent, a self-transition to 'PLAYING' occurs.
// The transient transition to 'win' is taken because the 'didPlayerWin'
// condition is satisfied.
gameService.send({ type: 'AWARD_POINTS' });
// => 'win'

就像转换一样,瞬态转换可以指定为单个转换(例如 '': 'someTarget')或条件转换数组。如果没有满足瞬态转换的条件转换,则机器将保持相同状态。

¥Just like transitions, transient transitions can be specified as a single transition (e.g., '': 'someTarget'), or an array of conditional transitions. If no conditional transitions on a transient transition are met, the machine stays in the same state.

对于每个内部或外部转换,空事件始终为 "sent"。

¥Null events are always "sent" for every transition, internal or external.

# 无事件 ("总是") 转换 4.11+

¥Eventless ("Always") Transitions 4.11+

无事件转换是当机器处于定义的状态并且其 cond 防护评估为 true 时始终进行的转换。他们被检查:

¥An eventless transition is a transition that is always taken when the machine is in the state where it is defined, and when its cond guard evaluates to true. They are checked:

  • 当进入状态节点时立即

    ¥immediately when the state node is entered

  • 每次机器收到可操作事件时(无论该事件是触发内部还是外部转换)

    ¥every time the machine receives an actionable event (regardless of whether the event triggers internal or external transition)

无事件转换在状态节点的 always 属性上定义:

¥Eventless transitions are defined on the always property of the state node:














 
 
 
 









































const gameMachine = createMachine(
  {
    id: 'game',
    initial: 'playing',
    context: {
      points: 0
    },
    states: {
      playing: {
        // Eventless transition
        // Will transition to either 'win' or 'lose' immediately upon
        // entering 'playing' state or receiving AWARD_POINTS event
        // if the condition is met.
        always: [
          { target: 'win', cond: 'didPlayerWin' },
          { target: 'lose', cond: 'didPlayerLose' }
        ],
        on: {
          // Self-transition
          AWARD_POINTS: {
            actions: assign({
              points: 100
            })
          }
        }
      },
      win: { type: 'final' },
      lose: { type: 'final' }
    }
  },
  {
    guards: {
      didPlayerWin: (context, event) => {
        // check if player won
        return context.points > 99;
      },
      didPlayerLose: (context, event) => {
        // check if player lost
        return context.points < 0;
      }
    }
  }
);

const gameService = interpret(gameMachine)
  .onTransition((state) => console.log(state.value))
  .start();

// Still in 'playing' state because no conditions of
// transient transition were met
// => 'playing'

// When 'AWARD_POINTS' is sent, a self-transition to 'PLAYING' occurs.
// The transient transition to 'win' is taken because the 'didPlayerWin'
// condition is satisfied.
gameService.send({ type: 'AWARD_POINTS' });
// => 'win'

# 无事件与通配符转换

¥Eventless vs. wildcard transitions

  • 进入状态节点时不检查 通配符转换。无事件转场是。在执行其他操作之前(甚至在评估进入操作的防护之前),会先评估无事件转换的防护。

    ¥Wildcard transitions are not checked on entering state nodes. Eventless transitions are. Guards for eventless transitions are evaluated before doing anything else (even before evaluating guards of entry actions).

  • 无事件转换的重新评估由任何可操作事件触发。通配符转换的重新评估仅由与显式事件描述符不匹配的事件触发。

    ¥Re-evaluation of eventless transitions is triggered by any actionable event. Re-evaluation of wildcard transitions is triggered only by an event not matched by explicit event descriptors.

警告

如果误用无事件转换,则可能会创建无限循环。无事件转换应使用 targetcond + targetcond + actionscond + target + actions 来定义。目标(如果声明)应与当前状态节点不同。没有 targetcond 的无事件转换将导致无限循环。如果 cond 防护不断返回 true,则 condactions 的转换可能会陷入无限循环。

¥It is possible to create infinite loops if eventless transitions are misused. Eventless transitions should be defined either with target, cond + target, cond + actions, or cond + target + actions. Target, if declared, should be different than the current state node. Eventless transitions with no target nor cond will cause an infinite loop. Transitions with cond and actions may run into an infinite loop if its cond guard keeps returning true.

提示

当检查无事件转换时,会重复评估它们的防护,直到它们全部返回 false,或者验证带有目标的转换。在此过程中,每当某个守卫评估为 true 时,其相关操作就会执行一次。因此,在单个微任务期间,一些没有目标的转换可能会被多次执行。这与常见的转换形成对比,在常见的转换中始终可以进行最多一次转换。

¥When eventless transitions are checked, their guards are evaluated repeatedly until all of them return false, or a transition with target is validated. Every time some guard evaluates to true during this process, its associated actions are going to be executed once. Thus it is possible that during a single microtask some transitions without targets are executed multiple times. This contrasts with common transitions, where always maximum one transition can be taken.

# 禁止的转变

¥Forbidden Transitions

在 XState 中,"forbidden" 转换指定指定事件不应发生状态转换。也就是说,在禁止的转换上不应发生任何事情,并且该事件不应由父状态节点处理。

¥In XState, a "forbidden" transition is one that specifies that no state transition should occur with the specified event. That is, nothing should happen on a forbidden transition, and the event should not be handled by parent state nodes.

通过将 target 显式指定为 undefined 来进行禁止的转换。这与将其指定为不带任何操作的内部转换相同:

¥A forbidden transition is made by specifying the target explicitly as undefined. This is the same as specifying it as an internal transition with no actions:



 






on: {
  // forbidden transition
  LOG: undefined,
  // same thing as...
  LOG: {
    actions: []
  }
}

例如,我们可以模拟可以记录所有事件的遥测数据,除了用户输入个人信息时:

¥For example, we can model that telemetry can be logged for all events except when the user is entering personal information:















 










const formMachine = createMachine({
  id: 'form',
  initial: 'firstPage',
  states: {
    firstPage: {
      /* ... */
    },
    secondPage: {
      /* ... */
    },
    userInfoPage: {
      on: {
        // explicitly forbid the LOG event from doing anything
        // or taking any transitions to any other state
        LOG: undefined
      }
    }
  },
  on: {
    LOG: {
      actions: 'logTelemetry'
    }
  }
});

提示

请注意,在分层祖级-后代链中定义具有相同事件名称的多个转换时,将专门采用最内部的转换。在上面的示例中,这就是为什么当机器达到 userInfoPage 状态时,父 LOG 事件中定义的 logTelemetry 操作不会立即执行。

¥Note that when defining multiple transitions with the same event name in a hierarchical ancestor-descendant chain, the most inner transition will exclusively be taken. In the example above, this is why the logTelemetry action defined in the parent LOG event won't execute as soon as the machine reaches the userInfoPage state.

# 多目标

¥Multiple Targets

基于单个事件的转换可以有多个目标状态节点。这是不常见的,并且只有在状态节点合法的情况下才有效;例如,在复合状态节点中转换到两个兄弟状态节点是非法的,因为(非并行)状态机在任何给定时间只能处于一种状态。

¥A transition based on a single event can have multiple target state nodes. This is uncommon, and only valid if the state nodes are legal; e.g., a transition to two sibling state nodes in a compound state node is illegal, since a (non-parallel) state machine can only be in one state at any given time.

多个目标在 target: [...] 中被指定为数组,其中数组中的每个目标都是状态节点的相对键或 ID,就像单个目标一样。

¥Multiple targets are specified as an array in target: [...], where each target in the array is a relative key or an ID to a state node, just like single targets.























 





const settingsMachine = createMachine({
  id: 'settings',
  type: 'parallel',
  states: {
    mode: {
      initial: 'active',
      states: {
        inactive: {},
        pending: {},
        active: {}
      }
    },
    status: {
      initial: 'enabled',
      states: {
        disabled: {},
        enabled: {}
      }
    }
  },
  on: {
    // Multiple targets
    DEACTIVATE: {
      target: ['.mode.inactive', '.status.disabled']
    }
  }
});

# 通配符描述符 4.7+

¥Wildcard Descriptors 4.7+

使用通配符事件描述符 ("*") 指定的转换可由任何事件激活。这意味着任何事件都将与具有 on: { "*": ... } 的转换相匹配,并且如果守卫通过,则将进行该转换。

¥A transition that is specified with a wildcard event descriptor ("*") is activated by any event. This means that any event will match the transition that has on: { "*": ... }, and if the guards pass, that transition will be taken.

除非在数组中指定转换,否则将始终选择显式事件描述符而不是通配符事件描述符。在这种情况下,转换的顺序决定了选择哪个转换。

¥Explicit event descriptors will always be chosen over wildcard event descriptors, unless the transitions are specified in an array. In that case, the order of the transitions determines which transition gets chosen.



 




 




// For SOME_EVENT, the explicit transition to "here" will be taken
on: {
  "*": "elsewhere",
  "SOME_EVENT": "here"
}

// For SOME_EVENT, the wildcard transition to "elsewhere" will be taken
on: [
  { event: "*", target: "elsewhere" },
  { event: "SOME_EVENT", target: "here" },
]

提示

通配符描述符的行为方式与 瞬态转变(具有空事件描述符)不同。尽管每当状态处于活动状态时都会立即进行瞬态转换,但通配符转换仍然需要将某些事件发送到其状态才能触发。

¥Wildcard descriptors do not behave the same way as transient transitions (with null event descriptors). Whereas transient transitions will be taken immediately whenever the state is active, wildcard transitions still need some event to be sent to its state to be triggered.

示例:

¥Example:







 
 













const quietMachine = createMachine({
  id: 'quiet',
  initial: 'idle',
  states: {
    idle: {
      on: {
        WHISPER: undefined,
        // On any event besides a WHISPER, transition to the 'disturbed' state
        '*': 'disturbed'
      }
    },
    disturbed: {}
  }
});

quietMachine.transition(quietMachine.initialState, { type: 'WHISPER' });
// => State { value: 'idle' }

quietMachine.transition(quietMachine.initialState, { type: 'SOME_EVENT' });
// => State { value: 'disturbed' }

# 常见问题解答

¥FAQ's

# 如何在转换中执行 if/else 逻辑?

¥How do I do if/else logic on transitions?

有时,你会想说:

¥Sometimes, you'll want to say:

  • 如果某事为真,则进入此状态

    ¥If something is true, go to this state

  • 如果其他情况为真,则进入此状态

    ¥If something else is true, go to this state

  • 否则就进入这个状态

    ¥Else, go to this state

你可以使用 保护跃迁 来实现此目的。

¥You can use guarded transitions to achieve this.

# 我如何过渡到任何状态?

¥How do I transition to any state?

你可以通过为状态指定一个自定义 ID 并使用 target: '#customId' 来转换到任何状态。你可以阅读 有关自定义 ID 的完整文档请参见此处

¥You can transition to any state by giving that state a custom id, and using target: '#customId'. You can read the full docs on custom IDs here.

这允许你从子状态转换到父级的兄弟状态,例如本例中的 CANCELdone 事件:

¥This allows you to transition from child states to siblings of parents, for example in the CANCEL and done events in this example: