在 React 提到 Global State 时,大家很常会想到 React context,没错我就是要使用它!
由於 React 跟 XState 都使用到 context 一词,刚看文件时,在这里很容易混淆。希望在这第 29 天,我的读者们。已经对个词有点概念了。
本篇范例程序码( 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);
}
);
// ...
};
很多时候,我们拿到的资料或状态不能直接使用,我们很常需要进行一些运算拿到我们需要的资料(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
今天要来补充Day08的package.json的部分。 指路--->https://ithe...
前天我提到了希望可以研究一下如何做 playbook 的模组化,今天就来整理一下有哪些方式可以帮助我...
关於辞职,前前後後问了很多前辈。其中一个只见过一次面的 D桑 ,对我影响很大。 跟 D 桑是在某个 ...
今天是延续昨天的 Hooks 探索,要学习的是 useReducer 、useCallback、us...
在 昨天很多的教学後, 这次来个雷同的WindowsMenum的建立,直接上程序码吧! <!-...