儲存

Framework7 內建輕量級應用程式狀態管理函式庫 - Store。它作為應用程式中所有元件的集中式 Store。

你可以使用特定於函式庫的狀態管理函式庫,例如 Vue 的 Vuex、React 的 Redux,並使用內建的 Svelte store 功能。但如果需要簡單的功能,Framework7 Store 會是很好的選擇。

建立 Store

首先,我們需要建立 store。讓我們為此建立一個獨立的 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)- 建立 store

  • storeParameters - 物件。包含 store 參數的物件

方法會傳回已建立的 store 執行個體

Store 參數

現在,我們來看一下 storeParameters 物件

狀態

state 是包含所有應用程式層級狀態的單一物件,並作為「單一真實來源」。這也表示你通常只會為每個應用程式有一個 store。單一狀態樹讓尋找特定狀態片段變得簡單,並讓我們可以輕鬆擷取當前應用程式狀態的快照以進行除錯。

動作

actions 用於修改狀態、進行非同步處理,或呼叫其他 store 動作。動作處理常式會收到一個包含 store 狀態和 dispatch 方法的內容物件,以呼叫其他動作。因此,你可以存取 context.store 來存取狀態,或使用 context.dispatch 呼叫其他動作。

動作處理常式可以接收任何自訂資料作為第二個引數。

若要保持儲存庫反應靈敏,應透過指派來修改狀態。例如

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

// assignemt to new value - REACTIVE
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);
    }
  }
})

取得器處理常式也會接收一個內容物件,但僅包含儲存庫狀態。例如,無法從取得器呼叫其他動作。

使用儲存庫

現在我們已建立儲存庫,讓我們找出如何使用它。

首先,我們需要將建立的儲存庫傳遞至主應用程式執行個體

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