Skip to content

Commit cd981cc

Browse files
committed
feat: select 支持受控模式
1 parent 6ff083a commit cd981cc

File tree

6 files changed

+148
-20
lines changed

6 files changed

+148
-20
lines changed

components/_util/use/useModel.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ export const useNormalModel = <
2121
> = ModelValuePropKey,
2222
EventName extends string = string,
2323
>(
24-
props: Props,
25-
emit: (eventName: EventName, ...args: any[]) => void,
26-
config: UseNormalModelOptions<Props, Key> = {},
27-
): [WritableComputedRef<Props[Key]>, (val: Props[Key]) => void] => {
24+
props: Props,
25+
emit: (eventName: EventName, ...args: any[]) => void,
26+
config: UseNormalModelOptions<Props, Key> = {},
27+
): [WritableComputedRef<Props[Key]>, (val: Props[Key]) => void] => {
2828
const {
2929
prop = 'modelValue',
3030
deep = false,
@@ -106,13 +106,13 @@ export const useArrayModel = <
106106
> = Extract<ModelValuePropKey, GetKeysIsArrayType<Props>>,
107107
EventName extends string = string,
108108
>(
109-
props: Props,
110-
emit: (eventName: EventName, ...args: any[]) => void,
111-
config: UseNormalModelOptions<Props, Key> = {},
112-
): [
113-
WritableComputedRef<Props[Key]>,
114-
(val: ArrayOrItem<Props[Key]>) => void,
115-
] => {
109+
props: Props,
110+
emit: (eventName: EventName, ...args: any[]) => void,
111+
config: UseNormalModelOptions<Props, Key> = {},
112+
): [
113+
WritableComputedRef<Props[Key]>,
114+
(val: ArrayOrItem<Props[Key]>) => void,
115+
] => {
116116
const [computedValue, updateCurrentValue] = useNormalModel(props, emit, {
117117
...config,
118118
defaultValue: [] as Props[Key],

components/select/props.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ export const selectProps = {
9393
type: Boolean,
9494
default: false,
9595
},
96+
passive: {
97+
type: Boolean,
98+
default: true,
99+
},
96100
popperClass: [String, Array, Object] as PropType<string | [] | object>,
97101
triggerClass: [String, Array, Object] as PropType<string | [] | object>,
98102
triggerStyle: [Object, String] as PropType<CSSProperties | string>,

components/select/select.vue

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@
7474
</template>
7575

7676
<script lang="ts">
77-
import { type CSSProperties, computed, defineComponent, provide, ref, unref, watch } from 'vue';
77+
import { computed, defineComponent, provide, ref, unref, watch } from 'vue';
78+
import type { CSSProperties } from 'vue';
7879
import { isNil } from 'lodash-es';
7980
import { useTheme } from '../_theme/useTheme';
80-
import { type UseArrayModelReturn, useArrayModel, useNormalModel } from '../_util/use/useModel';
8181
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '../_util/constants';
8282
import useFormAdaptor from '../_util/use/useFormAdaptor';
8383
import Popper from '../popper';
@@ -88,6 +88,7 @@ import OptionList from './optionList';
8888
import { selectProps } from './props';
8989
import useOptions from './useOptions';
9090
import type { SelectOption, SelectValue } from './interface';
91+
import { useCurrentValue } from './useCurrentValue';
9192
9293
export default defineComponent({
9394
name: 'FSelect',
@@ -106,7 +107,7 @@ export default defineComponent({
106107
const innerDisabled = computed(() => props.disabled === true || isFormDisabled.value);
107108
const isOpenedRef = ref(false);
108109
// 与 props 中 modelValue 类型保持一致
109-
const [currentValue, updateCurrentValue] = props.multiple ? (useArrayModel(props, emit) as unknown as UseArrayModelReturn<SelectValue[]>) : useNormalModel(props, emit);
110+
const { currentValue, updateCurrentValue } = useCurrentValue(props, emit);
110111
111112
const triggerRef = ref();
112113
const triggerWidth = ref(0);
@@ -123,16 +124,15 @@ export default defineComponent({
123124
}
124125
});
125126
126-
const handleChange = () => {
127-
emit(CHANGE_EVENT, unref(currentValue));
127+
const handleChange = (value: SelectValue | SelectValue[]) => {
128+
emit(CHANGE_EVENT, value);
128129
validate(CHANGE_EVENT);
129130
};
130131
131132
const handleClear = () => {
132133
const value: null | [] = props.multiple ? [] : null;
133134
if (props.multiple ? ((currentValue.value as SelectValue[]) || []).length : currentValue.value !== null) {
134-
updateCurrentValue(value);
135-
handleChange();
135+
handleChange(updateCurrentValue(value));
136136
}
137137
filterText.value = '';
138138
cacheOptions.value = [];
@@ -260,8 +260,7 @@ export default defineComponent({
260260
}
261261
}
262262
}
263-
updateCurrentValue(unref(value));
264-
handleChange();
263+
handleChange(updateCurrentValue(unref(value)));
265264
};
266265
267266
// select-trigger 选择项展示,只在 currentValue 改变时才改变

components/select/useCurrentValue.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { useVModel } from '@vueuse/core';
2+
import type { SelectValue } from './interface';
3+
import type { SelectProps } from './props';
4+
5+
export function useCurrentValue(props: SelectProps, emit: (event: 'filter' | 'update:modelValue' | 'focus' | 'blur' | 'change' | 'clear' | 'search' | 'scroll' | 'removeTag' | 'visibleChange', ...args: any[]) => void) {
6+
const currentValue = useVModel(props, 'modelValue', emit, {
7+
passive: props.passive,
8+
});
9+
10+
function updateCurrentValue(value: SelectValue | SelectValue[]): SelectValue | SelectValue[] {
11+
if (!props.multiple) {
12+
currentValue.value = value;
13+
return value;
14+
} else {
15+
if (Array.isArray(value)) {
16+
currentValue.value = value;
17+
return value;
18+
}
19+
20+
// 兼容重复赋值为不符合预期数据类型的场景
21+
let val: SelectValue[] = [];
22+
if (!Array.isArray(currentValue.value)) {
23+
console.warn(
24+
'[useArrayModel] 绑定值类型不匹配, 仅支持数组类型, value:',
25+
currentValue.value,
26+
);
27+
val = [];
28+
} else {
29+
val = [...currentValue.value];
30+
}
31+
32+
const index = val.indexOf(value);
33+
if (index !== -1) {
34+
val.splice(index, 1);
35+
} else {
36+
val.push(value);
37+
}
38+
currentValue.value = val;
39+
return val;
40+
}
41+
};
42+
43+
if (props.multiple) {
44+
if (!Array.isArray(currentValue.value)) {
45+
console.warn(
46+
'[useArrayModel] 绑定值类型不匹配, 仅支持数组类型, value:',
47+
props.modelValue,
48+
);
49+
currentValue.value = [];
50+
}
51+
}
52+
53+
return {
54+
currentValue,
55+
updateCurrentValue,
56+
};
57+
}

docs/.vitepress/components/select/index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ addon.vue
134134
selectGroupOption.vue
135135
:::
136136

137+
### 受控模式
138+
139+
:::demo
140+
passive.vue
141+
:::
142+
137143
## Select Props
138144

139145
| 属性 | 说明 | 类型 | 默认值 |
@@ -156,6 +162,7 @@ selectGroupOption.vue
156162
| tag | 是否可以创建新的选项,需要和 `filterable` 一起使用 | boolean | `false` |
157163
| remote | 是否远程搜索,当输入内容时触发`search`事件 | boolean | `false` |
158164
| options | 选项配置 | array\<SelectOption\> | `[]` |
165+
| passive | 是否受控模式,true-非受控,false-受控 | boolean | `true` |
159166
| virtualScroll | 虚拟滚动 | boolean / number | `true` |
160167
| valueField | 替代 `SelectOption` 中的 `value` 字段名 | string | `value` |
161168
| labelField | 替代 `SelectOption` 中的 `label` 字段名 | string | `label` |
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<template>
2+
<FSpace vertical>
3+
<!-- <FSelect multiple :multipleLimit="2" :passive="false">
4+
<FOption
5+
v-for="(item, index) in optionList"
6+
:key="index"
7+
:value="item.value"
8+
:label="item.label"
9+
/>
10+
</FSelect> -->
11+
12+
<FSelect :modelValue="singleSelect" :passive="false" @change="changeSingle">
13+
<FOption
14+
v-for="(item, index) in optionList"
15+
:key="index"
16+
:value="item.value"
17+
:label="item.label"
18+
/>
19+
</FSelect>
20+
</FSpace>
21+
</template>
22+
23+
<script setup>
24+
import { reactive, ref } from 'vue';
25+
26+
const optionList = reactive([
27+
{
28+
value: 'HuNan',
29+
label: '湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南',
30+
},
31+
{
32+
value: 'HuBei',
33+
label: '湖北',
34+
disabled: true,
35+
},
36+
{
37+
value: 'ZheJiang',
38+
label: '浙江',
39+
},
40+
{
41+
value: 'GuangDong',
42+
label: '广东',
43+
},
44+
{
45+
value: 'JiangSu',
46+
label: '江苏',
47+
},
48+
]);
49+
50+
const singleSelect = ref();
51+
function changeSingle(value) {
52+
console.log(value);
53+
singleSelect.value = value;
54+
}
55+
</script>
56+
57+
<style scoped>
58+
.fes-select {
59+
width: 200px;
60+
}
61+
</style>

0 commit comments

Comments
 (0)