ストア

Framework7には、軽量のアプリケーション状態管理ライブラリである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);
        }
      }
    })

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

    ストアの使用

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

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

    <template>
      <!-- storeをアプリの "store "プロップに渡します。 -->
      <f7-app store={store}>
        <f7-view main>
          <!-- ... -->
        </f7-view>
      </f7-app>
    </template>
    <script>
    // インポートされたストア
    import store from 'path/to/store.js';
    
    export default {
      setup() {
        return {
          store,
        }
      }
    }
    </script>
    

    ストアとステートへのアクセス

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

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

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

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

    アクションの実行

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

    例えば、以下のようなstoreアクションがあるとします。

    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';
    
    // getUsers'アクションを呼び出す
    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 に直接アクセスし、そうでない場合はゲッターを使用してください。

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

    Vueコンポーネントで使用するための特別な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;
        }
      },
    });

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

    <template>
      <f7-page>
        <f7-list>
          <f7-list-item v-for="user in users" :title="user.name" />
        </f7-list>
      </f7-page>
    </template>
    <script>
      import { onMounted } from 'vue';
      // import special useStore helper/hook
      import { useStore } from 'framework7-vue';
      // インポートストア
      import store from 'path/to/store.js'
    
      export default {
        setup() {
          // retrieve "users" ゲッターハンドラの値。最初は空の配列
          const users = useStore('users');
    
          onMounted(() => {
            // コンポーネント実装時にユーザーをロードする
            store.dispatch('getUsers');
          });
    
          return {
            users,
          }
        }
      }
    </script>

    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;
    <template>
      <f7-page>
        <f7-navbar title="Store" />
        <f7-list v-if="users.length">
          <f7-list-item v-for="user in users" :title="user" />
        </f7-list>
        <f7-block v-else strong>
          <f7-button fill preloader :loading="loading" @click="loadUsers"> Load Users </f7-button>
        </f7-block>
      </f7-page>
    </template>
    <script>
    import { useStore } from 'framework7-vue';
    import store from './store';
    
    export default {
      setup() {
        const loading = useStore('loading');
        const users = useStore('users');
    
        const loadUsers = () => {
          store.dispatch('getUsers');
        };
    
        return {
          loading,
          users,
          loadUsers,
        };
      },
    };
    </script>