Day 29 - XState in React 2 (着重: global state and performance concerned)

1. XState as Global State in React

在 React 提到 Global State 时,大家很常会想到 React context,没错我就是要使用它!
由於 React 跟 XState 都使用到 context 一词,刚看文件时,在这里很容易混淆。希望在这第 29 天,我的读者们。已经对个词有点概念了。

Context

  1. Context in XState 指得是那些被 Machine 共享的资料、延伸状态 (Extended State)。
  2. Context in React 指得是那些不透过 props 传递,能跨 许多层级的 Component Tree 共享的资料。

How to...

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

简单来说,我们就只要将 Machine Instance (或叫做 Service, Actor)存入 React Context 中,即可跨元件共享资料了!

import React, { createContext } from 'react';
import { useMachine } from '@xstate/react';
import { authMachine } from './authMachine';

export const GlobalStateContext = createContext({});

export const GlobalStateProvider = (props) => {
  const authService = useMachine(authMachine);

  return (
    <GlobalStateContext.Provider value={{ authService }}>
      {props.children}
    </GlobalStateContext.Provider>
  );
};

但我们知道 React context 的值一改变,底下所有 children 都会被重新渲染( rerender ),而 authService 一词顾名思义,它并非 Primitive Type ( string, number, boolean... ),我们在昨天提到 useMachine 是回传一个 instance ,并且活在一个 Component 的生命周期里,也就是说当我们的元件被 unmount 时,就会销毁,当我们不断 rerender ,就会一直建立新的 instance。这在 React context 下的使用会非常棘手,我们需要考虑可能会造成「效能不佳」的问题。

幸运的是,XState 提供我们另外一个 API - useInterpret ,它会回传我们一个 service ,不过这个 serive 是一个静态的 reference 。(也就是说,他不会随着我们的生命周期而不断销毁、重建!)

  export const GlobalStateProvider = (props) => {
-   const authService = useMachine(authMachine);
+   const authService = useInterpret(authMachine);

    return (
      <GlobalStateContext.Provider value={{ authService }}>
        {props.children}
      </GlobalStateContext.Provider>
    );
  };

而底下要使用的人就是透过 useContext 取得 instance ,再透过 useActor 与它建立互动即可。

import React, { useContext } from 'react';
import { GlobalStateContext } from './globalState';
import { useActor } from '@xstate/react';

export const SomeComponent = (props) => {
  const globalServices = useContext(GlobalStateContext);
  const [state] = useActor(globalServices.authService);

  return state.matches('loggedIn') ? 'Logged In' : 'Logged Out';
};

而也正因为 useInterpret, 回传的是一个 static reference ,如果有需要的话,它也允许使用 observer 来订阅这个 service 。可以监听 状态的变化 并透过 observer 执行对应的操作。

const App = () => {
  const service = useInterpret(
    someMachine,
    extraOptions,
    // 用 observer 订阅 instance ,当状态改变就执行 console.log
    (state) => {
      console.log(state);
    }
  );
  // ...
};

2. 增进效能

很多时候,我们拿到的资料或状态不能直接使用,我们很常需要进行一些运算拿到我们需要的资料(Derived data)
如 Redux 官方提供 selector,让我们能把 store 里的一些 data 先进行一些运算,再回传需要的资料给我们。

假如资料没改变,Selector 让我们能限制资料的运算、进而降低重新渲扰的次数、也帮助我们避免这些不需要的重复计算。

以上述原本的例子,我们原本透过 useActor 拿到的 state "Object" ,其实我们在意的也就是指当前状态是不是 'loggedIn' 而已,一样的概念,此时我们便可以透过 XState 的 selector 来帮忙,降低我们重新渲染的次数。

  import React, { useContext } from 'react';
  import { GlobalStateContext } from './globalState';
- import { useActor } from '@xstate/react';
+ import { useSelector } from '@xstate/react';

+ const loggedInSelector = (state) => {
+   return state.matches('loggedIn');
+ };
+
  export const SomeComponent = (props) => {
    const globalServices = useContext(GlobalStateContext);
-   const [state] = useActor(globalServices.authService);
+   const isLoggedIn = useSelector(globalServices.authService, loggedInSelector);

-   return state.matches('loggedIn') ? 'Logged In' : 'Logged Out';
+   return isLoggedIn ? 'Logged In' : 'Logged Out';
  };

这样子,只有当 state.matches('loggedIn') 回传不同的结果,才会使 Component 重新渲染。比起使用 useActor ,在这个情境下,透过 useSelector 是官方推荐的使用方式。

此时假设有发送 event 的需求怎麽办?

还记得之前我们是透过 const [state, send] = useActor 拿到 send 方法来发送事件吗?
我们在这里可以透过解构 const { send } = globalServices.authService ,直接从 context 的 serice 拿出 send 方法来使用。或者直接执行 globalServices.authService.send('LOG_OUT')

明天会跟大家分享一点点 Redux 跟 XState 的比较 and 推荐一些学习资源让大家能更进一步学习。

ya~~ 最後一天罗

学习愉快 ^^

参考资料

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


<<:  Day29 procfs, sysfs, debugfs

>>:  Day30:今天来聊一下CEH中讲的Mail Cryptography

Day 10-不专业介绍package.jason

今天要来补充Day08的package.json的部分。 指路--->https://ithe...

Day 29:Playbook 的模组化

前天我提到了希望可以研究一下如何做 playbook 的模组化,今天就来整理一下有哪些方式可以帮助我...

离职倒数17天:幸福感是相对而来的,「好」是要高於身边最常接触的人的平均值。

关於辞职,前前後後问了很多前辈。其中一个只见过一次面的 D桑 ,对我影响很大。 跟 D 桑是在某个 ...

认识 React Hooks 之二

今天是延续昨天的 Hooks 探索,要学习的是 useReducer 、useCallback、us...

@Day14 | C# WixToolset + WPF 帅到不行的安装包 [Windows菜单捷径]

在 昨天很多的教学後, 这次来个雷同的WindowsMenum的建立,直接上程序码吧! <!-...