实际使用
- 覆盖 select panel 的 border color
- 覆盖 select panel 的 scroll bar 样式
- 显示小三角形
- select panel 中的 search icon 的位置 和样式
- select option 内容超出并省略的内容,则显示省略号,如果内容很长,隐藏起来,如何配置 total 提示
TODO
- 提供清空所有选中项的快捷 icon ❎
- 忽略过滤条件 大小写 - 涉及正则表达式的书写,可以作为面试点
- 高亮过滤内容
实现
-
Select 控制整体样式和事件
- 下拉框的展开和收起
- 统计所有Option 的数量
- 统计选择的所有的Option 的数量(响应子组件的点击事件,判断用户点击该选项,是选中还是取消选中)
- 缓存所有的Option对象,用于显示用户选择的 Option 的 Label
- 广播过滤条件
-
Option 负责单个选项
- 内容展示
- OptionsCount++
- SelectedOptionsCount++
- 向 Select 组件传递点击事件
- 判断当前Option是否选中, 当用户点击时候,向上 dispatch 点击事件以及被点击的当前 Option
- 需要根据 Select 组件的 Value (CheckedOptions) 来判断
- 无法根据用户的「点击行为」直接得出是选中还是取消的。
- 用户点击 Option,Select 接收 「点击的 Option」,根据如果 Option 已经出现在 SelectedOptions 中,则意味着 取消选中,反之则为选中
- 用于初始化用户传入的数据
- 接收 Select 传入的过滤条件。判断当前选项是否 visible
- 如果匹配,则设置 visible,且更新可见 Option 数量,进而更新可见区域高度
实现关键点
-
dispatch 和 broadcast(在Vue 源码中也有出现,如 keep-alive 组件)
- Select 通知 Option 进行过滤:
this.broadcast('GrOption', 'queryChange', query)
- Option 通知 Select 当前项被点击:
this.dispatch('GrSelect', 'onSelectOptionClick', this)
function broadcast (componentName, eventName, params) { this.$children.forEach(child => { const name = child.$options.name if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)) } else { // todo 如果 params 是空数组,接收到的会是 undefined broadcast.apply(child, [componentName, eventName].concat([params])) } }) } export default { methods: { dispatch (componentName, eventName, params) { let parent = this.$parent || this.$root let name = parent.$options.name // 经常在 Vue 源码中见到 // commonly used in Vue source code while (parent && (!name || name !== componentName)) { parent = parent.$parent if (parent) { name = parent.$options.name } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)) } }, broadcast (componentName, eventName, params) { broadcast.call(this, componentName, eventName, params) } } }
- Select 通知 Option 进行过滤:
-
Select 展开之后,过滤框自动获得焦点 StackOverFlow 中的解释
this.$nextTick(() => { this.$refs.input.focus() })
-
innerSelectedOptionsValue 需要这个值的原因:
<xx-select :value="[1,2,3]" @on-change="onChange"> <xx-option :value="1">Number1</xx-option> <xx-option :value="2">Number2</xx-option> </xx-select>
export default { data() { return { options: [1,2] } }, methods: { onChange(val) { this.options = val // 这边如果这样写, 则会在 option 内部重新触发 判断 当前 option 是否 active/selected // Element-UI 中流程如下: // value => this.select.value => xx-option inject => computed optionSelected => this.contains; // 但是按照 Element-UI 中的代码,发现如果不这样写(this.options = val)的话,则在点击新的 option的时候,不会触发 option 的 active/selected 效果 // 因为在 select.vue 的 handleOptionSelect 中,只是将 value 做了copy 并且 将增/删 之后的备份 value emit 出去 // 因此 value 并没有修改(这是正确的做法),但是这样也就导致了 value 没有变化,导致 option.vue 中的 itemSelected 中的 this.select.value.contains 也不会有变化。 // 这样就会有问题,因此需要将用户 toggle option 之后在 select.vue 中 emit 出去的修改之后的 copy 的 value 赋值给 innerValue, // 然后在 option.vue 中使用这个 innerValue 来做 contains 判断。 // 在 provider 和 inject 中使用,用于判断该 option 是否被选中 } } }