ストア

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

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

ストアの作成

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

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

// 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;

モジュールを使用していない場合、`store.js` ファイルは次のようになります

// save store as global object
window.store = Framework7.createStore({
  state: { /* ... */ },
  actions: { /* ... */ },
  getters: { /* ... */ },
})

この例では、次の 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 インスタンスに渡す必要があります

// import our store
import store from 'path/to/store.js';

const app = new Framework7({
  // pass store to the app's "store" parameter
  store,
  ...
})

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

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

import store from 'path/to/store.js';

console.log(store.state.users);

または、Framework7 インスタンスの `store` プロパティにアクセスすることによって

import store from 'path/to/store.js';

const app = new Framework7({
  store,
  ...
})

// somewhere later
console.log(app.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` に直接アクセスできます。それ以外の場合は、ゲッターを使用してください。

ルーターコンポーネントでの使用

ルーターコンポーネントコンテキストには、次のプロパティを持つ `$store` プロパティがあります

次のストアがある場合

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

たとえば、ルーターコンポーネントでは次のように使用する必要があります

<template>
  <div class="page">
    <ul>
      <!-- getter has value in ".value" property -->
      ${users.value.map((user) => $h`
        <li>${user.name}</li>
      `)}
    </ul>
  </div>
</template>
<script>
  export default (props, { $store, $on }) => {
    // retrieve "users" getter handler value. Initially empty array
    const users = $store.getters('users');

    $on('pageInit', () => {
      // load users on page init
      $store.dispatch('getUsers');
    });

    return $render;
  }
</script>

import { createStore } from 'store';

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_page.f7.html
<template>
  <div class="page">
    <div class="navbar">
      <div class="navbar-bg"></div>
      <div class="navbar-inner">
        <div class="title">Store</div>
      </div>
    </div>
    <div class="page-content">
      ${users.value.length > 0 ? $h`
      <div class="list list-strong list-outline-ios list-dividers-ios inset-md">
        <ul>
          ${users.value.map((user) => $h`
          <li class="item-content">
            <div class="item-inner">
              <div class="item-title">${user}</div>
            </div>
          </li>
          `)}
        </ul>
      </div>
      ` : $h`
      <div class="block block-strong block-outline-ios inset-md">
        <a href="#" class="button button-fill button-round button-preloader ${loading.value ? 'button-loading' : ''}"
          @click=${loadUsers}>
          <span class="preloader"></span>
          <span>Load Users</span>
        </a>
      </div>
      `}
    </div>
  </div>
</template>
<script>
  export default (props, { $store }) => {
    const loading = $store.getters.loading;
    const users = $store.getters.users;

    const loadUsers = () => {
      $store.dispatch('getUsers');
    };

    return $render;
  };
</script>