nuxt3のuseStateでページ遷移しても消えない状態管理。vuexもpluginも不要。

# 2021/11/18追記　その１
この記事は2021/10/28当時の公式の```useState```の情報（[こちら](https://v3.nuxtjs.org/docs/usage/state/)）が少なく、「pluginの中のdefineNuxtPlugin内で初期宣言しないといけないの？」と読めてしまう状態だったころに書かせていただいていました。

しかし、いつの間にか公式が更新され、わかりやすくなっています。

今となっては、この記事を読まなくても公式で十分です。

# 22021/11/18追記　その２
「```reactive(・・・)```と組み合わせることも可」としていましたが、```useState()```の返しは```Ref<T>```なので、そもそも```reactive(・・・)```は必要ないです。




# 概要
* nuxt3はuseStateでグローバルかつページ遷移しても消えない状態管理が可能。（vuex不要）
 * 型推論が簡単に効いて快適・・・！
* ~~公式（[こちら](https://v3.nuxtjs.org/docs/usage/state/)）ではぱっと見「pluginの中のdefineNuxtPlugin内で定義」の様に読めるが、pluginの中でなくてもよい。~~
 * ~~公式はssrContextを使用しようとしているからpluginの中で定義しているだけ・・・！~~
 * ↑2021/11/18追記 いつの間にか公式が更新され、サンプルでdefineNuxtPluginなど使われなくなりました。
* 公式ではstring（プリミティブな値）を例にしていたが、もちろんオブジェクトも可能。
 * １ページ内のリアクティブなメンバーを一発で丸ごと管理して、型推論も効く・・・！
* ライフサイクルはjavascirptのグローバル変数と同じなので、ブラウザリロードなどで消える。
 * これはvuexなどと同様。

# 実装時の要点
* ```useState<[型]>("[識別子]", [初使用の際に初期値を返す関数（省略可）])```
* ~~```reactive(・・・)```と組み合わせることも可~~
* ↑2021/11/18追記　```reactive(・・・)```を使わなくてもリアクティブです。

```js:例
const initialState = {foo:"baa", count:1};
const state = reactive(
  useState<typeof initialState>("state001", () => {
    return cloneDeep(initialState);
  })
);
```

* script内で使用する場合は「.value」が必要。

```js:例
state.value.qrText = await QRCode.toDataURL(state.value.foo);
```

* template内で使用する場合は「.value」をつけない。

```vue:例
<input type="text" v-model="state.foo" />
```

# 実装例
試しに以下の２ページを作り、ページ遷移しても値が保持されることを確認しました。
（リアクティブであることも確認できました。computedもwatchEffectも反応します。）
* （正規表現を試すページ） /pages/regex.vue
* （QRコードを作成するページ） /pages/qrcode.vue

```vue:/pages/regex.vue
<template>
  <div>
    <router-link :to="{ path: 'qrcode' }">qrcodeへ</router-link
    >&nbsp;&nbsp;&nbsp;&nbsp;<router-link
      :to="{ path: 'qrcode', query: { init: 1 } }"
      >qrcodeへ(値初期化)</router-link
    ><br />
    <table>
      <tr>
        <th>regex:</th>
        <td><input type="text" v-model="state.regex" /></td>
      </tr>
      <tr>
        <th>isGlobal:</th>
        <td><input type="checkbox" v-model="state.isGlobal" /></td>
      </tr>
      <tr>
        <th>target:</th>
        <td><textarea v-model="state.target"></textarea></td>
      </tr>
    </table>
    <br />
    <table>
      <tr>
        <th>regex.exec(target):</th>
        <td>{{ compExec }}</td>
      </tr>
      <tr>
        <th>regex.test(target):</th>
        <td>{{ compTest }}</td>
      </tr>
      <tr>
        <th>target.match(regex):</th>
        <td>{{ compMatch }}</td>
      </tr>
      <tr>
        <th>[...target.matchAll(regex)]:</th>
        <td>{{ compMatchAll }}</td>
      </tr>
      <tr>
        <th>target.search(regex):</th>
        <td>{{ compSearch }}</td>
      </tr>
      <tr>
        <th>target.replace(regex, "{{ replaceStr }}"):</th>
        <td>{{ compReplace }}</td>
      </tr>
      <tr>
        <th>target.split(regex):</th>
        <td>{{ compSplit }}</td>
      </tr>
    </table>
    <button @click="init">初期化</button>
  </div>
</template>

<style scoped>
@import url(https://fonts.xz.style/serve/inter.css);
@import url(https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css);
</style>

<script setup lang="ts">
import cloneDeep from "lodash.clonedeep"; //lodashはnuxt3に含まれている。

//初期値定義
const initialState = {
  regex: "cd",
  isGlobal: false,
  target: "abcdefgabcdefg",
};

//初期値適応
const state = reactive(
  // useRoute().path　→　/pages/regex.vueの場合は「/regex」（?・・・や#・・・があっても変化なし）
  // ページ単位で状態管理することが目的。
  useState<typeof initialState>(useRoute().path, () => {
    return cloneDeep(initialState);
  })
);

//初期化メソッド定義
const init = () => {
  state.value = cloneDeep(initialState);
};

//パラメータinit=1があれば初期化
if (useRoute().query.init) {
  init();

  //init=1を削除したURLに更新（history back時に初期化されないように）
  const nextQuery = cloneDeep(useRoute().query);
  delete nextQuery.init;
  useRouter().replace({ path: useRoute().path, query: nextQuery });
}

//以降、正規表現検証関連
const errMsg = "正規表現不正";
const replaceStr = "[---]";

const compRegObject = computed(() => {
  let reg = null;
  try {
    reg = state.value.isGlobal
      ? new RegExp(state.value.regex, "g")
      : new RegExp(state.value.regex);
  } catch {
    return null;
  }
  return reg;
});

const compExec = computed(() => {
  return compRegObject.value
    ? compRegObject.value.exec(state.value.target)
    : errMsg;
});

const compTest = computed(() => {
  return compRegObject.value
    ? compRegObject.value.test(state.value.target)
    : errMsg;
});

const compMatch = computed(() => {
  return compRegObject.value
    ? state.value.target.match(compRegObject.value)
    : errMsg;
});

const compMatchAll = computed(() => {
  return compRegObject.value
    ? state.value.isGlobal
      ? [...state.value.target.matchAll(compRegObject.value)]
      : ""
    : errMsg;
});

const compSearch = computed(() => {
  return compRegObject.value
    ? state.value.target.search(compRegObject.value)
    : errMsg;
});

const compReplace = computed(() => {
  return compRegObject.value
    ? state.value.target.replace(compRegObject.value, replaceStr)
    : errMsg;
});

const compSplit = computed(() => {
  return compRegObject.value
    ? state.value.target.split(compRegObject.value)
    : errMsg;
});
</script>
```


```vue:/pages/qrcode.vue<template>
  <div>
    <router-link :to="{ path: 'regex' }">regexへ</router-link
    >&nbsp;&nbsp;&nbsp;&nbsp;
    <router-link :to="{ path: 'regex', query: { init: 1 } }"
      >regexへ(値初期化)</router-link
    >
    <br />
    <input type="text" v-model="state.text" /><br />
    <img v-if="state.qrText" :src="state.qrText" alt="QRcode" /><br />
    <button @click="init">初期化</button>
    {{ state.qrText }}
  </div>
</template>

<style scoped>
@import url(https://fonts.xz.style/serve/inter.css);
@import url(https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css);
</style>

<script setup lang="ts">
import cloneDeep from "lodash.clonedeep";
import QRCode from "qrcode"; //qrcodeが必要。yarn add qrcode

//初期値定義
const initialState = {
  text: "この文字がQRコード化",
  qrText: "",
};

//初期値適応
const state = reactive(
  // useRoute().path　→　/pages/qrcode.vueの場合は「/qrcode」（?・・・や#・・・があっても変化なし）
  // ページ単位で状態管理することが目的。
  useState<typeof initialState>(useRoute().path, () => {
    return cloneDeep(initialState);
  })
);

//初期化メソッド定義
const init = () => {
  state.value = cloneDeep(initialState);
};

//パラメータinit=1があれば初期化
if (useRoute().query.init) {
  init();
  const nextQuery = cloneDeep(useRoute().query);
  delete nextQuery.init;
  useRouter().replace({ path: useRoute().path, query: nextQuery });
}

watchEffect(async () => {
  state.value.qrText = await QRCode.toDataURL(state.value.text);
});
</script>
```