先说前提.
这是极其小几率出现的 bug, 并不影响一般的网页开发. 只是谷歌内核开发的时候才会出现, 基本和前端关系不大.
我们公司有一个 pc 端的客户端, 内嵌了一个谷歌的浏览器. 为了处理某些奇奇怪怪的需求, 会按照某些规则屏蔽键盘事件. 只有在当前 focus 可编辑的时候, 才可以使用 backspace 键这样子, 客户端研发那边使用谷歌的 FocusOnEditableField 的 API 来识别当前的编辑状态
我在使用 element 重构项目之后, 使用了 el-select 的远程查找, 筛选功能, 在这里 发现 el-select 组件全部无法触发 FocusOnEditableField 了... 于是开始了 dubug 之旅.
这个 API 提到 当前 focus 的元素如果是可编辑状态, 就返回 true. 这让我想起了 dom 里面很冷门的一个 API
contenteditable 属性规定是否可编辑元素的内容.
OK, 来试一下. 这里有几个 API 可以帮我测试一下
document.activeElement 获取当前文档中获得焦点的元素
dom.contentEditable 获取当前元素的 contentEditable 值
- mounted () {
- document.onkeydown = function (e) {
- console.log(document.activeElement.contentEditable)
- }
- },
但是实际打印出来的都是 inherit, 看来 contentEditable 方法也和 dom.style 一样只能获得行内的值. 翻翻 MDN 找到了这个 apiisContentEditable 他可以获取计算之后的 contentEditable 的值.
OK. 再来试试
- mounted () {
- document.onkeydown = function (e) {
- console.log(document.activeElement.isContentEditable)
- }
- },
在 select 组建中测试返回 false.OK. 我们直接通过修改 dom 属性的方式来看看是不是这个属性的问题.
- mounted() {
- if (this.$refs.elSelect.$children[0].$options.name === 'ElInput') {
- this.$refs.elSelect.$children[0].$refs.input.contentEditable = true
- }
- },
组件加载出来之后, 暴力的修改 contentEditable 属性.. 测试一下, 功能 OK, 打完收工~.
当然不可能, 虽然实现了功能, 但是有很多疑点啊. 原生的 input 和 elemen 库的 input 都可以正常使用, 应该还有更深层次的问题.. 没办法了, 看源码吧
从 package 里得知, element 通过 USE 引入的组件都是从 \ node_modules\element-ui\lib\element-ui.common.JS 这里引入的. package 里的是未编译过的组件. 开始翻阅源码
然后一无所获..
编译过的这个文件虽然可以打断点, 但是没有办法去看具体 dom 的绑定属性.
那就用更简单暴力的方式, 直接从页面上导入 element 未编译的组建. node_modules/element-ui/packages/select/src/select.vue 就是他了. 然后项目就挂了. 这个组件还引入了其他组件, 其中有一部分使用了 jsx. 再该环境太麻烦了. 直接把和 bug 无关的代码全部干掉, 项目总算又跑起来了.
这样就清晰多了..
来看看 element 的 select 组建, 涨姿势了.
compositionstart, compositionupdate,compositionend
这三个事件, 新姿势 get~.
回归业务, 继续找 bug.
嗯~~~disabled,readonly 这两个属性比较可疑啊.
删掉 readonly 果然就 OK 了. 看来问题出现在这里了.
再看看相关的代码
- // 点击父元素的时候
- toggleMenu() {
- if (!this.selectDisabled) {
- if (this.menuVisibleOnFocus) {
- this.menuVisibleOnFocus = false;
- } else {
- this.visible = !this.visible;
- }
- if (this.visible) {
- (this.$refs.input || this.$refs.reference).focus();
- }
- }
- },
- computed: {
- readonly() {
- // trade-off for IE input readonly problem: https://github.com/ElemeFE/element/issues/10403
- const isIE = !this.$isServer && !isNaN(Number(document.documentMode));
- return !this.filterable || this.multiple || !isIE && !this.visible;
- },
- }
逻辑就应该是, 点击父元素的时候, 更改 visible 的值, 然后通过计算属性计算出 readonly, 然后通过 v-bind 来修改 dom 的属性.
看来找到原因了, 看起来这个操作是同步事件, 实际上 dom 的更新是异步事件.
而谷歌的 FocusOnEditableField 逻辑应该是, 有元素进入 focus-> 检查该元素的 readonly,contentEditable 等属性.
而实际上呢. 修改了 readonly -> 调用 focus 事件 这里并不是同步的, input 在只读状态进入了 focus. 所以无法触发 FocusOnEditableField 事件了.
具体的可以参考 Vue.nextTick 方法
写个 demoe 来试试
- <div style="width: 300px;height: 300px;background-color: pink" @click.stop="toggleMenu">
- <input ref="input" type="text" :readonly="readonly">
- </div>
- data () {
- return {
- readonly: true
- }
- },
- methods: {
- toggleMenu() {
- this.readonly = false
- console.log(this.$refs.input.readOnly )
- },
- }
嗯~ 果然. 第一次点击的时候打印的是 true. 在改一下
- toggleMenu() {
- this.readonly = false
- this.$nextTick(() => {
- console.log(this.$refs.input.readOnly)
- this.$refs.input.focus()
- })
- },
总算找到了问题的原因.
望着桌子上的头发, 这波不亏.
ps: 最近在翻阅 element 的源码, 收获颇丰. element 封装的 dispatch 方法, broadcast 方法也给我以后处理 vue 组建提供了灵感. 感谢大佬们的无私奉献.
来源: https://juejin.im/post/5bd7e85df265da0ae34441f0