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