# 活动

¥Activities

已弃用

活动已被弃用,并将在 XState 版本 5 中删除。推荐的方法是改为 调用角色

¥Activities are deprecated and will be removed in XState version 5. The recommended approach is to invoke an actor instead:

-activities: [(context, event) => {
-  // do something
-
-  return () => {/* cleanup */}
-}],
+invoke: {
+  src: (context, event) => (sendBack, receive) => {
+    // do something
+
+    return () => {/* cleanup */}
+  }
+}

活动是随时间发生的动作,可以启动和停止。根据 Harel 最初的状态图论文:

¥An activity is an action that occurs over time, and can be started and stopped. According to Harel's original statecharts paper:

一项活动总是需要非零的时间,例如发出蜂鸣声、显示或执行冗长的计算。

¥An activity always takes a nonzero amount of time, like beeping, displaying, or executing lengthy computations.

例如,当 "beeps" 处于活动状态时,可以通过 'beeping' 活动来表示切换:

¥For example, a toggle that "beeps" when active can be represented by a 'beeping' activity:

const toggleMachine = createMachine(
  {
    id: 'toggle',
    initial: 'inactive',
    states: {
      inactive: {
        on: {
          TOGGLE: { target: 'active' }
        }
      },
      active: {
        // The 'beeping' activity will take place as long as
        // the machine is in the 'active' state
        activities: ['beeping'],
        on: {
          TOGGLE: { target: 'inactive' }
        }
      }
    }
  },
  {
    activities: {
      beeping: () => {
        // Start the beeping activity
        const interval = setInterval(() => console.log('BEEP!'), 1000);

        // Return a function that stops the beeping activity
        return () => clearInterval(interval);
      }
    }
  }
);

在 XState 中,活动在状态节点的 activities 属性上指定。当进入状态节点时,解释器应开始其活动,当退出状态节点时,应停止其活动。

¥In XState, activities are specified on the activities property of a state node. When a state node is entered, an interpreter should start its activities, and when it is exited, it should stop its activities.

为了确定哪些活动当前处于活动状态,State 有一个 activities 属性,该属性是活动名称到 true(如果活动已启动(活动))和 false(如果活动已停止)的映射。

¥To determine which activities are currently active, the State has an activities property, which is a mapping of activity names to true if the activity is started (active), and false if it is stopped.

const lightMachine = createMachine({
  key: 'light',
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: { target: 'yellow' }
      }
    },
    yellow: {
      on: {
        TIMER: { target: 'red' }
      }
    },
    red: {
      initial: 'walk',
      // the 'activateCrosswalkLight' activity is started upon entering
      // the 'light.red' state, and stopped upon exiting it.
      activities: ['activateCrosswalkLight'],
      on: {
        TIMER: { target: 'green' }
      },
      states: {
        walk: {
          on: {
            PED_WAIT: { target: 'wait' }
          }
        },
        wait: {
          // the 'blinkCrosswalkLight' activity is started upon entering
          // the 'light.red.wait' state, and stopped upon exiting it
          // or its parent state.
          activities: ['blinkCrosswalkLight'],
          on: {
            PED_STOP: { target: 'stop' }
          }
        },
        stop: {}
      }
    }
  }
});

在上述机器配置中,当进入 'light.red' 状态时,'activateCrosswalkLight' 将启动。它还将执行特殊的 'xstate.start' 操作,让 service 知道它应该启动该活动:

¥In the above machine configuration, the 'activateCrosswalkLight' will start when the 'light.red' state is entered. It will also execute a special 'xstate.start' action, letting the service know that it should start the activity:

const redState = lightMachine.transition('yellow', { type: 'TIMER' });

redState.activities;
// => {
//   activateCrosswalkLight: true
// }

redState.actions;
// The 'activateCrosswalkLight' activity is started
// => [
//   { type: 'xstate.start', activity: 'activateCrosswalkLight' }
// ]

在同一父状态内进行转换不会重新启动其活动,尽管它可能会启动新的活动:

¥Transitioning within the same parent state will not restart its activities, although it might start new activities:

const redWaitState = lightMachine.transition(redState, { type: 'PED_WAIT' });

redWaitState.activities;
// => {
//   activateCrosswalkLight: true,
//   blinkCrosswalkLight: true
// }

redWaitState.actions;
// The 'blinkCrosswalkLight' activity is started
// Note: the 'activateCrosswalkLight' activity is not restarted
// => [
//   { type: 'xstate.start', activity: 'blinkCrosswalkLight' }
// ]

离开一个状态将停止其活动:

¥Leaving a state will stop its activities:

const redStopState = lightMachine.transition(redWaitState, {
  type: 'PED_STOP'
});

redStopState.activities;
// The 'blinkCrosswalkLight' activity is stopped
// => {
//   activateCrosswalkLight: true,
//   blinkCrosswalkLight: false
// }

redStopState.actions;
// The 'blinkCrosswalkLight' activity is stopped
// => [
//   { type: 'xstate.stop', activity: 'blinkCrosswalkLight' }
// ]

任何停止的活动只会停止一次:

¥And any stopped activities will be stopped only once:

const greenState = lightMachine.transition(redStopState, { type: 'TIMER' });

green.activities;
// No active activities
// => {
//   activateCrosswalkLight: false,
//   blinkCrosswalkLight: false
// }

green.actions;
// The 'activateCrosswalkLight' activity is stopped
// Note: the 'blinkCrosswalkLight' activity is not stopped again
// => [
//   { type: 'xstate.stop', activity: 'activateCrosswalkLight' }
// ]

# 解释

¥Interpretation

在机器选项中,可以在 activities 属性中定义活动的 "start" 和 "stop" 行为。这是通过以下方式完成的:

¥In the machine options, the "start" and "stop" behavior of the activity can be defined in the activities property. This is done by:

  • 传入启动活动的函数(作为副作用)

    ¥Passing in a function that starts the activity (as a side-effect)

  • 从该函数返回另一个停止活动的函数(也是副作用)。

    ¥From that function, returning another function that stops the activity (also as a side-effect).

例如,以下是每隔 context.interval'BEEP!' 记录到控制台的 'beeping' 活动的实现方式:

¥For example, here's how a 'beeping' activity that logs 'BEEP!' to the console every context.interval would be implemented:

function createBeepingActivity(context, activity) {
  // Start the beeping activity
  const interval = setInterval(() => {
    console.log('BEEP!');
  }, context.interval);

  // Return a function that stops the beeping activity
  return () => clearInterval(interval);
}

活动创建者总是有两个参数:

¥The activity creator is always given two arguments:

  • 当前的 context

    ¥the current context

  • 定义的 activity

    ¥the defined activity

    • 例如,{ type: 'beeping' }

      ¥e.g., { type: 'beeping' }

然后,你可以将其传递到 activities 属性下的计算机选项(第二个参数):

¥Then you would pass this into the machine options (second argument) under the activities property:

const toggleMachine = createMachine(
  {
    id: 'toggle',
    initial: 'inactive',
    context: {
      interval: 1000 // beep every second
    },
    states: {
      inactive: {
        on: {
          TOGGLE: { target: 'active' }
        }
      },
      active: {
        activities: ['beeping'],
        on: {
          TOGGLE: { target: 'inactive' }
        }
      }
    }
  },
  {
    activities: {
      beeping: createBeepingActivity
    }
  }
);

使用 XState 的 interpreter,每次发生启动 Activity 的操作时,都会调用该 Activity 创建者来启动 Activity,并使用返回的 "stopper"(如果返回)来停止 Activity:

¥Using XState's interpreter, every time an action occurs to start an activity, it will call that activity creator to start the activity, and use the returned "stopper" (if it is returned) to stop the activity:

import { interpret } from 'xstate';

// ... (previous code)

const service = interpret(toggleMachine);

service.start();

// nothing logged yet

service.send({ type: 'TOGGLE' });

// => 'BEEP!'
// => 'BEEP!'
// => 'BEEP!'
// ...

service.send({ type: 'TOGGLE' });

// no more beeps!

# 重新开始活动

¥Restarting Activities

恢复持久状态 时,默认情况下不会重新启动之前运行的活动。这是为了防止不良和/或意外行为。但是,可以通过在重新启动服务之前将 start(...) 操作添加到持久状态来手动启动活动:

¥When restoring a persisted state, activities that were previously running are not restarted by default. This is to prevent undesirable and/or unexpected behavior. However, activities can be manually started by adding start(...) actions to the persisted state before restarting a service:

import { State, actions } from 'xstate';

// ...

const restoredState = State.create(somePersistedStateJSON);

// Select activities to be restarted
Object.keys(restoredState.activities).forEach((activityKey) => {
  if (restoredState.activities[activityKey]) {
    // Filter activities, and then add the start() action to the restored state
    restoredState.actions.push(actions.start(activityKey));
  }
});

// This will start someService
// with the activities restarted.
someService.start(restoredState);