[Vue.js]ScopedSlotsを使って状態管理をコンポーネントから分離する

#はじめに

VueのScopedSlotsが、ReactのRenderPropsと同じように問題を解決すると聞いて、
とりあえず状態管理分離できるのかなと、やってみました。

バケツリレーの煩雑さと、mixinの不透明性と名前の衝突を、わりと解決できたような気がするんですが、どうでしょうか。

#デモ
https://codesandbox.io/s/rj64v82q1p


#UserList.vue

新規登録フォームとかそんなかんじの画面です。
２つのコンポーネント、`<UserRecords>`と`<UserTempRecord>`が使われています。
これらは画面表示のためでなく、データとデータ操作メソッドを提供するためのものです。

```vue
<UserRecords><template slot-scope="{add:addUser}">
```
これは、`<UserRecords>`がslotPropsとして公開してるデータとメソッドから、
addメソッドを取り出して、addUserと別名を付けています。

```vue
<template functional>
  <div>
    <UserRecords>
      <template slot-scope="{add:addUser}">
        <UserTempRecord>
          <template 
            slot-scope="{id,name,is_valid,
                         emitValue:emitTmpUserValue,
                         reset:resetTempUser}">
            <div>
              <input
                type="text"
                :value="name"
                @input="emitTmpUserValue('name', $event.target.value)">
              <button
                :disabled="!is_valid"
                @click="addUser({id,name})+resetTempUser()">add
              </button>
            </div>

          </template>
        </UserTempRecord>
      </template>
    </UserRecords>


    <UserRecords>
      <template slot-scope="{records:users}">
        <ul>
          <li v-for="user in users" :key="user.id">
            {{user.name}}
          </li>
        </ul>
      </template>
    </UserRecords>
  </div>
</template>


```

#UserRecords.vue

Userデータの状態を管理するコンポーネントです。
render関数の中身は、htmlで書き直すと次のようになります。

```html
<template>
  <slot :records="records" :add="add" />
</template>
```

recordsデータと、add関数を公開しています。
なぜrenderで書いているかというと、templateで書くと、rootに直接slotが置けず、
`<template><div><slot>`　とならざるを得ないからです。

```vue
<script>
  import store from "./store.js";

  export default {
    data() {
      return {
        user: store.user
      };
    },
    methods: {
      add({id, name}) {
        this.user.records.push({
          id,
          name
        });
      }
    },
    render(h) {
      const contents = this.$scopedSlots.default({
        records: this.user.records,
        add: this.add
      });

      if (contents.length > 1) {
        console.error("<UserRecords> can only be used on a single element.");
      }

      return contents[0];
    }
  };
</script>
```

#UserTempRecord.vue

Userの一時編集用の状態を管理するコンポーネントです。  
分離する必要あるか？とは思ったんですが、せっかくなのでやってみました。 
バリデーションの処理などまとめられていいかもしれないです。

```vue
<script>
import uuid from "uuid";
const getInitialUserData = () => {
  return {
    id: uuid.v4(),
    name: ""
  };
};

export default {
  data() {
    return {
      temp: getInitialUserData()
    };
  },
  computed: {
    is_valid() {
      return !!this.temp.name.length;
    }
  },
  methods: {
    reset() {
      this.temp = getInitialUserData();
    },
    emitValue(name, value) {
      this.temp[name] = value;
    }
  },
  render(h) {
    const contents = this.$scopedSlots.default({
      id: this.temp.id,
      name: this.temp.name,
      is_valid: this.is_valid,
      emitValue: this.emitValue,
      reset: this.reset
    });
    if (contents.length > 1) {
      console.error("<UserTempRecord> can only be used on a single element.");
    }

    return contents[0];
  }
};
</script>

```


#おわりに
ScopedSlots思ったよりも強いかもしれないです。
というか分割代入が便利すぎますね。