状態管理

一部のストアフロントアプリケーションでは、グローバル状態 (global state) を管理する方法が要求されます。このために、ReduxMobX などの多くの状態管理ライブラリが提供されています。Salesforce では特定の状態管理ライブラリを推奨していません。状態管理ライブラリを使用しなくても PWA Kit アプリを作成することができます。

状態情報はいつでも props を使用してコンポーネントに渡すことができますが、React の高度な機能を使用して状態管理を行いたい場合のために、このガイドではその他のアプローチも説明しています。

props の使用 

コンポーネント間で状態 (state) を共有するもっとも簡単な方法は、props を通じて状態情報をコンポーネントに渡す方法です。そのコンポーネントは、状態データにアクセスする必要のあるすべてのコンポーネントの上位であることが必要です。この手法は React のドキュメントに詳細に説明されており、lifting state up (state のリフトアップ) と呼ばれています。

React Context API の使用 

React の Context API を使用すると、多数のコンポーネントにデータを提供するプロセスを簡略化できます。アプリのコンポーネントツリーの各レベルで props を通じてデータを手動で渡す必要はありません。

React Context の例 

以下に、すべてのコンポーネントのラッパーとして機能するコンポーネントとともに React Context API を使用して、すべてのコンポーネントに共有状態データを提供する例を示します。

まず、次の 2 つのコンテキストオブジェクトをもつ GlobalState という名前のコンポーネントを作成します:

  1. GlobalStateContext: このオブジェクトにはグローバル状態データを保管します。
  2. GlobalDispatchContext: dispatch 関数。reducer 関数を通じてグローバル状態の更新されたスナップショットを返します。

各コンテキストオブジェクトには、消費するコンポーネントがコンテキストの変更をサブスクライブできるようにする ” コンポーネントが付属しています。ここでは、コンテキストオブジェクトは initialStatedispatch の props で初期化されます。これらの props は、useReducer フックを使用して状態を reducer と接続する際に入手できます。Context API でのフックの使用方法などの React フックの詳細については、公式のReact フックリファレンスのドキュメントを参照してください。

以下に、ストアフロントアプリにグローバル状態を保存するために Context API を使用するコンポーネントを定義および初期化する方法を示します:

1// app/components/_app-config/index.jsx
2
3import {createContext, useContext} from 'react'
4
5export const GlobalContext = createContext()
6export const useGlobalState = () => useContext(GlobalContext)
7
8const AppConfig = (props) => {
9    const [globalState, setGlobalState] = useState({
10        customer: {name: 'Samantha', registered: true},
11    })
12
13    return (
14        <GlobalContext.Provider value={(globalState, setGlobalState)}>
15            {props.children}
16        </GlobalContext.Provider>
17    )
18}
19
20// app/pages/page/index.jsx
21
22import React, {useContext} from 'react'
23import {useGlobalState} from 'components/_app-config'
24
25export const Page = () => {
26    const {customer, setGlobalState} = useGlobalState()
27
28    // Use setGlobalState to change the global state.
29
30    return <div>{customer.name}</div>
31}

app/components/_app-config/index.jsx にある AppConfig 特殊コンポーネントで、状態管理システムを初期化できます。ここでは、props.children にアプリのすべてのコンポーネントが含まれているので、ここに GlobalState コンポーネントを追加します。

まず、初期化に必要な props オブジェクトを取得します。React フック useReducer を使用して、reducer に接続された状態を作成します。useReducer 関数は GlobalState コンポーネントからインポートされた initialState オブジェクトと reducer を受け入れます。次に、接続された状態と、その状態を編集する dispatch 関数を渡します。

以下に例を示します。

1// <PROJECT_DIR>/app/components/_app-config/index.jsx
2
3// Replace your existing React imports
4import React, { useState }, { useReducer }, from "react"
5
6// Import GlobalState component defined in previous example
7import GlobalState, { reducer } from "../global-state"
8
9const initialState = {
10  cart: null
11}
12
13// Combine this with your existing AppConfig component code
14function AppConfig(props) {
15  const [state, dispatch] = useReducer(reducer, initialState);
16
17  return (
18    <GlobalState initialState={state} dispatch={dispatch}>
19      {props.children}
20    </GlobalState>
21  )
22}

これで、更新する機能も含め、任意のコンポーネントにグローバルコンテキストデータへのアクセスを付与できます。GlobalState コンポーネントから GlobalStateContextGlobalDispatchContext をインポートし、これらのコンテキストオブジェクトを useContext() メソッドに提供します。

たとえば、次のようになります。

1import React, {useContext} from 'react'
2import {GlobalStateContext, GlobalDispatchContext, SET_CART_ITEMS} from 'app/components/global-state'
3
4function ExamplePage() {
5    const globalState = useContext(GlobalStateContext)
6    const dispatch = useContext(GlobalDispatchContext)
7
8    // Use this where you want to update the cart data in your global state
9    dispatch({type: SET_CART_ITEMS, payload: cartItems})
10
11    return ( /* render something */ )
12}

状態管理 (State-Management) ライブラリの使用 

app/components/_app-config/index.jsx にあるデフォルトの AppConfig 特殊コンポーネントを上書きして、アプリケーションで状態管理ライブラリを使用するようにカスタマイズできます。

Redux の例 

以下に、AppConfig の内部で PWA のために Redux を設定する方法を示します。コンポーネントには、使用する必要のあるメソッドが 4 つあります:

  1. restore メソッドでは、reducer、初期状態、および任意のミドルウェアを使用して Redux ストアを作成します。
  2. freeze メソッドでは、ページの HTML に埋め込むために状態管理バックエンドを凍結できます。
  3. extraGetPropsArgs メソッドでは、アプリ全体で getProps メソッドに挿入したい追加の引数を返すことができます。この場合、Redux ストアを追加の引数として返すことができます。
  4. render メソッドでは、状態管理ライブラリのコンテキストプロバイダーを設定できます。たとえば、Redux プロバイダーでアプリをラップするのに使用できます。

ここでは、AppConfig のメソッドを使用して、ストアフロントアプリ用の Redux を設定する方法を簡単に示します。完全なソリューションでは、ここで示すステップに加え、reducer、初期状態、および必要なミドルウェアを使用してストアを作成する必要があります。

1// <PROJECT_DIR>/app/components/_app-config/index.jsx
2
3// Combine these imports with your existing imports to include Redux
4imports import React, { useState }, { useReducer }, from "react"
5import PropTypes from "prop-types"
6import { createStore } from "redux"
7import { Provider } from "react-redux"
8
9/**
10 * Combine this code with your existing AppConfig code to use Redux by injecting store object and dispatch function into all getProps() functions.
11 */
12
13class AppConfig extends React.Component {
14  static restore(locals, frozen = {}) {
15    // create your store with a reducer, initial state, and any middleware.
16    const initialState = isServerSide ? {} : frozen
17    locals.store = createStore(reducer, initialState, middlewares)
18  }
19  static freeze(locals) {
20    return locals.store.getState()
21  }
22  static extraGetPropsArgs(locals) {
23    return {
24      store: locals.store,
25      dispatch: locals.store.dispatch
26    }
27  }
28
29  /**
30   * The render function will wrap the app in a Redux provider.
31   */
32  render() {
33    const { children, locals } = this.props
34    return <Provider store={locals.store}>{children}</Provider>
35  }
36}
37
38AppConfig.propTypes = {
39  children: PropTypes.node,
40  locals: PropTypes.object
41}
42
43export default AppConfig