Перейти к содержанию

Узлы состояния

Конечный автомат содержит узлы состояний (state nodes), которые в совокупности описывают общее состояние (state), в котором может находиться автомат. В автомате fetchMachine, описанном в следующем разделе, есть узлы состояний, такие как:

// ...
{
  states: {
    // узел состояния
    idle: {
      on: {
        FETCH: {
          target: 'pending';
        }
      }
    }
  }
}

И общее состояние (state), которое является возвращаемым значением функции machine.transition() или значением обратного вызова service.onTransition():

const nextState = fetchMachine.transition('pending', {
  type: 'FULFILL',
});
// State {
//   value: { success: 'items' },
//   actions: [],
//   context: undefined,
//   ...
// }

Что такое узлы состояния?

В XState узел состояния (state node) определяет конфигурацию состояния. Они определены в свойстве state автомата. Точно так же узлы подсостояния иерархически определены в свойстве states узла состояния.

Состояние, определенное из machine.transition(state, event), представляет собой комбинацию узлов состояния. Например, в автомате ниже есть узел состояния success и узел подсостояния items. Значение состояния {success: 'items'} представляет комбинацию этих узлов состояния.

const fetchMachine = createMachine({
  id: 'fetch',

  // Initial state
  initial: 'idle',

  // States
  states: {
    idle: {
      on: {
        FETCH: { target: 'pending' },
      },
    },
    pending: {
      on: {
        FULFILL: { target: 'success' },
        REJECT: { target: 'failure' },
      },
    },
    success: {
      // Initial child state
      initial: 'items',

      // Child states
      states: {
        items: {
          on: {
            'ITEM.CLICK': { target: 'item' },
          },
        },
        item: {
          on: {
            BACK: { target: 'items' },
          },
        },
      },
    },
    failure: {
      on: {
        RETRY: { target: 'pending' },
      },
    },
  },
});

Типы узлов состояния

Есть пять различных типов узлов состояния:

  • Узел атомарного (atomic) состояния не имеет дочерних состояний.
  • Узел составного (compound) состояния содержит одно или несколько дочерних состояний и имеет начальное состояние, которое является ключом одного из этих дочерних состояний.
  • Узел параллельного (parallel) состояния содержит два или более дочерних состояния и не имеет начального состояния, так как он представляет нахождение во всех своих дочерних состояниях одновременно.
  • Узел конечного (final) состояния — это конечный узел, который представляет абстрактное «конечное» состояние.
  • Узел состояния истории (history) — это абстрактный узел, который представляет преобразование в самое последнее мелкое или глубокое состояние истории своего родительского узла.

Тип узла состояния может быть явно определен на узле состояния:

const machine = createMachine({
  id: 'fetch',
  initial: 'idle',
  states: {
    idle: {
      type: 'atomic',
      on: {
        FETCH: { target: 'pending' },
      },
    },
    pending: {
      type: 'parallel',
      states: {
        resource1: {
          type: 'compound',
          initial: 'pending',
          states: {
            pending: {
              on: {
                'FULFILL.resource1': { target: 'success' },
              },
            },
            success: {
              type: 'final',
            },
          },
        },
        resource2: {
          type: 'compound',
          initial: 'pending',
          states: {
            pending: {
              on: {
                'FULFILL.resource2': { target: 'success' },
              },
            },
            success: {
              type: 'final',
            },
          },
        },
      },
      onDone: 'success',
    },
    success: {
      type: 'compound',
      initial: 'items',
      states: {
        items: {
          on: {
            'ITEM.CLICK': { target: 'item' },
          },
        },
        item: {
          on: {
            BACK: { target: 'items' },
          },
        },
        hist: {
          type: 'history',
          history: 'shallow',
        },
      },
    },
  },
});

Явное указание type как 'atomic', 'compound', 'parallel', 'history' или 'final' полезно для анализа и проверки типов в TypeScript. Однако это требуется только для параллельного, исторического и конечного состояний.

Узлы проходного состояния

Узел проходного состояния (transient state node) — это "проходной" узел состояния, который немедленно переходит к другому узлу состояния; то есть автомат не остается в проходном состоянии. Узлы проходного состояния полезны для определения того, в какое состояние автомат должен действительно перейти из предыдущего состояния на основе условий. Они больше всего похожи на псевдосостояния выбора в UML.

Лучший способ определить узел проходного состояния — это бессобытийное состояние (eventless state) и постоянный переход always. Это переход, при котором немедленно выполняется первое условие, которое оценивается как истинное.

Например, начальное переходное состояние этого автомата преобразуется в 'morning', 'afternoon' или 'evening', в зависимости от того, какое сейчас время (детали реализации скрыты):

const timeOfDayMachine = createMachine(
  {
    id: 'timeOfDay',
    initial: 'unknown',
    context: {
      time: undefined,
    },
    states: {
      // Transient state
      unknown: {
        always: [
          { target: 'morning', cond: 'isBeforeNoon' },
          { target: 'afternoon', cond: 'isBeforeSix' },
          { target: 'evening' },
        ],
      },
      morning: {},
      afternoon: {},
      evening: {},
    },
  },
  {
    guards: {
      isBeforeNoon: {},
      isBeforeSix: {},
    },
  }
);

const timeOfDayService = interpret(
  timeOfDayMachine.withContext({ time: Date.now() })
)
  .onTransition((state) => console.log(state.value))
  .start();

// => 'morning' (assuming the time is before noon)

Метаданные узла состояния

Мета-данные, которые представляют собой статические данные, описывающие соответствующие свойства любого узла состояния, могут быть указаны в свойстве .meta узла состояния:

const fetchMachine = createMachine({
  id: 'fetch',
  initial: 'idle',
  states: {
    idle: {
      on: {
        FETCH: { target: 'loading' },
      },
    },
    loading: {
      after: {
        3000: { target: 'failure.timeout' },
      },
      on: {
        RESOLVE: { target: 'success' },
        REJECT: { target: 'failure' },
        TIMEOUT: { target: 'failure.timeout' }, // manual timeout
      },
      meta: {
        message: 'Loading...',
      },
    },
    success: {
      meta: {
        message: 'The request succeeded!',
      },
    },
    failure: {
      initial: 'rejection',
      states: {
        rejection: {
          meta: {
            message: 'The request failed.',
          },
        },
        timeout: {
          meta: {
            message: 'The request timed out.',
          },
        },
      },
      meta: {
        alert: 'Uh oh.',
      },
    },
  },
});

Текущее состояние машины собирает .meta-данные всех узлов состояния, представленных значением состояния, и помещает их в объект, где:

Теги

Узлы состояния могут иметь теги (tags), которые представляют собой строковые термины, помогающие описать узел состояния. Теги - это метаданные, которые могут быть полезны при классификации узлов различных состояний. Например, вы можете указать, какие узлы состояния представляют состояния, в которых данные загружаются, с помощью тега "loading" и определить, содержит ли состояние эти тегированные узлы состояния с помощью state.hasTag(tag):

const machine = createMachine({
  initial: 'idle',
  states: {
    idle: {
      on: {
        FETCH: 'loadingUser',
      },
    },
    loadingUser: {
      tags: ['loading'],
      // ...
    },
    loadingFriends: {
      tags: ['loading'],
      // ...
    },
    editing: {
      // ...
    },
  },
});

machine.initialState.hasTag('loading');
// => false

machine
  .transition(machine.initialState, 'FETCH')
  .hasTag('loading');
// => true