Day 28 - XState in React (着重: local state)

前面介绍许多 State Machine 及 XState 的功能,由於篇幅不多了,今天想跟大家先快速的介绍一下在 React 中如何使用 XState。

快速小回顾一下我们之前学到的一些 API

本篇范例程序码( example code )皆出自 XState

XState Review

1. createMachine 将定义(config)建立出 FSM

2. interpret 将 FSM 建立出 Instance、Service

import { createMachine, interpret } from 'xstate';
const toggleMachineConfig = {
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: { on: { TOGGLE: 'active' } },
    active: { on: { TOGGLE: 'inactive' } }
  }
};
// createMachine API 把 config、definition  建立成一个 pure function 的状态机
// 让 interpreter 可以使用
const toggleMachine = createMachine(toggleMachineConfig);


// interpret API 把 state machine 建立成一个 instance、service 供现实应用
// 比如像监听 状态的转移、记忆当前状态
const toggleService = interpret(toggleMachine)
  .onTransition((state) => console.log(state.value))
  .start();
// => 'inactive'

// 比如像 发送事件
toggleService.send('TOGGLE');
// => 'active'
toggleService.send('TOGGLE');

3. config 可以描述 阶层式状态

4. config 可以描述 平行式状态

5. config 可以透过 context 共享资料

6. config 可以透过 Guard 保卫状态转移

7. config 可以透过 Action 执行 Side Effect


XState in React

1. useMachine API

useMachine 可以在这个 Component 的生命周期里,将我们经过 createMachine 的一台 FSM 启动成为一个服务。

可以从 useMachine 回传的一组 Tuple ,拿到下面 3 个东西

  1. state 这个 service 当下的 State 物件,很常被命名为 current 或 currentState
    (其实也就是前面 Demo XState 时一样的 State Object)
    state.value 拿到 当前 state;
    state.matches('stateName') 回传 true / false 告诉我们当前是不是我们期待的 state
  2. send 派发事件
    send('eventName') 可以在该 service 发送事件
  3. service 这个 FSM 产生的 service 本人
import { useMachine } from '@xstate/react';
import { toggleMachine } from '../path/to/toggleMachine';

function Toggle() {
  const [state, send, service] = useMachine(toggleMachine, {...someExtraOptions});
  // someExtraOptions 就像是我们之前学过得挂载 Actions 跟 Guards
  return (
    <button onClick={() => send('TOGGLE')}>
      {state.matches('inactive') ? 'Off' : 'On'}
    </button>
  );
}

useMachine 像是 useState 、 useEffect ,常被使用於 Component 的 Local State!

2. useService or useActor

有时候,我们也会想将这个 Machine Instance 作为 props 传给子元件使用,此时底下的子元件就需要透过 useService 及 useActor 来使用这个被建立好的 instance

import { useMachine } from '@xstate/react';
import { toggleMachine } from '../path/to/toggleMachine';

function ParentComponent() {
  const [state, send, service] = useMachine(toggleMachine);
  return (
    <div>
      <ChildComponent serivce={service} />
    </div>
  );
};

由於 Service 也是 Actor ,useService 的 API 将会在 V5 被弃用,原本透过 send event 额外带的资料,方式也从 send('TOGGLE', extraData) 改为 send({ type: 'TOGGLE', ...extraData })

    import { useActor, useService } from '@xstate/react';
    function ChildComponent({service}) {
-     const [state, send] = useService(service);  // V5 将被弃用
+     const [state, send] = useActor(service);
+     const extraData = { x:0,y:1 };
      return (
-       <button onClick={() => send('TOGGLE', extraData)}>
+       <button onClick={() => send({ type: 'TOGGLE', ...extraData })}>
          {state.matches('inactive') ? 'Off' : 'On'}
        </button>
      );
    }

3. Side Effect in React x XState

如果我们想要在某个 transition 前後执行 side effect 时,前面我们知道要使用 action ,并在里面订定 side effect 要做什麽,但假如今天 我们的 side effect 是想要与 React 元件互动的话...

3.1 我的 action 要被当成 React 里的 useEffect 执行 -> asEffect

XState 也提供 asEffect 这个 API,当 transition 发生时,action 不会马上被执行,他会被作为 useEffect 里的 side effect 被执行。

类似API 如 useLayoutEffect 也有对应的 asLayoutEffect

const machine = createMachine({
  initial: 'focused',
  states: {
    focused: {
      entry: 'focus'
    }
  }
});

const Input = () => {
  const inputRef = useRef(null);
  const [state, send] = useMachine(machine, {
    actions: {
      focus: asEffect((context, event) => {
        inputRef.current && inputRef.current.focus();
      })
    }
  });

  return <input ref={inputRef} />;
};

3.2 要执行的 side effect 是在 React 里面的其他 hook function。

比如点了某个 Toggle button 进入状态转换,转换到下个状态前,想要用 React Router 导向其他页面。

我们先在 Machine Config 定义 action 的名称 goToOtherPage

import { createMachine } from 'xstate';

export const machine = createMachine({
  initial: 'toggledOff',
  states: {
    toggledOff: {
      on: {
        TOGGLE: 'toggledOn'
      }
    },
    toggledOn: {
      entry: ['goToOtherPage']
    }
  }
});

再将 goToOtherPage 的实作放在 React 元件里

import { machine } from './machine';
import { useMachine } from '@xstate/react';
import { useHistory } from 'react-router';

const Component = () => {
  const history = useHistory();

  const [state, send] = useMachine(machine, {
    actions: {
      goToOtherPage: () => {
        history.push('/other-page');
      }
    }
  });

  return null;
};

3.3 透过 useEffect 将 XState 状态机 与 React 的资料更新同步

有时我们进行 AJAX 获取资料,像使用 SWR, React-Query 等,会非同步向外部取得资料。
此时我们可以藉由 useEffect 当资料发生改变时,就打出一个 send Event ,将新的资料传入 machine instance 里。

const Component = () => {
  const { data, error } = useSWR('/api/user', fetcher);

  const [state, send] = useMachine(machine);

  useEffect(() => {
    send({
      type: 'DATA_CHANGED',
      data,
      error
    });
  }, [data, error, send]);
};

4. 动态 JSX 的 UI 展现

也可以透过 switch / caseif / else 、三元运算式state==='some'? <A /> : <B /> ,搭配 XState 的 State Object

可以使用 state.value 或 state.matches('someState')

  switch (state.value) {
    case 'idle':
      return (
        <button onClick={() => send('FETCH', { query: 'something' })}>
          Search for something
        </button>
      );
    case 'loading':
      return <div>Searching...</div>;
    case 'success':
      return <div>Success! Data: {state.context.data}</div>;
    case 'failure':
      return (
        <>
          <p>{state.context.error.message}</p>
          <button onClick={() => send('RETRY')}>Retry</button>
        </>
      );
    default:
      return null;
  }

参考资料

https://xstate.js.org/docs/recipes/react.html
https://xstate.js.org/docs/packages/xstate-react/


<<:  创建App-上传App

>>:  聊聊 SaaS 有哪些吧!

Day 20 - 将 NEWS 後台储存资料提取後,送至前台渲染画面 (下) - News View Page InnerText 应用 - ASP.NET Web Forms C#

=x= 🌵 NEWS 前台 View 页面後端功能制作。 NEWS View 页面资料介绍 : 📌 ...

[Day 19] 阿嬷都看得懂的盒模型

阿嬷都看得懂的盒模型 各位阿嬷,我们今天要来寄自己腌渍的酱瓜给乖孙。 我们找来 4 个纸盒,想在里面...

Day 3 ARM的多样性与开发环境

一般来说,ARM组合语言,目前大部分是称作嵌入式系统,大部分会说它是写到韧体里面的程序语言,就是硬体...

Day 27: Incremental build

这系列的程序码在 https://github.com/DanSnow/ironman-2020/...

25.移转 Aras PLM大小事-流程签核动态指派(4)

流程指派这件事情,还会依据流程关卡不同,有不同方向走法 像下图用自动节点指向两种路线 同样套用程序J...