ホームページ  >  記事  >  ウェブフロントエンド  >  Vuex を例として、状態管理の謎を明らかにします。

Vuex を例として、状態管理の謎を明らかにします。

藏色散人
藏色散人転載
2021-12-16 15:46:081773ブラウズ

Vuex をガイドとして使用する、状態管理の全体像を垣間見る

ご存知のとおり、Vuex は Vue の公式状態管理ソリューションです。

Vuexの使い方やAPIは難しくなく、公式サイトの紹介も簡潔で分かりやすいです。このおかげで、Vuex をプロジェクトにすばやく統合するのは非常に簡単です。ただし、その柔軟な使用法のため、多くの学生は Vuex の設計と使用に多少混乱しています。

実際、これを使用する前に、少し立ち止まって、いくつかの質問について考えてみるとよいでしょう:

  • 状態管理とは何ですか?
  • なぜ Vuex を使用する必要があるのですか?
  • コンポーネントの内部状態と Vuex 状態を分散するにはどうすればよいですか?
  • Vuex の使用に伴う潜在的な問題は何ですか?

これらの質問について曖昧な場合は、おめでとうございます。この記事が必要なものかもしれません。

Vuex を例として使用して、原点から始まるステート管理の謎を明らかにするために、以下に参加してください。

概要プレビュー

この記事で紹介する内容には次の側面が含まれます:

  • 状態とコンポーネントの誕生
  • 状態管理は必要ですか実行されます?
  • 単一データソース
  • ステータス更新方法
  • 非同期更新?
  • 状態のモジュール化
  • モジュラースロット
  • 次のステップ

状態とコンポーネントの誕生

Since Three 誕生以来大きなフレームワークの中で、それらが共有する 2 つの機能が Jquery に完全に影響を与えています。これら 2 つの機能は次のとおりです:

  1. データ駆動型ビュー
  2. コンポーネント化

データ駆動型ビューこれにより、DOM の操作に頼ってページを更新することしかできなかった時代に別れを告げることができます。ページを更新するたびに、検索のレイヤーを介して DOM を見つけて、そのプロパティとコンテンツを変更する必要がなくなり、データを操作することでこれらのことを行うことができます。

もちろん、フロントエンドの観点からは、データは基本的にさまざまなデータ型を格納する 変数 として理解できます。 データドリブンの概念が登場した後、いくつかの変数にも特別な意味が与えられました。

1 つ目は通常の変数で、JQ 時代と何ら変わりはなく、データの保存にのみ使用されます。さらに、応答効果のあるタイプの変数があります。これらの変数はビューにバインドされています。変数が変更されると、これらの変数にバインドされているビューも対応する更新をトリガーします。私はこれらの変数を 状態変数#と呼びます##。

いわゆるデータ駆動型ビューは、厳密に言えば、状態変数がビューを駆動することを意味します。 Vue や React の普及により、フロントエンド開発者の焦点は DOM の操作からデータの操作へと徐々に移り、状態変数が中心になりました。

状態変数。今では誰もが

state と呼ぶことを好むようです。状態や状態管理についてよく話しますが、実際、この状態とは状態変数のことを指します。以下で説明する状態は状態変数も指します。

状態が確立されると、コンポーネントも付属します。

JQ の時代では、フロントエンド ページは単なる HTML であり、「コンポーネント」という概念はなく、ページのパブリック部分をエレガントに再利用することはそれほど難しいことではありません。幸いなことに、3 つの主要なフレームワークは非常に成熟したコンポーネント設計をもたらしており、DOM フラグメントをコンポーネントとして抽出するのが簡単で、コンポーネントは内部的に自身の状態を維持できるため、より独立性が高くなります。

コンポーネントの重要な特徴は、これらの内部状態が外部から分離されていることです。親コンポーネントは子コンポーネントの内部状態にアクセスできませんが、子コンポーネントは親コンポーネントから渡された状態(Props)にアクセスし、変更に応じて自動的に応答することができます。

この機能はモジュール化された状態と捉えることができます。この利点は、現在の設定状態が他のコンポーネントに影響を与えることを考慮する必要がないことです。もちろん、コンポーネントのステータスを完全に分離することは非現実的です。必然的に複数のコンポーネントがステータスを共有する必要があります。この場合、解決策は、コンポーネントに最も近い親コンポーネントにステータスを抽出し、それを Props を介して渡すことです。 。

上記のステータス共有スキームは通常は問題なく、公式に推奨されるベスト プラクティスでもあります。

しかし、ページが複雑な場合は、それでも不十分であることがわかります。例:

    コンポーネント階層が深すぎるため、状態を共有する必要があります。このとき、状態はレイヤーごとに渡される必要があります。
  • 子コンポーネントが状態を更新する場合、兄弟コンポーネントによって共有される複数の親コンポーネントが存在する可能性があり、実装が困難になります。
この場合、「

状態を親コンポーネントに抽出する」メソッドを使用し続けると、非常に複雑になることがわかります。そして、コンポーネントの数が増加し、ネストのレベルが深くなるにつれて、複雑さはますます高くなります。関連する状態や送信が複雑なため、特定のコンポーネントが不可解に更新されたり、特定のコンポーネントが更新されないなどの問題が発生しやすく、異常時のトラブルシューティングが困難になります。

これを考慮すると、この複雑な状況に対処するためのより洗練されたソリューションが必要です。

状態管理が必要ですか?

前のセクションで、ページが複雑なため、コンポーネント間で共有状態を実装する際に厄介な問題に遭遇したと述べました。

それでは解決策はあるのでしょうか?もちろん、それはあります。コミュニティのリーダーたちの努力のおかげで、複数の計画があります。しかし、これらのソリューションにはすべて共通の名前があり、それが 2 年前に私たちが非常に熱心に議論したことです - 状態管理

状態管理は、実際には グローバル状態管理として理解できます。ここでの状態は、コンポーネント内部の状態とは異なります。コンポーネントとは独立して維持され、コンポーネントのニーズと一致します。この状態のコンポーネントは関連付けられています。

各状態管理には独自の実装計画があります。 Vue には Vuex があり、React には Redux、Mobx があり、もちろん他のソリューションもあります。しかし、それらはすべて、コンポーネント間の状態共有の問題という同じ問題を解決します。

過去 2 年間で、「状態管理」の概念が普及したため、それがアプリケーション開発に不可欠な部分になったようだったことを覚えています。 Vue を例に挙げると、プロジェクトを作成すると、状態管理のために必然的に Vuex が導入されます。しかし、多くの人はステート管理をなぜ、いつ、どのように使用するのかを知らず、ただ盲目的にトレンドに従っており、その結果、ステート管理の悪用の例が数多く発生しています。

これを見ると、状態管理が必要ないことがわかるはずです。なぜそれが現れるのか、そしてそれがどのような問題を解決するのかについては、基本的に上記で説明されています。理解できない場合は、一時停止して最初からもう一度読んでください。技術的なソリューションが生まれた背景は重要ではないと考えず、それがどのような問題を解決するために設計されているのかを理解しなければ、その役割を真に果たすことはできません。

Redux 作者には有名な言葉があります: Redux (状態管理) が必要かどうかわからないなら、必要ありません

わかりました。状態管理を使用している場合、または問題解決に状態管理を使用する必要がある場合は、読み続けてください。

Vuex

Vue は中国、特に中小規模のチームで広く使用されているため、ほとんどの人が最初に触れる状態管理ソリューションは Vuex になるはずです。

それでは、Vuex はコンポーネント間の状態共有の問題をどのように解決するのでしょうか?一緒に調べてみましょう。

ストアの作成

上で述べたように、一般的なコンポーネントの共有ステータスについては、公式の推奨事項は「ステータスを最も近い親コンポーネントに抽出する」です。 Vuex はさらに一歩進んで、すべての状態を root コンポーネント に抽出し、任意のコンポーネントがそれにアクセスできるようにします。

おそらくあなたはこう尋ねるでしょう:これは国家を世界情勢にさらすことになるのではありませんか?それはモジュール化の利点を完全に排除することになりませんか?

実際にはそうではありません。これを行う Vuex の主な目的は、すべてのコンポーネントがこれらの状態にアクセスできるようにし、サブコンポーネントの状態にアクセスできない状況を完全に回避することです。 Vuex は、単一データ ソース 原則に従って、すべての状態データをオブジェクトに配置します。ただし、これは状態がスタックされているという意味ではなく、Vuex はこの単一の状態ツリーに独自のモジュール型ソリューションを実装します。

心配しないで、順を追って、まず Vuex の使用方法を見てみましょう。

Vuex は Vue のプラグインとして存在します。まず npm でインストールします:

$ npm install --save vuex

インストール後、新しい src/store フォルダーを作成し、すべての Vuex を配置します-関連のものはここにあります。

新しい index.js を作成し、次のコードを記述します。このコードの主な機能は、Vue.use メソッドを使用して Vuex プラグインをロードし、構成された Vuex.Store インスタンスをエクスポートすることです。

import Vue from 'vue'
import Vuex from 'vuex'
// 安装插件
Vue.use(Vuex)

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  modules: {}
})

通常、上記でエクスポートされたインスタンスを store と呼びます。ストアには、保存された状態 (state) と状態を変更する関数 (mutation) が含まれます。すべての状態と関連する操作はここで定義されます。

最後のステップは、上でエクスポートしたストア インスタンスをエントリ ファイルで Vue にマウントすることです:

import store from './store'

new Vue({
  el: '#app',
  store: store
})

注: このマウントのステップは必要ありません。このマウント手順の目的は、.vue コンポーネントの this.$store を介して、エクスポートされたストア インスタンスへのアクセスを容易にすることだけです。実装されていない場合は直輸入と同じです。

単一データ ソース (状態)

前のステップでは、コンストラクター Vuex.Store を使用してストア インスタンスを作成しました。誰もが少なくとも Vuex の使用方法を知っています。 。このステップでは、Vuex.Store コンストラクターの具体的な構成を見てみましょう。

最初は state 構成で、その値は状態を保存するために使用されるオブジェクトです。 Vuex は、単一状態ツリー 原則を使用してすべての状態をこのオブジェクトに配置し、後続の状態の特定とデバッグを容易にします。

たとえば、次のように、バージョンを示す初期状態 app_version があります。

new Vuex.Store({
  state: {
    app_version: '0.1.1'
  }
}

これをコンポーネント内で取得したいので、次のようにします。

this.$store.state.app_version

しかし、これが唯一の方法ではなく、次のようにも考えられます:

import store from '@/store' // @ 表示 src 目录
store.state.app_version

なぜこの点を強調する必要があるのでしょうか?なぜなら、多くの友人が Vuex は this.$store を通じてのみ操作できると考えているからです。コンポーネント以外の場合、たとえばリクエスト関数で特定の Vuex 状態を設定したい場合、どうすればよいかわかりません。

実際には、mapState 関数など、コンポーネントの状態を取得するより洗練された方法があり、複数の状態を簡単に取得できます。

import { mapState } from 'vuex'

export default {
  computed: {
    ... // 其他计算属性
    ...mapState({
      version: state => state.app_version
    })
  }
}

状态更新方式(mutation)

Vuex 中的状态与组件中的状态不同,不能直接用 state.app_version='xx' 这种方式修改。Vuex 规定修改状态的唯一方法是提交 mutation

Mutation 是一个函数,第一个参数为 state,它的作用就是更改 state 的状态。

下面定义一个名叫 increment 的 mutation,在函数内更新 count 这个状态:

new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment(state, count) {
      // 变更状态
      state.count += count
    }
  }
})

然后在 .vue 组件中触发 increment

this.$store.commit('increment', 2)

这样绑定了 count 的视图就会自动更新。

同步更新

虽然 mutation 是更新状态的唯一方式,但实际上它还有一个限制:必须是同步更新

为什么必须是同步更新?因为在开发过程中,我们常常会追踪状态的变化。常用的手段就是在浏览器控制台中调试。而在 mutation 中使用异步更新状态,虽然也会使状态正常更新,但是会导致开发者工具有时无法追踪到状态的变化,调试起来就会很困难。

再有 Vuex 给 mutation 的定位就是更改状态,只是更改状态,别的不要参与。所谓专人干专事儿,这样也帮助我们避免把更改状态和自己的业务逻辑混起来,同时也规范了函数功能。

那如果确实需要异步更新,该怎么办呢?

异步更新

异步更新状态是一个非常常见的场景,比如接口请求回来的数据要存储,那就是异步更新。

Vuex 提供了 action 用于异步更新状态。与 mutation 不同的是,action 不直接更新状态,而是通过触发 mutation 间接更新状态。因此即便使用 action 也不违背 “修改状态的唯一方法是提交 mutation” 的原则。

Action 允许在实际更新状态前做一些副作用的操作,比如上面说的异步,还有数据处理,按条件提交不同的 mutation 等等。看一个例子:

new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    add(state) {
      state.count++
    },
    reduce(state) {
      state.count--
    }
  },
  actions: {
    increment(context, data) {
      axios.get('**').then(res => {
        if (data.iscan) {
          context.commit('add')
        } else {
          context.commit('reduce')
        }
      })
    }
  }
})

在组件中触发 action:

this.$store.dispatch('increment', { iscan: true })

这些就是 action 的使用方法。其实 action 最主要的作用就是请求接口,拿到需要的数据,然后触发 mutation 修改状态。

其实这一步在组件中也可以实现。我看过一些方案,常见的是在组件内写一个请求方法,当请求成功,直接通过 this.$store.commit 方法触发 mutation 来更新状态,完全用不到 action。

难道 action 可有可无吗?

也不是,在特定场景下确实需要 action 的,这个会在下一篇说。

状态模块化(module)

前面讲过,Vuex 是单一状态树,所有状态存放在一个对象上。同时 Vuex 有自己的模块化方案
,可以避免状态堆砌到一起,变的臃肿。

Vuex 允许我们将 store 分割成模块(module),每个模块拥有自己的 state、mutation、action。虽然状态注册在根组件,但是支持模块分割,相当于做到了与页面组件平级的“状态组件”。

为了区分,我们将被分割的模块称为子模块,暴露在全局的称为全局模块

我们来看基础用法:

new Vuex.Store({
  modules: {
    user: {
      state: {
        uname: 'ruims'
      },
      mutation: {
        setName(state, name) {
          state.name = name
        }
      }
    }
  }
})

上面定义了 user 模块,包含了一个 state 和一个 mutation。在组件中使用方法如下:

// 访问状态
this.$store.state.user.uname
// 更新状态
this.$store.commit('setName')

大家发现了,访问子模块的 state 要通过 this.$store.state.[模块名称] 这种方式去访问,触发 mutation 则与全局模块一样,没有区别。

action 与 mutation 原理一致,不细说。

命名空间

上面说到,子模块触发 mutation 和 action 与全局模块一致,那么假设全局模块和子模块中都有一个名为 setName 的 mutation。在组件中触发,哪个 mutation 会执行呢?

经过试验,都会执行。官方的说法是:为了多个模块能够对同一 mutation 或 action 作出响应。

其实官方做的这个兼容,我一直没遇到实际的应用场景,反而因为同名 mutation 导致误触发带来了不少的麻烦。可能官方也意识到了这个问题,索引后来也为 mutation 和 action 做了模块处理方案。

这个方案,就是命名空间。

命名空间也很简单,在子模块中加一个 namespaced: true 的配置即可开启,如:

new Vuex.Store({
  modules: {
    user: {
      namespaced: true,
      state: {}
    }
  }
})

开启命名空间后,触发 mutation 就变成了:

this.$store.commit('user/setName')

可见提交参数由 '[mutation]' 变成了 '[模块名称]/[mutation]'

模块化的槽点

上面我们介绍了 Vuex 的模块化方案,将单一状态树 store 分割成多个 module,各自负责本模块状态的存储和更新。

模块化是必要的,但是这个模块的方案,用起来总觉得有点别扭

比如,总体的设计是将 store 先分模块,模块下在包含 state,mutation,action。

那么按照正常理解,访问 user 模块下 state 应该是这样的:

this.$store.user.state.uname

但是实际 API 却是这样的:

this.$store.state.user.uname

这个 API 仿佛是在 state 中又各自分了模块。我没看过源码,但从使用体验上来说,这是别扭一。

除 state 外,mutation,action 默认注册在全局的设计,也很别扭

首先,官方说的多个模块对同一 mutation 或 action 作出响应,这个功能暂无找到应用场景。并且未配 namespace 时还要保证命名唯一,否则会导致误触发。

其次,用 namespace 后,触发 mutation 是这样的:

this.$store.commit('user/setName')

这个明显是将参数单独处理了,为什么不是这样:

this.$store.user.commit('setName')

总体感受就是 Vuex 模块化做的还不够彻底。

为什么吐槽

上面说的槽点,并不是为了吐槽而吐槽。主要是感觉还有优化空间。

比如 this.$store.commit 函数可以触发任何 mutation 来更改状态。如果一个组件复杂,需要操作多个子模块的状态,那么就很难快速的找出当前组件操作了哪些子模块,当然也不好做权限规定。

我希望的是,比如在 A 组件要用到 b, c 两个子模块的状态,不允许操作其他子模块,那么就可以先将要用到模块导入,比如这样写:

import { a, b } from this.$store
export default {
  methods: {
    test() {
      alert(a.state.uname) // 访问状态
      a.commit('setName')// 修改状态
    }
  }
}

这样按照模块导入,查询和使用都比较清晰。

下一步

前面我们详细介绍了状态管理的背景以及 Vuex 的使用,分享了关于官方 API 的思考。相信看到这里,你已经对状态管理和 Vuex 有了更深刻的认识和理解。

然而本篇我们只介绍了 Vuex 这一个方案,状态管理的其他方案,以及上面我们的吐槽点,能不能找到更优的实现方法,这些都等着我们去尝试。

下一篇文章我们继续深挖状态管理,对比 Vuex 和 React,Fluter 在状态管理实现上的差异,然后在 Vue 上集成 Mobx,打造我们优雅的应用。

以上がVuex を例として、状態管理の謎を明らかにします。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。