Skip to content

Guards

Guard 就是给 Transition 加条件,让状态机判断是否允许走这条路

Guards具有如下要求:

纯函数(pure)

  • 不依赖外部状态(全局变量、网络请求等)
  • 不产生副作用(不改 context、不调接口、不做日志)
  • 只根据输入参数(context、event)计算返回值

同步(synchronous)

  • guard 必须立即返回结果
  • 不能是 async / Promise
  • 状态机在判断 transition 是否生效时,需要即时得到 true/false

返回布尔值(true / false)

  • true → transition 生效
  • false → transition 被跳过,检查同事件下的下一条 transition

创建guards

  • -
  • parmas:any:调用guards时传入的参数

在createMachine中创建

createMachine 第二个参数里的 guards 对象里统一声明。

ts
const feedbackMachine = createMachine(
  {
    // ...
    states: {
      form: {
        on: {
          'feedback.submit': {
            guard: 'isValid',
            target: 'submitting',
          },
        },
      },
      submitting: {
        // ...
      },
    },
  },
  {
    guards: {
      isValid: ({ context }) => {
        return context.feedback.length > 0;
      },
    },
  },
);

guards 对象中声明的名字可以在整个状态机中复用。

在setup中声明(Implementing guards)

可以将Guards剥离状态机

ts
import { setup } from 'xstate';

const machineSetup = setup({
  guards: {
    isValid: ({ context }) => context.feedback.length > 0,
    isAuthorized: ({ context }) => context.user?.role === 'admin',
  },
});

const feedbackMachine = machineSetup.createMachine({
  states: {
    form: {
      on: {
        'feedback.submit': {
          guard: 'isValid',  // 引用 setup 里声明的 guards
          target: 'submitting',
        },
      },
    },
    submitting: {},
  },
});

事件里直接内联声明(Inline guards)

你可以将守卫定义为内联函数。这对于快速原型设计逻辑很有用,但通常建议使用可序列化的守卫(字符串或对象),以便更好地重用和可视化。

ts
on: {
  event: {
    guard: ({ context, event }) => true,
    target: 'someState'
  }
}

复用已创建的Guards

String

如果你不需要传参数,可以使用字符串简写

ts
on: {
  someEvent: {
    // Equivalent to:
    // guard: { type: 'someGuard' }
    guard: 'someGuard';
  }
}

Guard Object

如果你需要在复用Guards时传参数,可以使用Guard Object

ts
interface Guard{
  type:string
  params:any
}
  • type:guards中定义的函数名
  • params:传入guard的参数
ts
const feedbackMachine = createMachine(
  {
    // ...
    states: {
      // ...
      form: {
        on: {
          submit: {
            guard: { type: 'isValid', params: { maxLength: 50 } },
            target: 'submitting',
          },
        },
      },
      // ...
    },
  },
  {
    guards: {
      isValid: ({ context }, params) => {
        return (
          context.feedback.length > 0 &&
          context.feedback.length <= params.maxLength
        );
      },
    },
  },
);

多重守卫的Transition

如果你希望在某个事件下,根据不同情况跳转到不同状态,可以提供一个守卫转换数组。数组中的每个转换会按顺序进行测试,第一个guard为 true 的transition将被执行。

你可以在数组的最后指定一个默认转换。如果没有任何guard为 true,则会执行默认转换。

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

// 定义状态机上下文类型
interface FeedbackContext {
  feedback?: string;
  sentiment?: 'good' | 'bad';
}

// 定义事件类型
type FeedbackEvent =
  | { type: 'feedback.provide'; feedback: string; sentiment: 'good' | 'bad' };

// 创建状态机
const feedbackMachine = createMachine<FeedbackContext, FeedbackEvent>(
  {
    id: 'feedback',
    initial: 'prompt',
    context: {
      feedback: undefined,
      sentiment: undefined,
    },
    states: {
      prompt: {
        on: {
          'feedback.provide': [
            // 如果 sentimentGood 守卫为 true,则跳转到 thanks
            {
              guard: 'sentimentGood',
              target: 'thanks',
            },
            // 如果 sentimentBad 守卫为 true,则跳转回 form
            {
              guard: 'sentimentBad',
              target: 'form',
            },
            // 默认跳转回 form
            { target: 'form' },
          ],
        },
      },
      form: {
        entry: 'logForm',
      },
      thanks: {
        entry: 'logThanks',
      },
    },
  },
  {
    guards: {
      sentimentGood: (context, event) => event.sentiment === 'good',
      sentimentBad: (context, event) => event.sentiment === 'bad',
    },
    actions: {
      logForm: (context, event) => {
        console.log('Returning to form. Feedback:', event.feedback);
      },
      logThanks: (context, event) => {
        console.log('Thank you for your feedback!', event.feedback);
      },
    },
  }
);

const service = interpret(feedbackMachine).start();

service.send({ type: 'feedback.provide', feedback: 'Nice job!', sentiment: 'good' });
// 输出: Thank you for your feedback! Nice job!

service.send({ type: 'feedback.provide', feedback: 'Not happy', sentiment: 'bad' });
// 输出: Returning to form. Feedback: Not happy

组合 Guards

我们可以组合多个Guards成为一个新的Guards:

  • and([...])—— 当数组中所有守卫都返回true时,结果为true
  • or([...])—— 当数组中任意守卫返回true时,结果为true
  • not(...)—— 当内部守卫返回false时,结果为true
ts
on: {
  event: {
    guard: and(['isValid', 'isAuthorized']);
  }
}

状态内守卫(In-state guards)

你可以使用 stateIn(stateValue) 守卫来检查当前状态是否匹配指定的 stateValue。这在并行状态(parallel states)中最为有用。

ts
on: {
  event: {
    guard: stateIn('#state1');
  },
  anotherEvent: {
    guard: stateIn({ form: 'submitting' })
  }
}

状态内守卫会匹配整个状态机的状态,而不是单个状态节点。对于普通状态,通常不需要使用状态内守卫。最好在设计状态机时,将转换逻辑建模清晰,从而尽量避免首先使用状态内守卫。

提示

状态内守卫(stateIn)确实在很多常规状态机场景下用处不大。它主要是为了处理并行状态或者复杂嵌套状态机时,才有实际价值。