# 受保护的转换

¥Guarded Transitions

很多时候,你希望仅在满足状态(有限或扩展)或事件的某些条件时才发生状态之间的转换。例如,假设你正在为搜索表单创建一台机器,并且你只希望在以下情况下允许搜索:

¥Many times, you'll want a transition between states to only take place if certain conditions on the state (finite or extended) or the event are met. For instance, let's say you're creating a machine for a search form, and you only want search to be allowed if:

  • 允许用户搜索(本例中为 .canSearch

    ¥the user is allowed to search (.canSearch in this example)

  • 搜索事件 query 不为空。

    ¥the search event query is not empty.

对于 "保护转变" 来说,这是一个很好的用例,它是仅在某些条件 (cond) 通过时才会发生的转换。带有条件的转换称为受保护的转换。

¥This is a good use case for a "guarded transition", which is a transition that only occurs if some condition (cond) passes. A transition with condition(s) is called a guarded transition.

# 守卫(条件函数)

¥Guards (Condition Functions)

在转换的 .cond 属性上指定的条件函数(也称为防护),作为具有 { type: '...' } 属性的字符串或条件对象,并采用 3 个参数:

¥A condition function (also known as a guard) specified on the .cond property of a transition, as a string or condition object with a { type: '...' } property, and takes 3 arguments:

争论 类型 描述
context object 机器环境
event object 触发条件的事件
condMeta object 元数据(见下文)

condMeta 对象包括以下属性:

¥The condMeta object includes the following properties:

  • cond - 原始条件对象

    ¥cond - the original condition object

  • state - 转换前的当前机器状态

    ¥state - the current machine state, before transition

  • _event - SCXML 事件

    ¥_event - the SCXML event

返回

¥Returns

truefalse,决定是否允许进行转换。

¥true or false, which determines whether the transition should be allowed to take place.















 
 













 
 
 
 
 











const searchValid = (context, event) => {
  return context.canSearch && event.query && event.query.length > 0;
};

const searchMachine = createMachine(
  {
    id: 'search',
    initial: 'idle',
    context: {
      canSearch: true
    },
    states: {
      idle: {
        on: {
          SEARCH: [
            {
              target: 'searching',
              // Only transition to 'searching' if the guard (cond) evaluates to true
              cond: searchValid // or { type: 'searchValid' }
            },
            { target: '.invalid' }
          ]
        },
        initial: 'normal',
        states: {
          normal: {},
          invalid: {}
        }
      },
      searching: {
        entry: 'executeSearch'
        // ...
      },
      searchError: {
        // ...
      }
    }
  },
  {
    guards: {
      searchValid // optional, if the implementation doesn't change
    }
  }
);

单击事件选项卡并发送类似下面的 { "type": "SEARCH", "query": "something" } 的事件:

¥Click the EVENTS tab and send an event like { "type": "SEARCH", "query": "something" } below:

如果 cond 防护返回 false,则不会选择转换,并且不会从该状态节点发生任何转换。如果子状态中的所有转换都有评估为 false 并阻止它们被选择的保护,则 event 将传播到父状态并在那里进行处理。

¥If the cond guard returns false, then the transition will not be selected, and no transition will take place from that state node. If all transitions in a child state have guards that evaluate to false and prevent them from being selected, the event will be propagated up to the parent state and handled there.

context 的使用示例:

¥Example of usage with context:

import { interpret } from 'xstate';

const searchService = interpret(searchMachine)
  .onTransition((state) => console.log(state.value))
  .start();

searchService.send({ type: 'SEARCH', query: '' });
// => 'idle'

searchService.send({ type: 'SEARCH', query: 'something' });
// => 'searching'

提示

通过直接在机器配置中内联指定 Guard cond 函数,可以快速原型化 Guard 实现:

¥Guard implementations can be quickly prototyped by specifying the guard cond function inline, directly in the machine config:




 



// ...
SEARCH: {
  target: 'searching',
  cond: (context, event) => context.canSearch && event.query && event.query.length > 0
}
// ...

在机器选项的 guards 属性中重构内联防护实现,可以更轻松地调试、序列化、测试和准确地可视化防护。

¥Refactoring inline guard implementations in the guards property of the machine options makes it easier to debug, serialize, test, and accurately visualize guards.

# 序列化守卫

¥Serializing Guards

守卫可以(并且应该)被序列化为字符串或具有 { type: '...' } 属性的对象。防护的实现细节在机器选项的 guards 属性上指定,其中 key 是防护 type(指定为字符串或对象),值是一个带有三个参数的函数:

¥Guards can (and should) be serialized as a string or an object with the { type: '...' } property. The implementation details of the guard are specified on the guards property of the machine options, where the key is the guard type (specified as a string or object) and the value is a function that takes three arguments:

  • context - 当前机器上下文

    ¥context - the current machine context

  • event - 触发(潜在)转换的事件

    ¥event - the event that triggered the (potential) transition

  • guardMeta - 包含有关保护和转换的元数据的对象,包括:

    ¥guardMeta - an object containing meta data about the guard and transition, including:

    • cond - 原始 cond 对象

      ¥cond - the original cond object

    • state - 转换前的当前机器状态

      ¥state - the current machine state, before transition

重构上面的例子:

¥Refactoring the above example:









 
 
 







 
 
 
 
 



const searchMachine = createMachine(
  {
    // ...
    states: {
      idle: {
        on: {
          SEARCH: {
            target: 'searching',
            // The 'searchValid' guard implementation details are
            // specified in the machine config
            cond: 'searchValid' // or { type: 'searchValid' }
          }
        }
      }
      // ...
    }
  },
  {
    guards: {
      searchValid: (context, event) => {
        return context.canSearch && event.query && event.query.length > 0;
      }
    }
  }
);

# 定制守卫 4.4+

¥Custom Guards 4.4+

有时,最好不仅在 JSON 中序列化状态转换,而且还要序列化保护逻辑。这是将守卫序列化为对象很有帮助的地方,因为对象可能包含相关数据:

¥Sometimes, it is preferable to not only serialize state transitions in JSON, but guard logic as well. This is where serializing guards as objects is helpful, as objects may contain relevant data:









 
 
 
 
 







 
 
 
 
 
 
 
 
 
 



const searchMachine = createMachine(
  {
    // ...
    states: {
      idle: {
        on: {
          SEARCH: {
            target: 'searching',
            // Custom guard object
            cond: {
              type: 'searchValid',
              minQueryLength: 3
            }
          }
        }
      }
      // ...
    }
  },
  {
    guards: {
      searchValid: (context, event, { cond }) => {
        // cond === { type: 'searchValid', minQueryLength: 3 }
        return (
          context.canSearch &&
          event.query &&
          event.query.length > cond.minQueryLength
        );
      }
    }
  }
);

# 多重守卫

¥Multiple Guards

如果你希望在某些情况下将单个事件转换为不同的状态,你可以提供一组条件转换。每个转换将按顺序进行测试,并且将采用 cond 防护评估为 true 的第一个转换。

¥If you want to have a single event transition to different states in certain situations you can supply an array of conditional transitions. Each transition will be tested in order, and the first transition whose cond guard evaluates to true will be taken.

例如,你可以对一个门进行建模,该门监听 OPEN 事件,如果你是管理员,则进入 'opened' 状态;如果 alert-ing 为 true,则进入 'closed.error' 状态;否则进入 'closed.idle' 状态。

¥For example, you can model a door that listens for an OPEN event, goes to the 'opened' state if you are an admin, or goes to the 'closed.error' state if alert-ing is true, or goes to the 'closed.idle' state otherwise.

























 
 
 












































import { createMachine, actions, interpret, assign } from 'xstate';

const doorMachine = createMachine(
  {
    id: 'door',
    initial: 'closed',
    context: {
      level: 'user',
      alert: false // alert when intrusions happen
    },
    states: {
      closed: {
        initial: 'idle',
        states: {
          idle: {},
          error: {}
        },
        on: {
          SET_ADMIN: {
            actions: assign({ level: 'admin' })
          },
          SET_ALARM: {
            actions: assign({ alert: true })
          },
          OPEN: [
            // Transitions are tested one at a time.
            // The first valid transition will be taken.
            { target: 'opened', cond: 'isAdmin' },
            { target: '.error', cond: 'shouldAlert' },
            { target: '.idle' }
          ]
        }
      },
      opened: {
        on: {
          CLOSE: { target: 'closed' }
        }
      }
    }
  },
  {
    guards: {
      isAdmin: (context) => context.level === 'admin',
      shouldAlert: (context) => context.alert === true
    }
  }
);

const doorService = interpret(doorMachine)
  .onTransition((state) => console.log(state.value))
  .start();
// => { closed: 'idle' }

doorService.send({ type: 'OPEN' });
// => { closed: 'idle' }

doorService.send({ type: 'SET_ALARM' });
// => { closed: 'idle' }
// (state does not change, but context changes)

doorService.send({ type: 'OPEN' });
// => { closed: 'error' }

doorService.send({ type: 'SET_ADMIN' });
// => { closed: 'error' }
// (state does not change, but context changes)

doorService.send({ type: 'OPEN' });
// => 'opened'
// (since context.isAdmin === true)

警告

cond 函数必须始终是仅引用 contextevent 参数的纯函数。

¥The cond function must always be a pure function that only references the context and event arguments.

提示

不要过度使用保护条件。如果某件事可以离散地表示为两个或多个单独的事件,而不是单个事件上的多个 conds,则最好避免 cond 并使用多种类型的事件。

¥Do not overuse guard conditions. If something can be represented discretely as two or more separate events instead of multiple conds on a single event, it is preferable to avoid cond and use multiple types of events instead.

# "处于状态" 守卫

¥"In State" Guards

当且仅当该状态节点在当前状态下处于活动状态时,in 属性将状态 ID 作为参数并返回 true。例如,我们可以给红绿灯机添加一个守卫:

¥The in property takes a state ID as an argument and returns true if and only if that state node is active in the current state. For example, we can add a guard to the traffic light machine:
























 















const lightMachine = createMachine({
  id: 'light',
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: { target: 'yellow' }
      }
    },
    yellow: {
      on: {
        TIMER: { target: 'red' }
      }
    },
    red: {
      initial: 'walk',
      states: {
        walk: {
          /* ... */
        },
        wait: {
          /* ... */
        },
        stop: {
          /* ... */
        }
      },
      on: {
        TIMER: [
          {
            target: 'green',
            in: '#light.red.stop'
          }
        ]
      }
    }
  }
});

in 状态防护与其他 cond 防护一起出现在同一转换中时,所有防护必须评估为 true 才能进行转换。

¥When an in-state guard is present with other cond guards in the same transition, all guards must evaluate to true for the transition to be taken.

提示

使用 "处于状态" 防护装置通常表明机器可以进行重构,从而无需使用它们。尽可能避免 "处于状态" 守卫。

¥Using "in state" guards is usually a sign that the machine can be refactored in a way that makes their usage unnecessary. Prefer avoiding "in state" guards when possible.