Vue
整体要学的
vue基础
vue-cli
vue-router
vuex
element-ui
vue3
简介
特点:
- 组件化
- 声明式编码
- 使用虚拟dom + 优秀Diff算法
在生成真实dom的时候,会先有一部虚拟dom,且新的虚拟dom会和旧的虚拟dom进行diff算法比较
如果有些dom没改,那么就可以不用再重新渲染了,提高性能
环境
安装node
node -> 16.20.2
切换淘宝镜像
npm install -g cnpm -registry=http://registry.npm.taobao.org
npm config set registry http://registry.npm.taobao.org/
使用了第二个,下一步才有用
安装vue
npm install -g @vue/cli
vscode中不给运行vue解决办法
set-ExecutionPolicy RemoteSigne
问题就是脚本的执行策略不行,应该是安全问题
创建vue项目
create vue jplan
vue还不给输入大写
选自己的配置
Vuex + router + CSS Pre-processors
版本
2.x
路径选择
history router no
css预处理语言 less
配置文件 -> package.json
启动
npm run serve
引入element-ui
引入
npm i element-ui -S
目录下必须有package.json
在 main.js 中写入以下内容:
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
引入Axios
安装
npm install axios
npm install vue-axios
main.js引入
//引入axios
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios,axios)
开发者工具devtools
vue2 只能用vue-devtools
Vue核心
模版语法
插值语法
{{ xxx}}
指令语法
v-bind 简写是 :xxx
数据绑定
单向的是 :value=" "
双向的是 v-model:value=" " 简写是 v-model=" "
v-model绑定的值不能是props传来的值,因为props传来的值是不可修改的
特别是对象的值,修改了props,别人还不知道,这不完蛋了吗
data的两种写法
对象式
<script type="text/javascript">
Vue.config.productionTip = false; //阻止vue启动时生成生产提示
new Vue({
el: "#app",
data: {
msg: 'wo de fuck'
}
})
</script>
函数式
<script type="text/javascript">
Vue.config.productionTip = false; //阻止vue启动时生成生产提示
new Vue({
el: "#app",
data() {
return {
msg: 'hi'
}
}
})
</script>
像一个函数返回一个对象
一般使用函数式,组件开发的时候必须写函数式,所以以后写函数式就ok
MVVM
Vue没有完全遵循MVVM,受启发
M 是 Model 模型,也就是我们的数据
V 是 View 视图,模版的代码 ->我们自己写的代码
VM 是视图模型,也就是Vue
Vue类似于数据和视图之间的中间商,我们在js代码中写的data会出现在vm里边,我们在{{}}模版语法里边可以获取到这些数据
在vm里边会有监听器,监听dom
Object.defineProperty()
Object.defineProperty(obj, prop, descriptor)
obj
要定义属性的对象。
prop
一个字符串或 Symbol,指定了要定义或修改的属性键。
descriptor
要定义或修改的属性的描述符。
描述符
1.configurable:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性,默认值为false。
2.enumerable:能不能枚举,这里就是能不能for遍历,默认值为false
3.writable:表示能否修改属性的值。默认值为false。
4.value:包含这个属性的数据值。默认值为undefined。
举例
let p = {}
object.defineProperty(p,'age',{
...
})
configurable 不能delete p.age
writable 不能 p.age = 12,这样是无效的
value是设置值
get,set
读取函数时调用get
写入属性时调用set
例如,假如我们想让对象p的age值变成响应式的,就可以使用get
<script>
let num = 1;
let p = {
name: 'jjking'
}
Object.defineProperty(p,"age",{
enumerable: true,
get() {
console.log('获取值,此处是相当于是响应式的');
return num;
}
})
console.log(p);
</script>
需要注意的是,get函数不能和属性value共存,会报错
这里如果我们之后修改num,重新获取age的值的话,就会更新
如何定义多个属性
var student = {};
Object.defineProperties(student,{
name:{
writable:false,
value:"lisi"
},
age : {
writable:true,
value : 16,
},
sex:{
get(){
return '男';
},
set(v){
p1.sex = v
}
}
})
数据代理
最简单的数据代理
通过一个对象代理对另一个对象属性的操作(读/写)
<script>
let obj1 = {x:100}
let obj2 = {y:200}
Object.defineProperty(obj2,'x',{
get() {
return obj1.x;
},
set(value) {
obj1.x = value;
}
})
</script>
此时我们就可以使用obj2来操作obj1了,并且obj1自己的改变,也会改变obj2的x值
- vue先是把data里边的数据放到vm里边,但是名字叫_data
- 然后通过Object.defineProperty()代理所有属性的操作,也就是这里的vm.name,和vm.address,这两个是代理对象
事件处理
基本使用
@xxx: 函数名
阻止默认时间prevent
<div id="app">
<a href="www.baidu.com" @click="showInfo">点我</a>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data: {
},
methods: {
showInfo(e) {
e.preventDefault();
alert("同学你好!");
}
}
})
</script>
a标签的默认事件是跳转
我们可以直接写e.preventDefault();
也可以用vue的@click.prevent
@click.prevent="showInfo"
事件冒泡
原生的写法
<div id="app">
<div @click="showDiv">
<button @click="showInfo">点我</button>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data: {
},
methods: {
showDiv() {
alert("DIV你好!");
},
showInfo(e) {
e.stopPropagation();
alert("同学你好!");
}
}
})
</script>
此时点button的话,他包裹的div也会触发时间showDiv,这就是冒泡
使用vue
@click.stop="showInfo"
事件只触发一次once
@click.once="showInfo"
事件捕获
capture
<div @click.capture="showDiv">
<button @click.stop="showInfo">点我</button>
</div>
正常来说,是先捕获,再冒泡
也就是 先div,再button
如果使用.capture的话,就会先执行div,再执行button
<div id="app">
<div @click.capture="showDiv">
<button @click="showInfo">点我</button>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data: {
},
methods: {
showDiv() {
alert("DIV你好!");
},
showInfo() {
alert("同学你好!");
}
}
})
</script>
事件当前操作元素才触发self
如上也是,只有当我们点击的是div的时候,才触发事件,点button冒泡过去的话,是不会触发事件的
<div @click.self="showDiv">
<button @click="showInfo">点我</button>
</div>
这个方法也可以防止冒泡
passive:事件的默认行为立即执行,无需等待事件回调执行完毕:
有一些@事件,他会先去处理方法,如果方法里边耗时很久的话,不执行默认行为就会有卡顿,所以passvie可以解决这个问题
如果需要多个事件处理
@click.prevent.stop
顺序无所谓
键盘事件
keydown,按下去就触发事件
keyup,按下去,抬上来之后才出发事件
event:当前事件
event.target:当前事件操作的元素
event.target.value 这是这个例子中input的value
<div id="app">
<input type="text" @keyup.enter="showInfo">
</div>
...方法
showInfo(e) {
console.log(e.target.value);
}
keyup.enter 的意思是,检测到回车才触发事件
常用的别名
Vue中常用的按键别名:
回车=>enter
删除=>delete(捕获“删除”和“退格”键)
退出=>esc
空格=>space
换行=>tab
上=>up
下=>down
左=>1eft
右=>right
计算属性
要用的属性不存在,需要自己计算得来
原理是: Object.defineProperty 提供的getter 和 setter
get方法调用的时机(1) 初次读取的时候 (2) 依赖的 数据发生变化的时候
和methods相比,优势是有缓存机制,效率高
计算属性在vm身上,直接调即可
<script type="text/javascript">
new Vue(
{
el: '#root',
data: {
firstName: '',
lastName: ''
},
computed: {
fullname: {
get() {
return this.firstName + '-' + this.lastName
}
}
}
}
)
</script>
简写只有get方法的时候
methods: {
fullname(){
return this.firstName+this.lastName
}
}
set方法
set方法里边我们必须使得计算属性的依赖的数据发生改变,整体才会改变
<div id="app">
<input type="text" v-model="firstName"> <br>
<input type="text" v-model="secondName">
<div>{{fullName}}</div>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data: {
firstName: "",
secondName: ""
},
computed: {
fullName: {
get() {
return this.firstName + "-" + this.secondName
},
set(value) {
const arr = value.split("-");
this.firstName = arr[0];
this.secondName = arr[1];
}
}
}
})
</script>
如果没改变get方法中的firstName 和 secondName,计算属性走的还是缓存
监视属性
写法
<div id="app">
<div>今天天气{{info}}</div>
<button @click="change">change</button>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data: {
isHot: false
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽';
}
},
methods: {
change() {
this.isHot = !this.isHot;
}
},
watch:{
isHot: {
handler(newValue,oldValue) {
console.log(newValue,oldValue);
}
}
}
})
</script>
我们在watch里边直接写,isHot发生变化,就会执行handler里边的方法
另外一种写法
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data: {
isHot: false
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽';
}
},
methods: {
change() {
this.isHot = !this.isHot;
}
}
})
vm.$watch('isHot',{
handler(newValue,oldValue) {
console.log(newValue,oldValue);
}
})
</script>
在外边写也可以,只不过要注意的是,这里的isHot必须加引号
深度监视
如果我们要监视一个对象里边的数据的改变,需要加上配置项,deep:true
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data: {
numbers: {
a: 1,
b: 2
}
},
watch: {
numbers: {
immediate:true,
deep: true,
handler(newValue, oldValue) {
console.log('numbers发生变化',newValue,oldValue);
}
}
}
})
</script>
绑定样式
适用于class和style
最简单的写法
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood" @click="changeMood"></div>
数组的绑定
绑定class样式–数组写法,适用于:要绑定的样式个数不确定、名字也不确定
<div class="basic" :class="['atguigu1','atguigu2']"></div>
对象的写法
适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用
<div class="basic" :class="{atguigu1:false, atguigu2:false}"></div>
简写
只有handler的时候,可以直接写
//原来的
watch: {
numbers: {
handler(newValue, oldValue) {
console.log('numbers发生变化',newValue,oldValue);
}
}
}
//修改后的:
watch: {
numbers(newValue, oldValue) {
console.log('numbers发生变化',newValue,oldValue);
}
}
computed 和 watch的区别
computed能实现的,watch都能实现,但是watch可以做异步操作
两个原则:
- 被vue管理的函数写成普通函数,this指向的是vm或者组件实例对象
- 所有不被vue所管理的函数,最好写成箭头函数,此时this指向的是vm或者组件实例对象
这里因为如果不被vue管理的函数写成普通函数,this指向的会是window
class 和 style样式
<div id="root">
<!-- 字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood">{{number.a}}</div>
<!-- 数组写法,适用于:要绑定的样式个数不确定,名字也不能确定 -->
<div class="basic" :class="moodArr">{{number.b}}</div>
<!-- 对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="moodObj">{{number.b}}</div>
</div>
...
data:{
number:{
a:1,
b:2,
},
mood:'happy',
moodArr:['happy'],
moodObj:{
happy:true
}
},
style的绑定和class的绑定一致
条件渲染
v-if,整个节点不在了
v-show 节点还在,只是隐藏
<h1 v-if="n===1">1</h1>
<h1 v-if="n===1">2</h1>
<h1 v-if="n===1">3</h1>
如果条件一样,我们可以再外边写一个div
<div v-if="n===1">
<h1>1</h1>
<h1>2</h1>
<h1>3</h1>
</div>
但是有可能会破坏结构,所以用template
<template v-if="n===1">
<h1>1</h1>
<h1>2</h1>
<h1>3</h1>
</template>
template只能和v-if使用
列表渲染
v-for
<!-- 遍历数组 -->
在遍历数组时,数组元素后面的一个参数,是遍历时的key值,因为我们这里没有自定义key,所以默认是012
<ul>
<li v-for="(person,index) in persons">
{{index}}--{{person.name}}--{{person.age}}
</li>
</ul>
<!-- 遍历对象 -->
遍历对象时,括号中第一个参数是对象中键值对的值,第二个参数是键值对的键,第三个参数是这个这条遍历的数据的key
<ul>
<li v-for="(value,key,index) in car">{{index}}--{{key}}--{{value}}</li>
</ul>
遍历字符串时,括号中第一个参数是字符串这个位置的值,第二个参数是这个这条遍历的数据的key
<!-- 遍历字符串 -->
<ul>
<li v-for="(value,key) in str">{{key}}--{{value}}</li>
</ul>
...
data: {
persons: [
{ id: "001", name: "ice", age: "13" },
{ id: "002", name: "peach", age: "12" },
],
car: {
speed: "20km/h",
year: "2014",
},
str: "i am a word",
},
key的原理和使用
key的内部原理
- 虚拟dom中key的作用:
key是虚拟dom对象的标识,vue会根据新数据生成新的虚拟dom,随后进行diff算法 - 对比规则:
(1) 旧的虚拟dom和新的虚拟dom,有相同key
a. 如果虚拟dom中内容没变,则直接复用之前的真实dom
b.如果内容变化,则生成新的dom
(2)没有相同key,创建新的dom
看这个图就知道为什么index作为key,如果有逆序操作的时候,会出现界面问题了
两边新旧都有虚拟dom,此时进行diff比较,我们新的是逆序添加了老刘这条li
key相同都是0,input也相同,只有张三-18
和老刘-30
不同,此时input相同
那么不同的内容,老刘-30
会创建新的dom
对于相同的内容,input输入框,就会复用之前的dom,也就是原本应该是张三的输入框
以此类推,这里整个往上移,就会出现页面问题
解决办法也很简单,就是我们绑定key的时候,用可以唯一标识的key,比如id,身份证,手机号等等
如果不存在对数据的逆序,破坏顺序操作,用index也是没有问题的
列表过滤
实现列表过滤的效果
watch监视
<div id="app">
<label>姓名:</label><input v-model="name">
<ul>
<li v-for="(person) in persnsOfSearch">
{{person.name}}--{{person.age}}
</li>
</ul>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data: {
name: '',
persnsOfSearch: [],
persons: [
{ id: '001', name: 'ice', age: '17' },
{ id: '002', name: 'ipeach', age: '18' },
{ id: '003', name: 'icepeach', age: '19' }]
},
watch: {
name: {
immediate: true,
handler(newValue, oldValue) {
console.log('输入发生变化');
this.persnsOfSearch = this.persons.filter((p) => {
return p.name.indexOf(newValue) != -1;
})
}
}
}
})
</script>
整体的写法就是
输入框的里边的值,我们监视他
如果发生变化,也就是传来新的值newValue
,我们就去过滤数组,
过滤的条件是,在persons数组中,查找每个对象的name
是否含有newValue
利用的是函数 str.indexOf(val) ,如果有就会返回位置,如果没有就会返回-1
所以只有 != -1,就说明含有该值,filter通过
最后我们把过滤好的数据传给persnsOfSearch
,不用原来的数组就是为了不破坏数据
计算属性实现
<div id="app">
<label>姓名:</label><input v-model="name">
<ul>
<li v-for="(person) in persnsOfSearch">
{{person.name}}--{{person.age}}
</li>
</ul>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data: {
name: '',
persons: [
{ id: '001', name: 'ice', age: '17' },
{ id: '002', name: 'ipeach', age: '18' },
{ id: '003', name: 'icepeach', age: '19' }]
},
computed: {
persnsOfSearch() {
return this.persons.filter((p) => {
return p.name.indexOf(this.name) != -1;
})
}
}
})
</script>
我们如果使用计算属性的话就可以快速的解决这个问题
第一,我们得监视name的改变,这里的监视name的改变,巧妙的把name写到计算属性里边了,计算属性里边,依赖的数据发生改变,那么计算属性也会发生改变
也就是我们这里的persnsOfSearch
,里边依赖着this.name
第二,我们做过滤
还有一个好处就是,我们不用像watch那样还得写immediate:true,计算属性一开始就会去进行计算
排序
在上面的基础上,加上一个age过滤的功能
<div id="app">
<label>姓名:</label><input v-model="name">
<ul>
<li v-for="(person) in persnsOfSearch">
{{person.name}}--{{person.age}}
</li>
</ul>
<button @click="sortType = 0">原顺序</button>
<button @click="sortType = 1">降序</button>
<button @click="sortType = 2">升序</button>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data: {
name: '',
persons: [
{ id: '001', name: 'ice', age: '17' },
{ id: '002', name: 'ipeach', age: '18' },
{ id: '003', name: 'icepeach', age: '19' }],
sortType: 0
},
computed: {
persnsOfSearch() {
const arr = this.persons.filter((p) => {
return p.name.indexOf(this.name) != -1;
})
//是否是原来的顺序
if(this.sortType != 0) {
arr.sort((p1,p2) => {
return this.sortType == '1' ? p2.age - p1.age : p1.age - p2.age;
})
}
return arr;
}
}
})
</script>
修改对象和数组数据
新增属性
Vue中,如果是对象数据,新增属性,和删除属性,Vue是不会监测到的,所以如果要加的话,得借助this.$set(target,propertyName/index,value)
或者用Vue.set(target,propertyName/index,value)
向响应式对象中添加响应式的属性
注意对象不能是Vue实例,或者Vue实例的跟数据对象
删除属性
删除属性和新增用法差不多
this.$delete(target,propertyName/index,value)
或者用Vue.delete(target,propertyName/index,value)
修改数组数据
要不使用数组的方法,数组的方法都集成了响应式,或者还是用
this.$set(数组,索引,修改的数据)
获取表单数据
<input type="text"/>
v-model收集的是value值,用户输入的就是value值
<input type="radio"/>
v-model收集的是value值,我们必须给标签设置value值
<input type="checkbox"/>
- 如果没有设置value值,收集的是checked值(布尔)
- 如果设置了value值
(1)v-model的初始值不是数组,收集的就是checked
(2)v-model的初始值是数组,收集的就是value组成的数组
备注: v-model的三个修饰符
lazy: 失去焦点的再收集数据
number: 输入字符串转为有效数字
trim: 去掉首尾空格
生命周期
beforeCreate 是数据代理监测和数据代理创建之前,不是vm
beforeMount 此时只有虚拟dom,所以此时没有把虚拟dom转换为真实dom
beforeUpdate 此时数据是最新的,但是页面是旧的
beforedestroy 此时数据,方法都在,但是此时如果修改数据,方法是不会触发数据更新流程
一般这个时候,清除定时器,解绑自定义事件
,取消订阅等等
注意
销毁之后,只是vm没了,但是真实dom还是存在的,也就是vm的工作成果还在,不过管理的他的vue死了
这里的解绑自定义事件是需要关注的,我们在destroy之后是不能触发自定义事件的
$nextTick()
等待下一次 DOM 更新刷新的工具方法
function nextTick(callback?: () => void): Promise<void>
里边的是回调函数,也就是在下次刷新之后的会执行的函数
插槽
默认插槽
<!-- 父组件中: -->
<Category>
<div>html结构1</div>
</Category>
// 子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot>插槽默认内容...</slot>
</div>
</template>
具名插槽
<!-- 父组件中: -->
<Category>
<template slot="center">
<div>html结构1</div>
</template>
<template v-slot:footer>
<div>html结构2</div>
</template>
</Category>
// 子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot name="center">插槽默认内容...</slot>
<slot name="footer">插槽默认内容...</slot>
</div>
</template>
v-slot:footer 是新的语法
作用域插槽
<!-- 父组件中: -->
<Category>
<template scope="scopeData">
<!-- 生成的是ul列表 -->
<ul>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ul>
</template>
</Category>
<Category>
<template slot-scope="scopeData">
<!-- 生成的是h4标题 -->
<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
</template>
</Category>
// 子组件中:
<template>
<div>
<slot :games="games"></slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title'],
//数据在子组件自身
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽']
}
},
}
</script>
slot-scope 是新语法
应用场景,数据在子组件,数据的生成的结构由组件的使用者决定