ストア

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

Vue 用の Vuex、React 用の Redux などのライブラリ固有の状態管理ライブラリを使用したり、組み込みの Svelte ストア機能を使用したりできます。しかし、単純なものが required な場合は、Framework7 ストアが適している可能性があります。

ストアの作成

まず、ストアを作成する必要があります。そのためには、別の `store.js` ファイルを作成しましょう

// First import createStore function from Framework7 core
import { createStore } from 'framework7/lite';

// create store
const store = createStore({
  // start with the state (store data)
  state: {
    users: [],
    // ...
  },

  // actions to operate with state and for async manipulations
  actions: {
    // context object containing store state will be passed as an argument
    getUsers({ state }) {
      // fetch users from API
      fetch('some-url')
        .then((res) => res.json())
        .then((users) => {
          // assign new users to store state.users
          state.users = users;
        })
    },
    // ...
  },

  // getters to retrieve the state
  getters: {
    // context object containing store state will be passed as an argument
    users({ state }) {
      return state.users;
    }
  }

})

// export store
export default store;

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

createStore(storeParameters)- ストアを作成する

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

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

ストアパラメータ

次に、`storeParameters` オブジェクトを見てみましょう

状態

`state` は、アプリケーションレベルのすべての状態を含む単一のオブジェクトであり、「唯一の真実のソース」として機能します。これは通常、アプリケーションごとに 1 つのストアしか持たないことも意味します。単一の状態ツリーにより、特定の状態を簡単に見つけることができ、デバッグのために現在のアプリ状態のスナップショットを簡単に撮ることができます。

アクション

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

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

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

// modification of current state property - NOT REACTIVE
state.users.push(...users);

// assignemt to new value - REACTIVE
state.users = [...state.users, ...users];

ゲッター

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

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 コンポーネントに渡す必要があります

<template>
  <!-- pass store to the App's "store" prop -->
  <f7-app store={store}>
    <f7-view main>
      <!-- ... -->
    </f7-view>
  </f7-app>
</template>
<script>
// import our store
import store from 'path/to/store.js';

export default {
  setup() {
    return {
      store,
    }
  }
}
</script>

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

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

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` メソッドを呼び出す必要があります。

次のストアアクションがある場合

const store = createStore({
  // ...
  actions: {
    // handler receives custom data in second argument
    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;
    },
    // handler context also contains "dispatch" method
    getUsers({ state, dispatch }, { total }) {
      // call other action
      dispatch('setLoading', true);
      fetch(`some-url?total=${total}`)
        .then((res) => res.json())
        .then((users) => {
          state.users = users;
          // call other action
          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 - 文字列 - ゲッターハンドラの名前

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

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

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

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

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

次のストアがある場合

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
  import store from 'path/to/store.js'

  export default {
    setup() {
      // retrieve "users" getter handler value. Initially empty array
      const users = useStore('users');

      onMounted(() => {
        // load users when component mounted
        store.dispatch('getUsers');
      });

      return {
        users,
      }
    }
  }
</script>

Framework7 の `useStore` ヘルパー/フックを使用したため、ユーザーがロードされたときにコンポーネントが自動的に更新されます。

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;
store.vue
<template>
  <f7-page>
    <f7-navbar title="Store" />
    <f7-block strong outline-ios inset-md>
      <p>
        Framework7 comes with a built-in lightweight application state management library - Store.
        It serves as a centralized Store for all the components in an application.
      </p>
    </f7-block>
    <f7-block v-if="!users" class="text-align-center">
      <f7-button v-if="!loading" fill round @click="load">Load Users</f7-button>
      <f7-preloader v-else />
    </f7-block>
    <f7-list v-if="users" strong outline-ios dividers-ios inset-md>
      <f7-list-item v-for="user in users" :key="user" :title="user" />
    </f7-list>
  </f7-page>
</template>
<script>
import {
  f7,
  useStore,
  f7Page,
  f7Navbar,
  f7Block,
  f7Button,
  f7Preloader,
  f7List,
  f7ListItem,
} from 'framework7-vue';

export default {
  components: {
    f7Page,
    f7Navbar,
    f7Block,
    f7Button,
    f7Preloader,
    f7List,
    f7ListItem,
  },
  setup() {
    // Subscribe to store getters
    const users = useStore('users');
    const loading = useStore('usersLoading');

    // Call store action
    const load = () => f7.store.dispatch('loadUsers');

    return {
      users,
      loading,
      load,
    };
  },
};
</script>