iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
Software Development

From State Machine to XState系列 第 29

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/


上一篇
Day 28 - XState in React (著重: local state)
下一篇
Day 30 - Finite State Machine x XState 推薦學習資源
系列文
From State Machine to XState31

1 則留言

0

要完賽了 恭喜!

我要留言

立即登入留言