vue3输入框生成的时候自动获取焦点
前言
当我们在做vue3
的项目的时候,在对一些信息的修改的时候,需要双击或者点击按钮来进行操作,让数据变成输入框来进行修改数据,当输入框失去焦点的时候就进行保存,然而不方便的是,输入框出现的时候不能获取焦点导致用户的体验不好。
创建实例演示(创建文件,可忽略)
首先我们需要一个vue3
的项目,如何创建一个vue3
的项目,新建一个空的文件夹,cmd
打开,输入
1. vue create 项目的名称
举例:vue create demo
我们选择自定义,即
2. Manually select features
回车,按需引入自己需要的,按空格即代表选中
3. 举例:
>(*) Babel
(*) TypeScript
( ) Progressive Web App (PWA) Support
(*) Router
(*) Vuex
(*) CSS Pre-processors
(*) Linter / Formatter
( ) Unit Testing
( ) E2E Testing
回车
4. 选择要用来启动项目的 Vue.js版本(使用箭头键),选择3.x
5. 使用类样式组件语法? 输入N
6. 将 Babel 与 TypeScript 一起使用(现代模式、自动检测的 polyfills、转译 JSX 需要)?输入Y
7. 路由器使用history模式?选择N (这个决定你编译出来的地址是否含有 /#/ 的字样)
8. 选择一个 CSS 预处理器 这里按自己的需求来,这里我选择Sass/SCSS (with dart-sass)
9. 选择一个 linter /格式化程序配置 选择ESLint + Standard config
10. Pick additional lint features 选择Lint on save
11. 您更喜欢将 Babel、ESLint 等的配置放在何处? 选择In dedicated config files,这样好处理
12. 将此保存为将来项目的预设?这里我选择N,看个人的需求了
定义一些简单的页面,我们用到了bootstrap
的一些样式需要在public
文件夹的index.html
添加外部样式的引入<link href=”https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css" rel=”stylesheet”>书写在title
标签下即可,在HomeView
页面文件书写简单的代码
<template>
<div class="container">
<ul class="list-group">
<template v-for="(i, index) in list" :key="index">
<li class="list-group-item d-flex justify-content-between" v-if="!i.checked">
<div class="form-group form-check mb-0">
<input type="checkbox" class="form-check-input" />
<label v-if="!i.isEdit" class="form-check-label" @dblclick="showEdit(i, index)"> {{ i.name }} </label>
<label v-else class="form-check-label" :for="'i-' + index">
<!-- -->
<input type="text" v-model="editValue" @blur="changeEdit" ref="myInput"
/></label>
</div>
<button type="button" class="close" aria-label="Close" @click="remove(index)">
<span aria-hidden="true">×</span>
</button>
</li>
</template>
</ul>
</div>
</template>
JS
代码如下
<script lang="ts">
import { defineComponent, reactive, toRefs, ref } from 'vue'
export default defineComponent({
name: 'HomeView',
setup() {
// 元素节点
const myInput = ref(null)
// 编辑的索引
let editIndex = 0
// 是否获取焦点
const open = 0
// 数据
const state = reactive({
value: '',
editValue: '',
list: [
{
name: '1',
checked: false,
isEdit: false
},
{
name: '2',
checked: false,
isEdit: false
},
{
name: '3',
checked: false,
isEdit: false
}
]
})
// 双击修改
const showEdit = (item, index) => {
if (open == 0) {
open = 1
editIndex = index
item.isEdit = true
state.editValue = item.name
}
}
// 失去焦点
const changeEdit = () => {
state.list[editIndex] = {
name: state.editValue,
checked: false,
isEdit: false
}
open = 0
editIndex = 0
}
// 移除
const remove = (index) => {
state.list.splice(index, 1)
}
return {
...toRefs(state),
showEdit,
changeEdit,
remove,
myInput
}
}
})
</script>
基础页面搭建好了之后,在终端输入
npm run serve
将项目跑起来,我们会看到三条任务
双击就可以编辑,点击叉号可以删除,复选框和添加的未加上
解决方法
当我们双击进行编辑的时候,会发现输入框不能获取焦点,用户的输入十分不方便,而且当我们想失去焦点的时候,也不许去点击输入框再失去焦点才能取消修改,十分的麻烦。
1、方法一
我们可以添加异步的手法让输入框出现之后再执行获取焦点的手法来解决,具体的解决代码如下
// 双击修改
const showEdit = (item, index) => {
if (open == 0) {
setTimeout(() => {
myInput.value[0].focus()
})
open = 1
editIndex = index
item.isEdit = true
state.editValue = item.name
}
}
通过插入一个延时器来解决问题
2、方法二
熟悉vue2
的朋友应该知道$nextTick
就可以解决,但在这里vue3
需要配合监听使用,还需要ref
来选择我们需要操作的元素对象,查阅官网的ref
使用方法,和vue2
不一样,在vue2
中,我们需要给我们需要的元素节点打上ref
标签,读取直接使用this.$refs
来操作,演示如下
template:
<button ref="btn"></button>
methods:
this.$refs.btn
在vue3
中的使用读取:
<template>
<div ref="box">div</div>
</template>
<script>
// 引入
import { onMounted, ref } from 'vue';
export default {
name: 'App',
setup() {
let box = ref(null);
// onMounted() 中的行为会在声明周期 mounted 中执行。
onMounted(() => {
// 在这里就可以读取到我们需要的元素节点
console.log('box.value', box.value);
})
return {box};
}
}
</script>
但是,我们的input
框是根据state.list
每一项的isEdit
决定的,也就是说onMounted
执行的时候,我们的输入框一直是未打开的,只有当我们双击的时候,才会唤醒我们的输入框,而onMounted
只会在页面完全加载的时候执行一次,所以当我们双击的时候,控制台就会打印出null
,此时我们是获取不到我们需要的元素节点,也就是代码
// 使用之前记得引入onMounted
onMounted(() => {
console.log('myInput.value', myInput.value)
})
控制台打印出的是null
这时候获取焦点
onMounted(() => {
console.log('myInput.value', myInput.value)
myInput.value[0].focus()
})
控制台就会报错
Uncaught TypeError: Cannot read properties of null (reading '0')
at Proxy.showEdit (HomeView.vue?4752:47:1)
at onDblclick (HomeView.vue?475e:67:1)
at callWithErrorHandling (runtime-core.esm-bundler.js?d2dd:155:1)
at callWithAsyncErrorHandling (runtime-core.esm-bundler.js?d2dd:164:1)
at HTMLLabelElement.invoker (runtime-dom.esm-bundler.js?2725:369:1)
如果我们使用onUpdated
,通过获取页面更新之后就去让输入框获取焦点
// 使用之前记得引入onUpdated
onUpdated(() => {
console.log('myInput.value', myInput.value)
})
我们可以获取一个空数组的代理对象,而且我们失去焦点就会报错
// 代理对象
Proxy {0: input}[[Handler]]: Object
[[Target]]: Array(0)
[[IsRevoked]]: false
报错:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'focus')
at eval (demoView.vue?0f1f:69:1)
at callWithErrorHandling (runtime-core.esm-bundler.js?d2dd:155:1)
at callWithAsyncErrorHandling (runtime-core.esm-bundler.js?d2dd:164:1)
at Array.hook.__weh.hook.__weh (runtime-core.esm-bundler.js?d2dd:2685:1)
at flushPostFlushCbs (runtime-core.esm-bundler.js?d2dd:356:1)
at flushJobs (runtime-core.esm-bundler.js?d2dd:401:1)
我们需要监视open
的变化,在使用nextTick
当页面完全加载的时候获取输入框的元素节点在使其获取焦点接口,因为我们之前是使用let open = 0
,我们需要一定的修改,否则watch
的open
会报错:
No overload matches this call.
The last overload gave the following error.
Argument of type 'number' is not assignable to parameter of type 'object'.
引入:
import { defineComponent, reactive, toRefs, ref, nextTick, watch } from 'vue'
修改open
:
// 是否获取焦点
let open = ref(0)
将后面所用到的所有open
改成open.value
,添加监听代码:
watch(open, (newValue, oldValue) => {
if (newValue == 1) {
nextTick(() => {
console.log(myInput.value)
console.log('--- DOM更新了 ---')
myInput.value[0].focus()
})
}
})
这时候我们就可以在每次双击改变open
,即代码:open.value = 1
,就会被监听到,从而使输入框获取焦点。