日常工作中,使用vue进行项目开发居多,因此把一些vue项目常见问题进行总结,在此记录,避免以后开发中做过多的重复工作。
而掌握一些有用的技巧,使用一些更高级的技术点,也能让我们成为更好的 Vue 开发者
当 watch 一个变量的时候,初始化时并不会执行,如下面的例子,你需要在created的时候手动调用一次。
- // bad
- created() {
- this.getsearchText();
- },
- watch: {
- searchText: 'getSearchText',
- }
- 复制代码
你可以添加immediate属性,这样初始化的时候也会触发,代码也就可以简化成这样
- // good
- watch: {
- searchText: {
- handler: 'getSearchText',
- immediate: true,
- }
- }
- 复制代码
使用 this.$router.resolve
- const openNewUrl=(url) => {
- let routeData = this.$router.resolve({path: url})
- window.open(routeData.href, '_blank')
- }
- 复制代码
- <el-input v-model.number="num" onkeyup="this.value = this.value.replace(/[^\d.]/g,'');"></el-input>
- 复制代码
v-model拆分为:value和@input
- <el-input :value="input" @input='e => input = idCardValid (e)' placeholder="请输入内容"></el-input>
- 复制代码
- methods:{
- idCardValid(val){
- const idCard= val.replace(/^(\d{6})\d+(\d{4})$/, "$1******$2")
- console.log(idCard)
- return idCard
- }
- },
- 复制代码
- <a href="/demo.rar" download="demo.rar">点击下载</a>
- 复制代码
例如我们检测一个id为 target 的 div 目标元素
- let el= document.querySelector('#target')
- window.addEventListener('mousedown', e => {
- // 获取被点击的元素
- const clickedEl = e.target;
-
- if (el.contains(clickedEl)) {
- //在 "el "里面点击了
- } else {
- //在 "el "外点击了
- }
- });
- 复制代码
- const { href } = this.$router.resolve({ path: "/index", query: { key: key } });
- // iframe 控制父页面跳转
- window.parent.window.location.href = href
- 复制代码
开发中用到定时器时我们一般这样
- // bad
- mounted() {
- // 创建一个定时器
- this.timer = setInterval(() => {
- // ......
- }, 500);
- },
- // 销毁这个定时器。
- beforeDestroy() {
- if (this.timer) {
- clearInterval(this.timer);
- this.timer = null;
- }
- }
- 复制代码
而借助 hook,可以更方便维护
- // good
- mounted() {
- let timer = setInterval(() => {
- // ......
- }, 500);
- this.$once("hook:beforeDestroy", function() {
- if (timer) {
- clearInterval(timer);
- timer = null;
- }
- });
- }
- 复制代码
原本
- //父组件
- <child
- :value="value"
- @childMounted="onChildMounted"
- />
- method () {
- onChildMounted() {
- // do something...
- }
- },
-
- // 子组件
- mounted () {
- this.$emit('childMounted')
- },
- 复制代码
- //父组件
- <child
- :value="value"
- @hook:mounted="onChildMounted"
- />
- method () {
- onChildMounted() {
- // do something...
- }
- },
- 复制代码
在Vue组件中,可以用过$on,$once
去监听所有的生命周期钩子函数,如监听组件的 updated 钩子函数可以写成 this.$on('hook:updated', () => {})
我们有时会遇到这样的情况,用了一个第三方组件,当需要监听第三方组件数据的变化,但是组件又没有提供change事件时。我们可以利用Vue 提供的@hook:updated
来监听组件的 updated 生命钩子函数
- <template>
-
-
- <custom-select @hook:updated="onSelectUpdated" />
- template>
- <script>
- import CustomSelect from './components/custom-select'
- export default {
- components: {
- CustomSelect
- },
- methods: {
- onSelectUpdated() {
- console.log('custom-select组件的updated钩子函数被触发')
- }
- }
- }
- script>
- 复制代码
在vue的router的js中添加下面代码,new VueRouter 前
- const originalPush = VueRouter.prototype.push
- const originalReplace = VueRouter.prototype.replace
- // push
- VueRouter.prototype.push = function push(location, onResolve, onReject) {
- if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
- return originalPush.call(this, location).catch(err => err)
- }
- // replace
- VueRouter.prototype.replace = function push(location, onResolve, onReject) {
- if (onResolve || onReject) return originalReplace.call(this, location, onResolve, onReject)
- return originalReplace.call(this, location).catch(err => err)
- }
- 复制代码
根目录下新建文件 vue.config.js
- // vue.config.js
- module.exports = {
- publicPath: './'
- }
- 复制代码
默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上,例如 www.my-app.com/。如果应用被部署在一个… www.my-app.com/my-app/,则设置 publicPath 为 /my-app/。
这个值也可以被设置为空字符串 ('') 或是相对路径 ('./'),这样所有的资源都会被链接为相对路径,这样打出来的包可以被部署在任意路径。
如果我们布局的是fixed并且想要高度为100%的时候,我们一般会这样设置:
- div {
- display:fixed;
- height:100%;
- overflow:scroll;
- }
- 复制代码
但是这样的话不会出现滚动条,设置
- div {
- top: 0;
- bottom:0;
- position:fixed;
- overflow-y:scroll;
- overflow-x:hidden;
- }
- 复制代码
require.context():
你可以通过 require.context() 函数来创建自己的 context。
可以给这个函数传入三个参数:一个要搜索的目录,一个标记表示是否还搜索其子目录, 以及一个匹配文件的正则表达式。
webpack 会在构建中解析代码中的 require.context() 。
- // 利用require.context()自动引入 除 index.js 外其他 js 文件
- const routerContext = require.context('./', true, /\.js$/)
- routerContext.keys().forEach(route => {
- // 如果是根目录的 index.js 、不处理
- if (route.startsWith('./index')) {
- return
- }
- const routerModule = routerContext(route)
- /**
- * 兼容 import export 和 require module.export 两种规范
- */
- routes = routes.concat(routerModule.default || routerModule)
- })
- 复制代码
vue.config.js 中配置
- configureWebpack: (config) => {
- if (process.env.NODE_ENV === "production") {
- config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true;
- config.optimization.minimizer[0].options.terserOptions.compress.pure_funcs = [
- "console.log",
- ];
- }
- }
- 复制代码
当一个 form 元素中只有一个输入框时,在该输入框中按下回车应提交该表单。如果希望阻止这一默认行为,可以在
标签上添加 @submit.native.prevent。
- <templat>
- <el-form @submit.native.prevent >< /el-form >
- </templat>
- 复制代码
使用样式穿透修改下拉框样式,你会发现打死都不生效,那是因为下拉框是默认挂载在 body 下面。解决办法:设置 :popper-append-to-body="false"
,然后再用样式穿透
- @change="onChange($event,customParam)"
- 复制代码
- @change="((val)=>{changeEvent(val,args)})"
- 复制代码
其他组件的的默认事件同样的方法传递
- <el-dropdown trigger="click" @command="((val)=>{handleCommand(val,scope.row)})">
- <span class="el-dropdown-link">
- <i class="el-icon-more el-icon--right"></i>
- </span>
- <el-dropdown-menu slot="dropdown">
- <el-dropdown-item command="volumes">新增</el-dropdown-item>
- <el-dropdown-item command="log">查看</el-dropdown-item>
- <el-dropdown-item command="shell">更新</el-dropdown-item>
- <el-dropdown-item command="container">删除</el-dropdown-item>
- </el-dropdown-menu>
- </el-dropdown>
- 复制代码
解决
- <el-input class="clear-number-input" type="number"></el-input>
-
- <style scoped>
- .clear-number-input ::v-deep input[type="number"]::-webkit-outer-spin-button,
- .clear-number-input ::v-deep input[type="number"]::-webkit-inner-spin-button {
- -webkit-appearance: none !important;
- }
- </style>
- 复制代码
我们在开发登录页的时候经常遇到,登陆页的表单自动填充导致input文本框背景失效的问题。
- // 自动填充样式覆盖
- input:-internal-autofill-previewed,
- input:-internal-autofill-selected {
- -webkit-text-fill-color: #fff;
- transition: background-color 5000s ease-out 0.5s;
- }
- 复制代码
$options是一个记录当前Vue组件的初始化属性选项,当我们想把 data 里的某个值重置为初始值时,非常有用
例如:
- this.value = this.$options.data().value;
- 复制代码
利用上面介绍的 $options 特性
我们经常的业务场景是这样:一个el-dialog中有一个el-form,而且我们通常是新增和编辑复用同一个组件,现在我们要求每次打开el-dialog时都要重置el-form里的数据,并且清除校验状态。
- // 弹框打开时
- initForm(){
- this.$refs['form'] && this.$refs['form'].resetFields()
- this.form = this.$options.data.call(this).form;
- }
- 复制代码
我们在使用 prop 时,可能会有时候需要判断该 prop 是否在我们规定的范围内(或者说规定的值内),这个时候我们可以使用 prop 定义中的 validator 选项,将一个 prop 类型限制在一组特定的值里。
- // 只能选择一个
- props: {
- type: String,
- validator(value) {
- return ['A', 'B', 'C'].indexOf(value) > -1
- }
- }
- 复制代码
validator 函数接收一个prop值,如果 prop 有效或无效,则返回 true 或 false。
在做二次封装时,我们经常用到,v-bind="$attrs"
和 v-on="$listeners"
进行多层组件监听,那么我们还可以利用 $listeners
在子组件中判断父组件是否传来事件
例如我们封装一个搜索组件,里面有重置按钮,当我们点击重置按钮时,默认操作是清空搜索栏的值并且刷新列表,而如果父组件传来事件,则以自定义事件为准,即我们想点击重置按钮做一些其他的自定义操作。
- resetFields() {
- //...
- if (this.$listeners.resetFields) {
- // 自定义事件
- this.$emit('resetFields')
- } else {
- // 默认刷新列表事件
- this.loadList()
- }
- }
- 复制代码
为每个table指定对应且唯一的key属性。
其他一些类似的问题也可以尝试为其添加key属性来解决
- <template>
- <el-form ref="form1"></el-form>
- <el-form ref="form2"></el-form>
- <el-form ref="form3"></el-form>
- </template>
- <script>
- export default{
- methods: {
- onValidate() { // 保存操作
- const formArr =['form1', 'form2','form3']//三个form表单的ref
- const resultArr = [] //用来接受返回结果的数组
- let _self = this
- function checkForm(formName) { //封装验证表单的函数
- let result = new Promise(function (resolve, reject) {
- _self.$refs[formName].validate((valid) => {
- if (valid) {
- resolve();
- } else { reject() }
- })
- })
- resultArr.push(result) // 得到promise的结果
- }
- formArr.forEach(item => { // 根据表单的ref校验
- checkForm(item)
- })
- Promise.all(resultArr).then(values => {
- // 此时必填完成,做保存后的业务操作
- // ...
- console.log('success');
- }).catch(_ => {
- console.log('err')
- })
- },
- }
- }
- </script>
- 复制代码
- <script>
- import { debounce } from "lodash";
-
- export default {
- methods: {
- search: debounce(async function (keyword) {
- // ... 请求逻辑
- }, 500),
- },
- };
- </script>
- 复制代码
-
- <template>
- <slot change-display="changeDisplay">slot>
- <div v-show="visiable">*下拉框代码省略*<div>
- <template>
-
- <script>
- export default {
- data(){
- return {
- visiable: false
- }
- }
-
- methods:{
- changeDisplay(){
- this.visiable = !this.visiable
- }
- }
- }
- script>
- 复制代码
使用:
- <dropdown v-model="value" :list="list">
- <button slot-scope="{changeDisplay}"
- @click="changeDisplay">{{value}}button>
- dropdown>
- 复制代码
- <!-- 伪代码:下拉框组件 -->
- <template>
- <slot></slot>
- <div v-show="visiable">*下拉框代码省略*<div>
- <template>
-
- <script>
- export default {
- data(){
- return {
- visiable: false
- reference: undefined
- }
- }
-
- methods:{
- changeDisplay(){
- this.visiable = !this.visiable
- }
- }
-
- mounted() {
- if (this.$slots.default) {
- this.reference = this.$slots.default[0].elm
- }
- if (this.reference) {
- this.reference.addEventListener('click', this.changeVisiable, false)
- // hook
- this.$once('hook:beforeDestroy', () => {
- this.reference.removeEventListener('click', this.changeVisiable)
- })
- }
- }
- }
- </script>
- 复制代码
在二次封装组件时,我们知道可以通过判断 $slots.xxx
是否存在来判断我们在使用这个组件时是否传递了插槽内容。从而更好的定制默认的插槽内容。
那么在二次封装一个原本具有作用域插槽的组件时,我们可以通过 $scopedSlots.xxx
来进行判断
子组件
- <template>
- <Tree class="tree" v-if="items.length" :data="items" :options="options" :filter='search' ref="tree" v-model="treeModel">
- <!-- 作用域插槽 -->
- <template slot-scope="{node}">
- <span v-if="!$scopedSlots.default">{{node.text}}</span>
- <slot v-else :node="node"></slot>
- </template>
- </Tree>
- </template>
-
- 使用
-
- ```vue
- <template>
- <custom-tree ref="tree" checkbox :data='data' :props="{children:'children',text: 'text'}">
- <template slot-scope="{node}">
- <span class="tree-text">
- <!-- 自定义插槽内容 -->
- <template v-if="!node.children">
- <van-icon name="user-o" size="18" class="icon" />
- {{ node.text }}
- </template>
- <template v-else>
- {{ node.text }}
- </template>
- </span>
- </template>
- </custom-tree>
- </template>