Vuex で深くネストしたオブジェクトを扱いたい - normalizr 備忘録

## はじめに
### ◯ 背景
Vuex でデッカいオブジェクト、言いかえれば、深くネストしたオブジェクトを扱いたいと思いました。

### ◯ 問題
この時に問題になるのが、深くネストしたオブジェクトを mutate をすることが、とても煩雑になることです。

### ◯ 解決策 1 - normalizr

そのようなケースでは木構造を一旦、データベースのテーブルのような形に変形して対処するようです。この変形を normalize, 正規化と表現されているのを目にします。

* [Vuex best practices for complex objects](https://forum.vuejs.org/t/vuex-best-practices-for-complex-objects/10143/2)
* [paularmstrong/normalizr - GitHub](https://github.com/paularmstrong/normalizr)

表示するときは木構造で扱いたいのにテーブルにされたら困る、とも思ったのですが。結局、テーブルの形式から再度、木構造に直せば良いだけかなと思いました。

ただ、たしかにテーブルの構造から木構造に戻すことはできるのですが、getters, rootGetters などを使い補完も効かないため、まどろっこしい書き方になるため、なかなか辛い作業でした。かなり簡単な処理であるにも関わらず。

特に自分の場合、木が再帰的な構造をしているような、若干複雑な構造になっているため、コードが getters 祭りになってしまいました...orz map, filter も、とても使いにくいです。


### ◯ 解決策 2 - Vuex ORM

こういったデータベース的な使い方をする場合、以下の方が言われているように Ruby, PHP などで提供されている ORM フレームワーク的な API にした方が、綺麗なるのかなと思いました。

* [Vuex でリレーションを持った複雑なデータ構造を上手く扱うには - Qiita](https://qiita.com/kiaking/items/bcd5360fb3106cf22a81)
* [vuex-orm/vuex-orm - GitHub](https://github.com/vuex-orm/vuex-orm)

今回は Vuex の経験そのものが浅く理解できなかったので使えなかったのですが、次回以降ぜひ使わせていただきたいです。



## normalizr の操作

以下「愚直」に normalize して七転八倒した際の備忘録です。サンプルコードを示します。

### Step 1.

normalizr というライブラリを使います。

```
yarn add normalizr
```

* [paularmstrong/normalizr - GitHub](https://github.com/paularmstrong/normalizr)

### Step 2.

`index.js` に以下の内容をコピペします。あるいはインタープリタ `>` にそのままコピペで実行できます。基本的な考え方は **子へのプロパティ名だけを指定する** と言うことです。子とのリレーションを定義するだけで自動的にくみあげてくれるなんて、本当に有難いライブラリです。

```index.js
const { normalize, schema } = require('normalizr');
const util = require('util')

const originalData = [
  {
    title: 'オブジェクト',
    type: 'category',
    contents: [
      {
        title: '名前空間',
        type: 'article',
        path: '/namespace/',
      }
    ],
  },
  {
    title: '高速化したい',
    type: 'category',
    contents: [
      {
        title: '二分探索木',
        type: 'article',
        path: '/initial/'
      }
    ]
  }
]

const article = new schema.Entity('articles', {}, {
  idAttribute: 'path'
});
const category = new schema.Entity('categories', {}, {
  idAttribute: 'title'
});
const content = new schema.Union({ article, category }, 'type')
const contents = new schema.Array(content)
category.define({ contents })

const normalizedData = normalize(originalData, new schema.Array(content));
console.log(util.inspect(normalizedData, {showHidden: false, depth: null}))

```

#### idAttribute

主キーとなるプロパティを指定できます。デフォルトでは `id` となります。

#### noarmalize 関数

第一引数は、解析するデータ。第二引数は、解析するデータの根のスキーマを指定します。

#### schema.Union, 
２つ種類はいる可能性のあるスキーマを定義します。以下の例では `article` または `content` となります。第二引数には、データがどちらのスキーマに分類されているかを確認するために参照するべきプロパティ名を指定します。

```js
const content = new schema.Union({ article, category }, 'type')
```

#### schema.Array

配列です。

#### schema.Entity

データベースのテーブルに相当するものを定義します。第一引数には、テーブルの名前を書きます。

第二引数には、他のスキーマへのプロパティのみを書きます。スキーマに該当しないプロパティは、何も書きません。例えば `article` は第二引数が空のオブジェクト `{}` です。

しかし、ここでは `category` も空のオブジェクト `{}` です。これは循環参照をしているため、後から `contents` を付け足しています。付け足す際には `define` を使います。

```
category.define({ contents })
```

* [How to define schema for recursive model with Normalizr - Stackoverflow](https://stackoverflow.com/questions/41506533/how-to-define-schema-for-recursive-model-with-normalizr)


### Step 3.

実行します。

```
node index.js
```
```
$ node index.js
{
  entities: {
    articles: {
      '/namespace/': { title: '名前空間', type: 'article', path: '/namespace/' },
      '/initial/': { title: '二分探索木', type: 'article', path: '/initial/' }
    },
    categories: {
      'オブジェクト': {
        title: 'オブジェクト',
        type: 'category',
        contents: [ { id: '/namespace/', schema: 'article' } ]
      },
      '高速化したい': {
        title: '高速化したい',
        type: 'category',
        contents: [ { id: '/initial/', schema: 'article' } ]
      }
    }
  },
  result: [
    { id: 'オブジェクト', schema: 'category' },
    { id: '高速化したい', schema: 'category' }
  ]
}
$
```

`entities` の配下には、 `articles`, `categories` というテーブルが出力されます。レコードは、主キーを持っていて O(1) で探索できるようになっています。

`result` の配下には、元の木構造の根が出力されます。これを使って、木構造を復元するような形になるのでしょうか。

* [Normalizr - for what purpose “result” prop is placed in the output? - Stackoverflow](https://stackoverflow.com/questions/52277375/normalizr-for-what-purpose-result-prop-is-placed-in-the-output)



## Vuex での利用

`Object.assign` でまとめて state に叩き込むと便利な気がします。1 つのテーブルに対して 1 つの store を割り当てて、 store を class のような使い方をすると、良いような気がしました、個人的に。

```js
export default {
  namespaced: true,
  mutations: {
    init (state, categories) {
      Object.assign(state, categories)
    },
  }
}
```

* [Vuexのstateを手短く初期化したいがうまく行かなかったお話し - Qiita](https://qiita.com/renowan/items/ea1030a3b0fe12f29455)

### ◯ ハマったこと

Vuex にしてもプロパティを追加する際は `Vue.set` 使わないとリアクティブにならないので注意してください。自分は、ハマってしまいました笑

```js
// これは NG
// Object.assign(state, { property: value })

// これは OK
Vue.set(state, 'property', value)
```

* [Vuex がリアクティブにならない - teratail](https://teratail.com/questions/254581#reply-368724)

## 参考にさせていただいた資料

### ◯ 問題の確認

* [Make your Vuex State Flat: State Normalization with Vuex](https://markus.oberlehner.net/blog/make-your-vuex-state-flat-state-normalization-with-vuex/)



### ◯ normalizr の使い方

* [normalizr の使い方 - タイトルなし](https://naoty.dev/posts/63.html)
* [normalizr の使い方 - ラボラジアン](https://laboradian.com/how-to-use-normalizr/)


### ◯ 気になったこと - React

normalizr は React の Redux でよく使われているそうです。そして、いま Redux は Hooks に置き換えられつつあります。normalizr が Hooks で使われているのを見かけず、使って良いのか、若干、疑問があります。

* [さよなら normalizr](https://scrapbox.io/terrierscript/%E3%81%95%E3%82%88%E3%81%AA%E3%82%89normalizr)

Vue の composition API について考えつつ、代替案があるのか、考えたのですが思いつかず、そのまま normalizr を使用しました。

React, nested state で検索して見たのですが、ヒットするもののいまいちわからず。

> I'm working with a deeply nested state object in React...
>
> I think you should be using the functional form of setState, so you can have access to the current state and update it...
>
> * [React: Setting State for Deeply Nested Objects w/ Hooks - Stackoverflow](https://stackoverflow.com/questions/57798841/react-setting-state-for-deeply-nested-objects-w-hooks)

でも結局、やっぱり使ってもいいみたい。ics.media の方が言われてるなら、大丈夫かな。

> Hooksの登場で責任分担をある程度分散できるようにはなりました。しかし、ある程度の規模以上のアプリケーションになった場合は、Reduxの採用を視野に入れた方がいいでしょう。 逆にシンプルなWebアプリケーションの場合、Reduxを採用するのはオーバースペックです。 
>
> * [ベストな手法は？Reactのステート管理方法まとめ - ics.media](https://ics.media/entry/200409/)