- Published on
设计一个兼容外部传参的Vue组件
- Authors
- Name
- Deng Hua
目录
假设有一个手风琴组件,组建内部有自己的 open 状态,该组件将在单击时显示和隐藏其内容。某些情况下,你希望能够覆盖该内部状态并能从父组件控制它,该如何设计?
组件内部的Props
从最简单的部分开始:
<template>
<div>
<div
class="title"
@click="toggleHidden"
>
{{ title }}
</div>
<div v-if="!hidden" class="content">
<slot />
</div>
</div>
</template>
import { ref } from "vue";
const props = defineProps({
title: String,
});
const hidden = ref(true);
const toggleHidden = () => {
hidden.value = !hidden.value;
};
如果您使用 Options API,它会如下所示:
export default {
name: 'Toggle',
props: {
title: {
type: String,
},
},
data() {
return {
hidden: true,
};
},
methods: {
toggleHidden() {
this.hidden = !this.hidden;
},
},
};
只需要为组件提供content
便可使用:
<template>
<Toggle title="Toggled Content">
This content can be toggled on and off.
</Toggle>
</template>
接受外部传参控制
<div class="content">/*...*/</div>
的这部分通常是隐藏的,直到用户单击标题切换显示。
但现在需求发生了变化,我们希望父组件也能够触发内容切换。
换句话说,我们不再希望由组件内部来管理状态。我们希望由父级来管理和切换状态。
这很简单:
import { ref } from "vue";
const props = defineProps({
title: String,
hidden: Boolean,
});
const emit = defineEmits(['click']);
const toggleHidden = () => emit('click');
Options API版本:
export default {
name: 'Toggle',
emits: ['click'],
props: {
title: {
type: String,
required: true,
},
hidden: {
type: Boolean,
default: true,
},
},
methods: {
toggleHidden() {
this.$emit('click');
},
},
};
现在可以从父组件控制它了:
<template>
<Toggle
title="Toggled Content"
:hidden="hidden"
@click="hidden = !hidden"
>
This content can be toggled on and off.
</Toggle>
</template>
两者兼得
我们现在已经看到了编写该组件的两种不同方法。
第一种方法是最容易使用的,组件内部会管理其自身的状态。第二种方式给了我们更多的控制权,但父级必须管理维护状态。
但我们能两者兼得吗?
有没有一种方法可以让一个组件可以以两种方式使用?
我们需要一个 hidden
props以及一个 hidden
的state。
为了使用不同的名称,我们将内部状态称为 _hidden
,这样props仍然可以使用hidden
。
<template>
<div>
<div
class="title"
@click="toggleHidden"
>
{{ title }}
</div>
<div
class="content"
v-if="!hidden"
>
<slot />
</div>
</div>
</template>
import { ref } from "vue";
const props = defineProps({
title: String,
hidden: Boolean,
});
const emit = defineEmits(['click']);
const _hidden = ref(true);
const toggleHidden = () => emit('click');
Options API版本:
export default {
name: 'Toggle',
emits: ['click'],
props: {
title: {
type: String,
},
hidden: {
type: Boolean,
}
},
data() {
return {
_hidden: true,
};
},
methods: {
toggleHidden() {
this.$emit('click');
},
},
};
请注意,我们没有为 hidden
props提供默认值。 因为我们将使用计算属性将 props 和内部state“组合”在一起:
const hidden = computed(() =>
props.hidden !== undefined
? props.hidden
: _hidden.value
);
Options API版本:
computed: {
$hidden() {
return this.hidden !== undefined
? this.hidden
: this._hidden;
},
},
即如果设置了props,我们将使用props来源的hidden
。否则我们使用组件内部的_hidden
。
<template>
<div>
<div
class="title"
@click="toggleHidden"
>
{{ title }}
</div>
<div
class="content"
v-if="!hidden"
>
<slot />
</div>
</div>
</template>
import { ref, computed } from "vue";
const props = defineProps({
title: String,
hidden: Boolean,
});
const _hidden = ref(false);
const emit = defineEmits(['click']);
const toggleHidden = () => emit('click');
const hidden = computed(() =>
props.hidden !== undefined ? props.hidden : _hidden.value
);
Options API 版本如下:
<template>
<div>
<div
class="title"
@click="toggleHidden"
>
{{ title }}
</div>
<div
class="content"
v-if="!$hidden"
>
<slot />
</div>
</div>
</template>
export default {
name: 'Toggle',
emits: ['click'],
props: {
title: {
type: String,
},
hidden: {
type: Boolean,
}
},
data() {
return {
_hidden: true,
};
},
methods: {
toggleHidden() {
this.$emit('click');
},
},
computed: {
$hidden() {
return this.hidden !== undefined
? this.hidden
: this._hidden;
},
},
};
但是toggleHidden
函数还不完善,需要判断是否存在hidden
props,以及对应的分支逻辑:
/*
toggleHidden() {
this.$emit('click');
}
改写为:
*/
const toggleHidden = () => {
if (props.hidden !== undefined) {
emit('click');
} else {
_hidden.value = !_hidden.value;
};
};
Options API:
toggleHidden() {
if (this.hidden !== undefined) {
this.$emit('click');
} else {
this._hidden = !this._hidden;
}
},
完整示例:
<template>
<div>
<div
class="title"
@click="toggleHidden"
>
{{ title }}
</div>
<div v-if="!hidden" class="content">
<slot />
</div>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
const props = defineProps({
title: String,
hidden: Boolean,
});
const emit = defineEmits(["click"]);
const _hidden = ref(true);
const toggleHidden = () => {
if (props.hidden !== undefined) {
emit("click");
} else {
_hidden.value = !_hidden.value;
}
};
const hidden = computed(() =>
props.hidden !== undefined ? props.hidden : _hidden.value
);
</script>
简而言之,如果父组件外部提供了props,则子组件使用外部提供的props,并且状态更改时也是更改外部的props。
否则使用组件内部维护的状态。
End.