表单验证: vee-validate插件

  • 安装
- npm i vee-validate@2 --save // 安装'2'版本的
  • 插件 模块化
### plugins.validate.js
    import Vue from 'vue'
    import VeeValidate from 'vee-validate'

    console.log('表单验证',VeeValidate) // 测试
    Vue.use(VeeValidate)
    
### main.js
	......
	import "@/plugins/validate" // 加载插件
  • 配置字段
### plugins.validate.js

import Vue from 'vue'
import VeeValidate from 'vee-validate'
import zh_CN from 'vee-validate/dist/locale/zh_CN'

Vue.use(VeeValidate)

// 表单验证
VeeValidate.Validator.localize('zh_CN', {
    messages: {
        ...zh_CN.messages,
        is: (field) => `${field}必须与密码相同`   // 修改内置规则 message,  让确认密码与密码相同
    },
    attributes: { // 给校验的field属性名映射中文名称
        phone: '手机号',
        code: '验证码',
        password: '密码',
        password1: '确认密码',
        agree:'用户协议'
    }
})

// 自定义校验规则:用户必须同意协议
VeeValidate.Validator.extend('tongyi', {
    validate: value => value,
    getMessage: field => field + '必须同意'
})


  • 使用: 在注册页面补充表单验证的逻辑
### Register.index.vue

<div class="content">
	<label>手机号:</label>
	<input type="text" placeholder="手机号" v-model="phone" name="phone"
	<!--v-validate传入配置项required和'正则'-->       <!--配置错误消息提示-->
	v-validate="{ required:true,regex:/^1\d{10}$/ }" :class="{ invalid:errors.has('phone') }">
	<span class="error-msg">{{ errors.first("phone") }}</span>
  </div>
  <div class="content">
	<label>验证码:</label>
	<input type="text" placeholder="验证码" v-model="code" name="code"
	<!--逻辑同上,只不过正则匹配不同-->
	v-validate="{ required:true,regex:/^\d{6}$/ }" :class="{ invalid:errors.has('code') }">
	<span class="error-msg">{{ errors.first("code") }}</span>
	<button style="width: 100px;height: 38px;" @click="getCode">获取验证码</button>
  </div>
  <div class="content">
	<label>登录密码:</label>
	<input placeholder="请输入你的密码" v-model="password" name="password" type="password"
			<!--正则:允许8~20位数字+密码-->
		   v-validate="{ required: true, regex: /^[0-9A-Za-z]{8,20}$/ }"
		   :class="{ invalid: errors.has('password') }"/>
	<span class="error-msg">{{ errors.first("password") }}</span>
  </div>
  <div class="content">
	<label>确认密码:</label>
	<!--保证和password一致即可-->
	<input placeholder="请再次输入你的密码" v-model="password1" name="password1" type="password"
		   v-validate="{ required: true, is: password}" :class="{ invalid: errors.has('password1') }"/>
	<span class="error-msg">{{ errors.first("password1") }}</span>
  </div>

  <div class="controls">
	<!--使用了'自定义校验'-->
	<input type="checkbox" v-model="agree" name="agree" v-validate="{ required: true, tongyi: true}"
		   :class="{ invalid: errors.has('agree') }"/>
	<span>同意协议并注册《尚品汇用户协议》</span>
	<span class="error-msg">{{ errors.first("agree") }}</span>
  </div>
  ......
  methods:{
	async getCode(){
		......
	},
	async userRegister(){
		// 确保所有的字段通过校验,再进行后续的逻辑
		const success = await this.$validator.validateAll()
		if(success){
			try{
				const {phone,code ,password,password1} = this;
				phone && code && password && password1 && (await this.$store.dispatch('userRegister',{phone,code,password}))
				this.$router.push('/login')
			}catch(error){
				alert(error.message)
			}
		}
	}
}

路由懒加载

  • 引入场景
当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效
### router.routes.js
......
{
	path: "/home",
	// component: Home,
	// 访问该路由的时候,才加载该组件,节省性能的开销
	component: ()=>import('@/pages/Home'),
	meta: {
		show: true
	}
},

打包上线

  • 命令如下:
- npm run build
  • 项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错
  • 有了map就可以像未加密的代码一样,准确输出哪一个行代码有错(该文件如果项目不需要,可以除去)
### vue.config.js配置
......
productionSourceMap:false

自定义事件深入

  • 组件绑定原生DOM事件,默认绑定的是自定义事件
<template>
	<!--此时单击该子组件,是不会触发该事件的-->
	<ChildrenComponent1 @click="xxx"></ChildrenComponent1>
</template>

- 此时如果想触发click事件(即触发原生DOM事件),加一句即可
	<!--"@click.native"就行了-->
	<ChildrenComponent1 @click.native="xxx"></ChildrenComponent1>
	
- 类似自定义事件这样的写法: <ChildrenComponent1 @click="xxx"></ChildrenComponent1>
  本质就是把"click事件"绑定到子组件ChildrenComponent1的根元素div节点上(也就是'事件委托')
  所以点击,根div节点下面的所有元素,该click事件均会被触发
  • 注意事项: 给原生DOM元素绑定自定义事件没有任何意义,因为不会触发$emit

v-model深入解析

  • 基本使用: 收集用户输入的数据,实现数据的双向绑定
......
<input type="text" v-model="msg">
<span>{{msg}}</span>
......
data() {
	return {
		......
		msg:''
	}
},
  • v-model实现的原理: 单向绑定+事件
......
<!--单向绑定 :value + @input-->
<input type="text" :value="msg1" @input="msg1=$event.target.value">
<span>{{msg1}}</span>
......
data() {
	return {
		......
		msg1:''
	}
},
  • 利用v-model的原理,实现父子组件之间的通讯
### 父组件
......
<!--单向绑定+自定义事件input-->
<!--'$event'表示的是'接收子组件传过来的返回值'-->
<SearchSelector :value="msg" @input="msg=$event"/>

### 子组件
......
<!--接收value值并利用原生DOM的input事件,去触发自定义事件'input',并传值-->
<input type="text" :value="value" @input="$emit('input',$event.target.value)">
export default {
	......
	props:['value']
}

- 最终实现效果: 只要修改msg的值,父子组件之间的数据实现同步,即相同

- 父组件的代码,可以化简: <SearchSelector v-model="msg"/>,一模一样的效果
	- 注意: 此时 子组件的props接收值可以任意命名

	
  • sync修饰符:意思就是同步,实现父子组件之间数据的同步(即一致)
### 父组件
......
<!--传值并绑定自定义事件(接收子组件传过来的值,':money'指明要更新的是money属性)-->
<SearchSelector :money="money" @update:money="money=$event"/>
......
data() {
	return {
		......
		money:1000
	}
},

### 子组件
......
<!--接收money值并利用原生DOM的click事件,去触发自定义事件'update:money',并传值-->
<button @click="$emit('update:money',money-100)">{{money}}</button>
export default {
	......
	props:['money']
}

- 最终实现效果: 点击button,父子组件之间的数据money实现同步,即相同

- 引入sync: 实质就是'父组件'写法的'语法糖',简写
	<!--一模一样的效果-->
	<SearchSelector :money.sync="money"/>
	
- 注意: 此时子组件的'update'方法不能随意命名
  • sync文档参考地址
- https://www.cnblogs.com/aimee2004/p/15776454.html
  • 小结: 看到组件内传v-model或者sync修饰符```即表明,父子组件之间数据互相影响并且保持一致

$attrs属性介绍

  • 作用: 获取父组件传过来的所有属性值(前提是: props不接收,若接收了,$attrs就取不到值)

  • 引入场景: 二次封装element-ui-button,增加鼠标hover提示

- 先安装'element-ui'插件

- 新建组件

	### HintButton.vue
	<template>
        <div>
        	<!--运用父组件传过来的四个值-->
            <a :title="title">
                <el-button :type="type" :icon="icon" :size="size">添加</el-button>
            </a>

        </div>
    </template>

    <script>

        export default {
            name: 'HintButton',
            props:["type","icon","size","title"], // 接收父组件传过来的四个值
            mounted(){
                console.log(this.$attrs) // 此时为 {} 空对象,因为被props占用了
            }
        }
    </script>
    
    ### 父组件
    ......
    <!--给子组件传4个值-->
    <!--当鼠标移动到该按钮时,会有hover提示-->
    <HintButton type="primary" icon="el-icon-plus" size="mini" title="提示按钮"></HintButton>
    ......
    <script>
        ......'
        import HintButton from '../HintButton/index.vue'

        export default {
            name: 'SearchSelector',
            ......
            components:{
                HintButton // 注册
            }
        }
    </script>

  • 现在,使用$attrs实现一模一样的效果(代码可以更加精简)
### 父组件不变,小改子组件
<template>
	<div>
		<!--直接从$attrs取值,更为方便-->
		<a :title="$attrs.title">
			<el-button :type="$attrs.type" :icon="$attrs.icon" :size="$attrs.size">添加</el-button>
		</a>
		
	</div>
</template>

<script>
	
	export default {
		name: 'HintButton',
		// props:["type","icon","size","title"], // 必须注释掉
	}
</script>

  • 上述写法可以进一步精简成以下模样
<template>
	<div>
		<a :title="$attrs.title">
			<!-- <el-button :type="$attrs.type" :icon="$attrs.icon" :size="$attrs.size">添加</el-button> -->
			<!--最终精简:获取所有的$attrs属性,然后打散后分配给el-button-->
			<el-button v-bind="$attrs">添加</el-button>
		</a>
	</div>
</template>

$listeners属性介绍

  • 作用: 获取父组件传过来的自定义事件对象

  • 引入场景: 父组件子组件传递自定义事件,由子组件触发该事件

### 父组件
......
<!--绑定自定义事件click(这里若想实现原生click事件,加'.native'即可)-->
<HintButton ...... @click="handler"></HintButton>

### 子组件
<template>
	<div>
		<a :title="$attrs.title">
			<!--使用v-on触发'自定义事件'-->
			<el-button v-bind="$attrs" v-on="$listeners">添加</el-button>
		</a>
		
	</div>
</template>

<script>
	
	export default {
		name: 'HintButton',
		......
		mounted(){
			console.log(this.$listeners) // {click:f......}
		}
	}
</script>

$children$parent介绍

  • $children获取所有的子组件

  • $parent获取父组件

  • 引入场景demo

### 父组件
......
父亲的钱是: {{money}}元
<button @click="borrowFromSon">找儿子借100元</button>
<SearchSelector ref="son"/>
......
data() {
	return {
		......
		money:1000 // 初始化值
	}
},
methods:{
	......
	borrowFromSon(){ // 同时操作本组件和子组件的值
		this.money +=100
		this.$refs.son.qian -= 100
	}
},

### 子组件
......
儿子的钱是: {{qian}}
......
data(){
	return {
		qian:700 // 初始化
	}
},

  • 使用$children实现上述场景
### 父组件
......
父亲的钱是: {{money}}元
<!--绑定事件-->
<button @click="borrowAll">向所有人借150元</button>
<SearchSelector ref="son"/>
......
data() {
	return {
		......
		money:1000 // 初始化值
	}
},
methods:{
	......
	borrowAll(){
	this.money +=150
	// $children 是array,存储所有的子组件实例
	// 注意不要用索引($children[0])去访问,可能该'项'不是你想要的'项'
	this.$children.forEach(item=>{
		item.qian -= 150
	})
}
},

### 子组件不变
  • 使用$parentdemo
### 子组件
......
儿子的钱是: {{qian}}
<button @click="sendMoney(200)">给父亲200块</button>
......
methods:{
	......
	sendMoney(money){
		this.qian -= money
		this.$parent.money += money // 获取父组件实例并赋值
	}
}
  • ref属性的两个作用
    • 标识该组件
    • 获取该组件实例的所有信息

mixin: 打包公用的js逻辑

### 新建 myMixin目录,新建 myMixin.js

export default {
	methods:{
		giveMoney(money){
			this.money -= money;
			this.$parent.money += money
		}
	}
}

### 组件使用
......
import myMixin from 'xxxx'
mixins:[myMixin] // 使用钩子配置项