Vue の Boolean型のプロパティのdefaultを省略した場合の挙動について


`Vue` の `Boolean` の プロパティのデフォルト値を省略した場合の挙動についてのメモです。

調べた`Vue` のバージョンは `3.0.11`です。

## 検証コード

```vue:ParentComponent.vue
<template>
  <ChildComponent />
</template>

<script lang="ts">
import { defineComponent } from "vue";
import ChildComponent from "./ChildComponent.vue";

export default defineComponent({
  name: "ParentComponent",
  components: {
    ChildComponent,
  },
  setup() {
    return {};
  },
});
</script>
```


```vue:ChildComponent.vue
<template>
  <div></div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  name: "ChildComponent",
  props: {
    testValue: {
      type: Boolean,
    },
  },
  setup(props) {
    console.log(props.testValue); //false が出力される
    return {};
  },
});
</script>
```

`testValue` という `Boolean` 型のプロパティを定義してデフォルト値をなしにしています。

この時に`testValue` を `console.log` で出力してみました。

`default` がないので `undefined` になるかと思ったのですが `false` になるようです。


## Vueのコードを追いかけてみる
[componentProps.ts](https://github.com/vuejs/vue-next/blob/v3.0.11/packages/runtime-core/src/componentProps.ts) あたりでされているようです。

`resolvePropValue` という関数の [この部分](https://github.com/vuejs/vue-next/blob/v3.0.11/packages/runtime-core/src/componentProps.ts#L344)を見てみます。

```ts
// boolean casting
if (opt[BooleanFlags.shouldCast]) {
  if (!hasOwn(props, key) && !hasDefault) {
    value = false
  } else if (
    opt[BooleanFlags.shouldCastTrue] &&
    (value === '' || value === hyphenate(key))
  ) {
    value = true
  }
}
```

ここのコードにより `value=false` が行われているようです。

`BooleanFlags.shouldCast`, `BooleanFlags.shouldCastTrue` を参照しているようですがこれは

```ts
const enum BooleanFlags {
  shouldCast,
  shouldCastTrue
}
```
このように定義されています。
`props`の各プロパティに追加する属性として定義されているような感じ（かな？）

`hyphenate` は`AbcDef`を`abc-def`　という値に変換する関数です。

`opt` に対する設定は `normalizePropsOptions` という関数の[この部分](https://github.com/vuejs/vue-next/blob/v3.0.11/packages/runtime-core/src/componentProps.ts#L417)で行われています。
(こっちの関数だと`prop` ですね)

```ts
const opt = raw[key]
const prop: NormalizedProp = (normalized[normalizedKey] =
  isArray(opt) || isFunction(opt) ? { type: opt } : opt)
if (prop) {
  const booleanIndex = getTypeIndex(Boolean, prop.type)
  const stringIndex = getTypeIndex(String, prop.type)
  prop[BooleanFlags.shouldCast] = booleanIndex > -1
  prop[BooleanFlags.shouldCastTrue] =
    stringIndex < 0 || booleanIndex < stringIndex
  // if the prop needs boolean casting or default value
  if (booleanIndex > -1 || hasOwn(prop, 'default')) {
    needCastKeys.push(normalizedKey)
  }
}
```

プロパティの`type` の中に`Boolean` が含まれていたら `BooleanFlags.shouldCast` のところは`true` になるようです。

また プロパティが`Boolean`のみ もしくは 複数の型を許容しており`Boolean`, `String` の順番で登録されていると `BooleanFlags.shouldCastTrue` が `true` になるようです。


## 上記の内容を踏まえたうえで、追加の検証をしてみる

上記のコードを確認したうえで 追加の検証をしてみます。

```vue:ParentComponent.vue
<template>
  <ChildComponent
    boolean02
    booleanOrString02
    booleanOrString04

    hyphenate01="hyphenate01"
    hyphenate02="hyphenate02"
   
   />
</template>
```

`<script>` 部分は省略

```vue:ChildComponent.vue
<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  name: "ChildComponent",
  props: {
    //Booleanのみ
    boolean01: {
      type: Boolean,
    },
    boolean02: {
      type: Boolean,
    },

    //Booleanが先
    booleanOrString01: {
      type: [Boolean, String],
    },
    booleanOrString02: {
      type: [Boolean, String],
    },

    //Stringが先
    booleanOrString03: {
      type: [String, Boolean],
    },
    booleanOrString04: {
      type: [String, Boolean],
    },

    //特殊
    hyphenate01: {
      type: [Boolean, String],
    },
    hyphenate02: {
      type: [String, Boolean],
    },
  },
  setup(props) {
    console.log(props.boolean01); //false
    console.log(props.boolean02); //true

    console.log(props.booleanOrString01); //false
    console.log(props.booleanOrString02); //true

    console.log(props.booleanOrString03); //false
    console.log(props.booleanOrString04); //(空文字列)

    console.log(props.hyphenate01); //true
    console.log(props.hyphenate02); //hyphenate02

    return {};
  },
});
</script>
```

`<template>` 部分は省略

上記のような結果になりました。

`String`, `Boolean` の順番を入れ替えると props名のみを与えた場合の挙動は異なるようです。

`hyphenate01` が`true` になることは意外でした。


## 感想
ただの`Boolean` のプロパティについてでしたが コードを見てみると意外な発見があり面白かったです。