LoginSignup
5
8

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-06-18

はじめに

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

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

デモ

UserList.vue

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

<UserRecords><template slot-scope="{add:addUser}">

これは、<UserRecords>がslotPropsとして公開してるデータとメソッドから、
addメソッドを取り出して、addUserと別名を付けています。

<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で書き直すと次のようになります。

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

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

<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の一時編集用の状態を管理するコンポーネントです。

分離する必要あるか?とは思ったんですが、せっかくなのでやってみました。
バリデーションの処理などまとめられていいかもしれないです。

<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思ったよりも強いかもしれないです。
というか分割代入が便利すぎますね。

5
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
8