ストア

Framework7には、軽量のアプリケーション状態管理ライブラリであるStoreが組み込まれています。これは、アプリケーション内のすべてのコンポーネントのための一元化されたStoreとして機能します。

VueにはVuex、ReactにはReduxのようなライブラリ固有の状態管理ライブラリを使用したり、Svelteの組み込みストア機能を使用することができます。しかし、シンプルなものが必要な場合には、Framework7 Storeが適しているでしょう。

    ストアの作成

    まず最初にストアを作成しなければなりません。そのために別の store.js ファイルを作成しましょう。

    // まず、CreateStore関数をFramework7 coreからインポートします。
    import { createStore } from 'framework7/lite';
    
    // ストアの作成
    const store = createStore({
      // 状態(ストアデータ)から始める
      state: {
        users: [],
        // ...
      },
    
      // 状態を操作するアクションと、非同期操作のためのアクション
      actions: {
        // ストアの状態を含むコンテキストオブジェクトが引数として渡される
        getUsers({ state }) {
          // APIからユーザーを取得する
          fetch('some-url')
            .then((res) => res.json())
            .then((users) => {
              // 新しいユーザーをストアに割り当てる state.users
              state.users = users;
            })
        },
        // ...
      },
    
      // 状態を取得するゲッター
      getters: {
        // ストアの状態を含むコンテキストオブジェクトは、引数として渡されます。
        users({ state }) {
          return state.users;
        }
      }
    
    })
    
    // ストアのエクスポート
    export default store;

    この例では、次のようなAPI関数を使用しました。

    createStore(storeParameters)- クリエイトストア

    • storeParameters - object. ストアのパラメータを持つオブジェクト

    メソッドは作成されたストアのインスタンスを返します

    ストアのパラメータ

    では、storeParametersオブジェクトを見てみましょう。

    状態

    stateは、すべてのアプリケーションレベルの状態を含む単一のオブジェクトで、「単一の真実の情報源」として機能します。これは、通常、各アプリケーションに対して1つのストアしか持たないことを意味します。単一のステートツリーを使用すると、特定のステートを簡単に見つけることができ、デバッグ目的で現在のアプリケーションの状態のスナップショットを簡単に取ることができます。

    アクション

    アクション」は、ステートを変更したり、非同期操作をしたり、他のストアアクションを呼び出したりするのに使用します。アクションハンドラーは、ストアの状態と、他のアクションを呼び出すためのディスパッチメソッドを持つコンテキストオブジェクトを受け取ります。つまり、context.storeにアクセスしてステートにアクセスしたり、context.dispatchで他のアクションを呼び出したりすることができます。

    第2引数として、アクションハンドラは任意のカスタムデータを受け取ることができます。

    ストアのリアクティブ性を保つために、ステートの変更は代入で行う必要があります。例えば、以下のようになります。

    // 現在の状態のプロパティの変更 - リアクティブではない
    state.users.push(...users);
    
    // 新しい値への代入 - リアクティブ
    state.users = [...state.users, ...users];

    ゲッター

    ゲッター`ハンドラーは、ストアの状態からデータを返すために使用します。また、アイテムのリストをフィルタリングするなど、ストアの状態に基づいて派生した状態を計算する必要がある場合にも便利です。

    const store = createStore({
      state: {
        users: [
          { id: 1, name: '...', registered: true },
          { id: 2, name: '...', registered: false }
        ]
      },
      getters: {
        registeredUsers: ({ state }) => {
          return state.users.filter((user) => user.registered);
        }
      }
    })

    ゲッターハンドラーもコンテキストオブジェクトを受け取りますが、ストアの状態のみを受け取ります。たとえば、ゲッターから他のアクションを呼び出すことはできません。

    ストアの使用

    ストアを作成したら、次はそれをどう使うかを考えましょう。

    まず最初に、作成したストアをメインのAppコンポーネントに渡す必要があります。

    import React from 'react';
    import { App, View } from 'framework7-react';
    // import our store
    import store from 'path/to/store.js';
    
    export const App = () => {
      // ...
      return (
        {/* pass store to the App's "store" prop */ }
        <App store={store}>
          <View main>
            {/* ... */}
          </View>
        </App>
      )
    }
    

    ストアと状態へのアクセス

    作成したstoreのインスタンスを参照することで、store(とその状態)に直接アクセスすることができます。

    import store from 'path/to/store.js';
    
    console.log(store.state.users);

    または、Framework7 のインスタンスの store プロパティにアクセスします。

    import { f7 } from 'framework7-react';
    
    console.log(f7.store.state.users);
    

    アクションの実行

    アクションを呼び出すには、呼び出すアクションの名前を指定して store.dispatch メソッドを呼び出す必要があります。

    以下のようなストアアクションがあるとします。

    const store = createStore({
      // ...
      actions: {
        // ハンドラーは第2引数にカスタムデータを受け取ります
        getUsers({ state }, { total }) {
          fetch(`some-url?total=${total}`)
            .then((res) => res.json())
            .then((users) => {
              state.users = users;
            })
        },
      },
      // ...
    })

    とすると、store.dispatchメソッドを呼び出さなければなりません。

    import store from 'path/to/store.js';
    
    // call 'getUsers' actions
    store.dispatch('getUsers', { total: 10 })

    アクション・ハンドラの中で、別のアクション・ハンドラを呼び出したいとします。

    const store = createStore({
      // ...
      actions: {
        setLoading({ state }, isLoading) {
          state.isLoading = isLoading;
        },
        // ハンドラーコンテキストには "dispatch" メソッドも含まれています
        getUsers({ state, dispatch }, { total }) {
          // 他のアクションを呼び出す
          dispatch('setLoading', true);
          fetch(`some-url?total=${total}`)
            .then((res) => res.json())
            .then((users) => {
              state.users = users;
              // 他のアクションを呼び出す
              dispatch('setLoading', false);
            })
        },
      },
      // ...
    });
    

    ゲッター

    ゲッターの値は store.getters オブジェクトのスタティックなプロパティとしてアクセスできます。

    const store = createStore({
      state: {
        count: 10,
      },
      getters: {
        count({ state }) {
          return state.count;
        },
        double({ state }) {
          return state.count * 2;
        },
      },
    });
    import store from 'path/to/store.js';
    
    const count = store.getters.count;
    const double = store.getters.double;

    ゲッター値は、ゲッターハンドラの結果を含む .value プロパティを持つ静的オブジェクトです。

    console.log(count.value); // -> 10
    console.log(double.value); // -> 20

    ゲッターはステートとは異なり、リアクティブであることが求められます。反応性を必要としない場合は、store.stateに直接アクセスし、そうでない場合はゲッターを使用してください。

    Reactコンポーネントでの使用

    Reactコンポーネントで使用するための特別なuseStoreヘルパーがあります。これはストアをリアクティブに保つためのものです(ステートやゲッターの値が変更されたときにコンポーネントを自動更新します)。

    useStore(getterName)- ゲッターの値を直接返し、状態の更新を購読します。

    • getterName - string - ゲッターハンドラの名前。

    ゲッターハンドラの値を返すメソッド

    他のストアのインスタンスからゲッター値を取得する必要がある場合は、ストアを渡す必要があります。

    useStore(store, getterName)- 直接ゲッター値を返し、状態の更新を購読します。

    • store - store instance - ゲッターを探すためのストア インスタンス。指定されていない場合は、<App>コンポーネントに渡されたデフォルトのストアが使用されます。
    • getterName - string - ゲッターハンドラーの名前です。

    ゲッターハンドラの値を返すメソッド

    以下のようなストアがあるとします。

    const store = createStore({
      state: {
        users: [],
      },
      actions: {
        getUsers({ state }) {
          // ...
        },
      },
      getters: {
        users({ state }) {
          return state.users;
        }
      },
    });

    とすると、例えばReactコンポーネントでは以下のように使用します。

    import React, { useEffect } from 'react';
    // import special useStore helper/hook
    import { useStore, Page, List, ListItem } from 'framework7-react';
    // インポートストア
    import store from 'path/to/store.js'
    
    export const UsersPage = () => {
      // retrieve "users" ゲッターハンドラの値。初期状態では空の配列
      const users = useStore('users');
    
      useEffect(() => {
        // コンポーネント実装時にユーザーをロードする
        store.dispatch('getUsers');
      }, []);
    
      return (
        <Page>
          <List>
            {users.map((user, index) => (
              <ListItem title={user.name} key={index} />
            ))}
          </List>
        </Page>
      )
    }

    Framework7のuseStoreヘルパー/フックを使用しているので、ユーザーがロードされるとコンポーネントが自動更新されます。

    Examples

    import { createStore } from 'framework7/lite';
    
    const store = createStore({
      state: {
        loading: false,
        users: [],
      },
      actions: {
        getUsers({ state }) {
          state.loading = true;
          setTimeout(() => {
            state.users = ['User 1', 'User 2', 'User 3', 'User 4', 'User 5'];
            state.loading = false;
          }, 3000);
        },
      },
      getters: {
        loading({ state }) {
          return state.loading;
        },
        users({ state }) {
          return state.users;
        },
      },
    });
    
    export default store;
    import React from 'react';
    import { useStore, Page, Navbar, Block, List, ListItem, Button } from 'framework7-react';
    import store from './store';
    
    export default () => {
      const loading = useStore('loading');
      const users = useStore('users');
    
      const loadUsers = () => {
        store.dispatch('getUsers');
      };
    
      return (
        <Page>
          <Navbar title="Store" />
          {users.length && (
            <List>
              {users.map((user) => (
                <ListItem title={user} key={user} />
              ))}
            </List>
          )}
          {!users.length && (
            <Block strong>
              <Button fill preloader loading={loading} onClick={loadUsers}>
                Load Users
              </Button>
            </Block>
          )}
        </Page>
      );
    };