Vue核心
特点
- 采用组件化模式,提高代码复用率、且让代码更好维护
- 声明式编码,让编码人员无需直接操作DOM,提高开发效率
- 使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点
- 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
- root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
- root容器里的代码被称为Vue模板
插值语法例子:
<body>
<div id="root">
<h1>Hello,{{name}}</h1>
</div>
<script type="text/javascript">
// Vue.config.productionTip = false;
//创建Vue实例
new Vue({
el: '#root', //el用于指定当前Vue实例为那个容器服务,值通常为css选择器字符串
data: { //data中用于存储数据,数据供el所指定的容器去使用,值暂时先写成一个对象
name: '陈润z'
}
})
</script>
</body>
每个容器一一对应
指令语法例子:v-开头
-
单向绑定(v-bind:)数据只能从data流向页面
-
双向绑定(v-model)数据不仅能从data流向页面,还可以从页面流向data
- 只能用在表单类元素(输入类元素)上
- v-model:value可以简写为v-medel
data与el的2种写法
- el有两种写法
- new vue时候配置el属性
- 先创建vue实例,随后再通过vm.$mount(‘#root’)指定el的值
- data有两种写法
- 对象式
- 函数式
- 目前两种都可以用,学到组件时,data必须使用函数式,否则会报错
- 一定不要写箭头函数,this只需window而不是vue
事件修饰符
鼠标事件:
- prevent:阻止默认事件
- stop:阻止事件冒泡
- once:事件只触发一次
- capture:使用事件的捕获模式
- self:只有event.target是当前操作的元素是才出发事件
- passive:事件的默认行为立即执行,无需等待事件回调执行完毕
键盘事件:
- 回车:enter
- 删除:delete
- 退出:esc
- 空格:space
- 换行:tab
- 上:up
- 下:down
- 左:left
- 右:right
Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case
系统修饰键:ctrl、alt、shift、meta
配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
配合keydown使用:正常触发事件
vue中允许链式编程
计算属性
- 定义:要用的属性不存在,要通过已有属性计算得来
- 原理:底层借助了Objcet.defineproperty方法提供的getter和setter
- get函数什么时候执行
- 初次读取时会执行一次
- 当依赖的数据发生改变时会被再次调用
- 优势:与methods实现相比,内部有缓存机制(复用)效率更高,调试方便
- 备注:
- 计算属性最终会出现在vm上,直接读取使用即可
- 如果计算属性要被修改,那必须写set函数去响应修改,切set中要引起计算时依赖的数据发生变化
监视属性watch
- 当被监视的属性变化时,回调函数自动调用,进行相关操作
- 监视的属性必须存在,才能进行监视!!!
- 监视的两种写法
- new Vue时传入watch配置
- 通过vm.$watch监视
深度监视
- Vue中的watch默认不监测对象内部值的改变(一层)
- 配置deep:true可以监测对象内部值的改变(多层)
- 备注
- vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
- 使用watch时根据的具体结构,决定是否采用深度监视
computed和watch之间的区别:
- computed能完成的功能,watch都可以完成
- watch能完成的功能,computed不一定能完成,例如watch可以进行异步操作
两个细节:
- 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象
- 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等),最好写成箭头函数,这样this的指向才是vm或组件实例对象
Vue本身有自带的监测信息是否改变,但是有一个bug,用如下方法Vue开发者工具无法监测到无法监测到
//可以被监测到
this.persons[0].name = 'xxx'
this.persons[0].age = 50
this.persons[0].sex = '男'
//无法被监测到
this.persons[0]={id:'001',name:'xxx',age:'50',sex:'男'}
Vue监测数据的原理
创建Vue数据时会把变量进行数据代理,Vue给它set get方法,如果不是一开始就创建的,则无法成功代理,也就无法完成响应式操作,一个对象中想用的数据一定要提前配置好.Vue提供了后期添加响应式数据的方法:Vue.set(‘想要给谁添加数据’,‘键’,‘值’) 或者vm.$set(),注意:这个方法只能给data数据里某一个对象里面追加属性,不能直接给data追加属性
Vue无法监测到数组里的的数据,数组对象Vue无法创建set get方法——无法响应式操作
但是在数组中可以调用数组中对应的7个方法,修改完数组后Vue重新编译并在页面展示
- Vue会监视data中所有层次的数据
- 如何监测对象中的数据
- 通过setter实现监视,且要在new Vue时就传入要监测的数据
- 对象中追加的属性,Vue默认不做响应式处理
- 如需给后添加的属性做响应式,请使用如下API
- Vue.set(‘想要给谁添加数据’,‘键’,‘值’)
- vm.$set(‘想要给谁添加数据’,‘键’,‘值’)
- 通过setter实现监视,且要在new Vue时就传入要监测的数据
- 如何监测数组中的数据?
- 通过包裹数组更新元素的方法实现,本质就是做了两件事:
- 通过包裹数组更新元素的方法实现,本质就是做了两件事
- 调用原生对应的方法对数组进行更新(7个)
- 重新解析模板进而更新页面 同上API
- 通过包裹数组更新元素的方法实现,本质就是做了两件事
- 通过包裹数组更新元素的方法实现,本质就是做了两件事:
- 在Vue修改数组中的某个元素一定要用如下方法
- push() pop() shift() unshift() splice() sort() reverse()
- Vue.set() 或 vm.$set()
- 特别注意:Vue.set() 和vm.$set() 不能给vm或vm的 根数据对象添加属性!!! data和data在data一级的对象
绑定样式:
v-bind:class 缩写 :class
- class样式
- 写法:class=“xxx” xxx可以是字符串、对象、数组
- 字符串写法适用于:类名不确定,要动态获取
- 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定
- 数组写法适用于:要绑定多个样式,个数确定,,名字也确定,但不确定用不用
- style样式
- :style="{fontSize:xxx}"其中xxx是动态值
- :style="{a,b}"其中a、b是样式对象 样式对象必须是Css样式里存在的,而且要符合驼峰命名规则
<body>
<div id="root">
<div class="basic" :class="mood" @click="changeMood">{{name}}</div>
<div class="basic" :style="styleObj">{{name}}</div>
</div>
<script>
new Vue({
el: '#root',
data: {
name: '陈润喆',
mood: 'normal',
styleObj: {
fontSize: '40px'
}
},
methods: {
changeMood() {
const arr = ['happy', 'sad', 'normal']
const index = Math.floor(Math.random() * 3) //向下取整,取值0 1 2
this.mood = arr[index]
}
},
})
</script>
</body>
条件渲染
v-show v-if
v-if与template的关系
使用template模板时只可以使用v-if,否则会失效
- v-if
- 写法
- v-if=“表达式”
- v-else-if=“表达式”
- v-else=“表达式”
- 适用于:切换频率较低的场景
- 特点:不展示DOM元素直接被移除
- 注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被打断
- 写法
- v-show
- 写法:v-show=“表达式”
- 适用于:切换频率较高的场景
- 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
- 备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到
列表渲染
v-for指令
- 用于展示列表数据
- 语法:v-for=“(item,index) in xxx” :key=“yyy”
- 可遍历:数组 对象 字符串 指定次数
<body>
<div id="root">
<h2>人员列表</h2>
<ul>
<li v-for="(p,index) in persons" :key="index">
{{p.name}}-{{p.age}}-------{{index}}
</li>
</ul>
</div>
<script>
new Vue({
el: '#root',
data: {
persons: [
{ id: '001', name: '张三', age: '18' },
{ id: '002', name: '李四', age: '19' },
{ id: '003', name: '王五', age: '20' }
]
}
})
</script>
</body>
用id和用key中index做唯一标识的区别,如果数据是从上面插入时,用数据中的id做唯一标识,向下插入用index。
react、vue中的key有什么作用?(key的内部原理)
- 虚拟DOM中key的作用:
- key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟标识】随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
- 对比规则:
- 旧虚拟DOM中找到了新虚拟DOM相同的key:
- 若虚拟DOM中内容没变,直接使用之前的真实DOM
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 旧虚拟DOM中未找到与新虚拟DOM相同的key
- 创建新的真实DOM,随后渲染到页面
- 旧虚拟DOM中找到了新虚拟DOM相同的key:
- 用index作为key可能会引发的问题
- 若对数据进行更新:逆序添加、逆序删除等破坏
- 会产生没必要的真实DOM更新==>界面效果没问题,但效率低
- 如果结构中还包含输入类的DOM:
- 会产生错误的DOM更新==>界面有问题
- 若对数据进行更新:逆序添加、逆序删除等破坏
- 开发中如何选择key?
- 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证等
- 如果不存在对数据逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key
- 简而言之: 用每条数据的唯一标识就完了
收集表单数据
<body>
<div id="root">
<form action="" @submit.prevent="sub">
<label for="demo">账号</label>
<input type="text" id="demo" v-model="userInfo.account">
<br><br>
<label for="demo1">密码</label>
<input type="password" id="demo1" v-model.number="userInfo.password">
<br><br>
年龄: <input type="number" v-model.number="userInfo.age">
<br><br>
性别:
男<input type="radio" value="male" name="sex" v-model="userInfo.sex">
女<input type="radio" value="female" name="sex" v-model="userInfo.sex">
<br><br>
爱好:
学习<input type="checkbox" value="study" v-model="userInfo.hobby">
打游戏<input type="checkbox" value="game" v-model="userInfo.hobby">
打篮球<input type="checkbox" value="bas" v-model="userInfo.hobby">
<br><br>
所属校区
<select v-model="userInfo.city">
<option value="">请选择校区</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="henan">河南</option>
</select>
<br><br>
其他信息:
<textarea v-model="userInfo.other"></textarea>
<br><br>
<input type="checkbox" v-model="userInfo.agree"> 阅读并接受<a href="#">用户协议</a>
<br><br>
<button>提交</button>
</form>
</div>
<script>
new Vue({
el: '#root',
data: {
userInfo: {
account: '',
password: '',
age: '',
sex: '',
hobby: [],
city: 'beijing',
other: '',
agree: ''
}
},
methods: {
sub() {
console.log(JSON.stringify(this.userInfo));
alert('数据已提交')
}
},
})
</script>
</body>
过滤器(filters)
局部过滤器
写在Vue实例对象内部,只能被当前实例使用
写法{{值 | 过滤器方法}}
全局过滤器
定义在newVue实例之前 filter
内置指令
所有指令里的this都指向window
之前学过的:
v-bind
v-model
v-for
v-on
v-if
v-else
v-show
v-text 向一个数据中添加文本 并且覆盖原来的内容
v-html 支持html的编译
作用:向指定节点中渲染包含html结构的内容
与插值语法的区别
1.v-html会替换掉节点中所有的内容,{{xx}}不会
2.v-html可以识别html结构
严重注意:v-html有安全问题
1.在网站上动态渲染任意html是非常危险的,容易导致xss攻击
2.一定要在可信的内容上使用v-html,永远不要再用户提交的内容上
v-cloak(没有值):
1.本质是一个特殊的属性,Vue实例创建完毕后接管容器后,会删除v-cloak属性
2.使用css display:none配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题
v-once 所在节点初次动态选然后,就视为静态内容了
以后数据的改变也不会引起v-once所在结构的更新,可以用于优化性能
v-pre 跳过其所在节点的编译过程,可利用它跳过:没有使用指令语法、没有使用插值语法的特点,会加快编译
自定义指令
<body>
<!--
需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍
-->
<div id="root">
<h2>当前的n值是: <span v-text="n"></span></h2>
<h2>放大十倍后的n值是: <span v-big="n"></span></h2>
<button @click="n++">点我n+1</button>
</div>
<script>
new Vue({
el: '#root',
data: {
n: 1
},
directives: {
//两个参数,一个是真实的DOM节点,一个是被绑定的数据,我们需要它的value值即可
big(element, binding) {
console.log(binding);
element.innerText = binding.value * 10
}
}
})
</script>
</body>
<body>
<!--
需求2:定义一个v-fbind指令,和v-bind类似,但可以他让其所绑定的input元素默认获取焦点信息
-->
<div id="root">
<h2>当前的n值是: <span v-text="n"></span></h2>
<h2>放大十倍后的n值是: <span v-big="n"></span></h2>
<button @click="n++">点我n+1</button>
<hr>
<input type="text" v-fbind:value="n">
</div>
<script>
new Vue({
el: '#root',
data: {
n: 1
},
directives: {
//两个参数,一个是真实的DOM节点,一个是被绑定的数据,我们需要它的value值即可
fbind: {
//指令与元素成功绑定时调用(一上来)
bind(element, binding) {
//当前节点中的值等于绑定来数据处理过的值
element.value = binding.value
},
//指令所在元素被插入页面时调用
inserted(element, binding) {
element.focus()
},
//指令所在模板被重新解析时
update(element, binding) {
element.value = binding.value
},
}
}
})
</script>
</body>
总结:
-
定义语法:
-
局部指令
-
new Vue({ directives:{指令名:配置对象} }) new Vue({ directives:{指令名:回调函数} })
-
-
全局指令
-
Vue.directive(指令名,配置对象) Vue.directive(指令名,回调函数)
-
-
-
配置对象中常用的3个回调
- bind:指令与元素成功绑定时调用
- inserted 指令所在元素被插入页面时调用
- update 指令所在模板结构被重新解析时调用
-
备注
- 指令定义时不加v-,但使用时要加v-
- 指令名如果是多个单词,要使用camel-case命名方式,不要用camelCase命名
生命周期:
- 又名:生命周期回调函数/生命周期函数/生命周期钩子
- 是什么:Vue在关键时刻帮我们调用一些特殊名称的函数
- 生命周期函数的名字不可以更改,但函数的具体内容是程序员根据需求编写的
- 生命周期函数中的this指向是vm或组件实例对象
常见的生命周期
一、创建
beforeCreate
此时无法通过vm访问到data中的数据、methods中的方法,此时浏览器已经初始化完成,但是生命周期、事件、数据代理还没开始
created
进一步初始化,已经开始数据监测、数据代理,此时已经可以通过vm访问到data中的数据、methods中配置的方法
二、挂载
beforeMount
挂载前,Vue开始解析模板,此时生成的是虚拟对象,页面上插值语法的地方还无法显示,这个时候即便对DOM进行操作,最好仍不奏效,最后生成的真实DOM会将其覆盖
mounted
将内存中的虚拟DOM转为真实DOM插入页面,此时vue完成编译,并将最后的结果呈现在页面上,这个时候虽然可以对DOM元素进行操作,但是不推荐(多此一举),在此过程结束后一般进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件、等初始化操作
应用场景:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
三、更新
beforeUpdate
此时数据是新的,但页面是旧的,新数据还没有渲染到页面上
updated
新的数据已经渲染到页面上了
四、销毁
beforeDestroy&destroyed
在这两个阶段中,vm所有的data、methods、指令等都处于可用状态,并马上要执行销毁过程:一般在此阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作。总之,一般在这个阶段,就不要在进行数据的操作,都准备销毁了,更新,修改数据也没有用 Ps:需要下面的方法调用
应用场景:清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】
vm.$destroy()
完全销毁一个实例.清理它与其他实例的连接,解绑它的全部指令及事件监听器(自定义事件)
关于销毁Vue实例:
- 销毁后借助Vue开发者工具看不到任何信息
- 销毁后自定义事件会失效,但原生DOM事件依然有效
- 一般不会再beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了
五、激活状态(路由器中的生命周期钩子,下同)
必须配合keep-alive
router标签才能被触发
activated:当前切换的组件,打开后为激活状态,执行此生命钩子里的代码
六、失活状态
deactivated:离开当前组件,则变为失活状态,执行此钩子里的代码
Vue组件化编程
为什么要组件化:传统的html开发模式,每一个项目引用多个css文件、js文件,容易导致各个文件的依赖关系混乱且不宜维护,代码复用率也不高
组件:实现应用中局部功能代码和资源的集合
模块化:当应用中的js都以模块来编写的,那这个应用就是一个模块化应用
组件化:当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
一、组件的两种书写模式
1.非单文件组件
一个文件中包含有n个组件
2.单文件组件
一个文件中只包含一个组件
二、使用组件的三大步骤
-
定义组件(创建组件)
-
注册组件
-
使用组件(写组件标签)
-
如何定义一个组件?
- 使用Vue。extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但 也有区别:具体如下
- el不要写,写什么? 最终所有的组件都要经过vm的管理,由vm中的el决定服务哪个容器
- data必须写成函数,为什么? —避免组件被复用时,数据存在引用关系
- 备注:使用template可以配置组件结构
- 使用Vue。extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但 也有区别:具体如下
-
如何注册组件?
- 局部注册:靠new Vue的时候传入components选项
- 全局注册:靠Vue.component(‘组件名’,组件)
-
编写组件标签
<body>
<div id="root">
<!-- 3.编写组件标签 -->
<school></school>
<hr>
<student></student>
</div>
<script>
//1.创建school组件
const school = Vue.extend({
template: `
<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click='look'>点我查看学校名称</button>
</div>
`,
data() {
return {
schoolName: '河南科技学院',
address: '河南',
}
},
methods: {
look() {
alert(this.schoolName)
}
},
})
//创建学生组件
const student = Vue.extend({
template: `
<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data() {
return {
studentName: '陈润喆',
age: 20
}
},
})
//创建vm
new Vue({
el: '#root',
//2.注册组件
components: {
school: school,
student: student
}
})
</script>
</body>
三、组件的嵌套
<body>
<div id="root">
</div>
<script>
//定义student组件
const student = Vue.extend({
template: `
<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data() {
return {
studentName: '陈润喆',
age: 20
}
},
})
//定义school组件
const school = Vue.extend({
//将学生的标签名写在这个模板中
template: `
<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div>
`,
data() {
return {
schoolName: '河南科技学院',
address: '河南',
}
},
//在学校组件中注册学生组件,将学生嵌套在老师中
components: {
student: student
}
})
const app = Vue.extend({
template: `<school></school>`,
components: {
school: school
}
})
//创建vm
new Vue({
el: '#root',
template: `<app></app>`,
//2.注册组件
components: {
app: app
}
})
</script>
</body>
四、VueComponent:
- school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
- 我们只需要写或者,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)
- 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!
- 关于this指向:
- 组件配置中:
- data函数 methods中的函数 watch中的函数 computed中的函数 他们的this均是VueComponent实例对象
- new Vue()配置中
- data函数 methods中的函数 watch中的函数 computed中的函数 他们的this均是Vue实例对象
- 组件配置中:
- VueComponent 的实例对象,以后简称vc也可称之为组件实例对象.
Vue的实例对象,以后简称vm vm管理着一堆vc
原型对象
prototype 显示原型属性
__proto__隐式原型属性 二者共同属于原型对象
程序员可以通过这两个属性操作原型对象
一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性、方法
五、单文件组件
js代码块暴露的三种方式
1.分别暴露 注意:引入school时要用 {}包住 import {school} from ‘文件’
export const school = Vue.extend({
data() {
return {
schoolName: "河南科技学院",
address: "河南",
};
},
});
2.统一暴露
const school = Vue.extend({
data() {
return {
schoolName: "河南科技学院",
address: "河南",
};
}
});
export {school}
3.默认暴露
const school = Vue.extend({
data() {
return {
schoolName: "河南科技学院",
address: "河南",
};
}
});
export default school
4.项目中常用写法
<script>
export default {
name: "School",
data() {
return {
schoolName: "河南科技学院",
address: "河南",
};
},
methods: {
look() {
alert(this.schoolName);
},
},
};
</script>
六、模块化开发流程
-
写单文件组件 .vue结尾的文件–>需要Vetur插件 以School.vue文件为例
-
<template> <div class="demo"> <h2>学校名称:{{ schoolName }}</h2> <h2>学校地址:{{ address }}</h2> <button @click="look">点我查看学校名称</button> </div> </template> <script> export default { name: "School", data() { return { schoolName: "河南科技学院", address: "河南", }; }, methods: { look() { alert(this.schoolName); }, }, }; </script> <style> .demo { background-color: orange; } </style>
-
-
单文件组件写完之后,写一个最大的App组件,并将所有的组件引入
-
<template> <div> <School /> </div> </template> <script> //引入组件 import School from "./School.vue"; export default { components: { School }, name: "App", comments: { School, }, }; </script> <style> </style>
-
-
写main.js文件,将App.vue引入,并准备执行
-
import App from '.App.vue' new Vue({ el:'#root', components:{ App } })
-
-
写html页面,引入vue.js文件和App.vue进行页面显示-----需要脚手架才可以成功运行
-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>测试一下</title> </head> <body> <div id="root"> <App></App> </div> <script src="../js/vue.js"></script> <script src="./main.js"></script> </body> </html>
-
七、组件自定义事件
-
一种组件间通信的方式,适用于: 子组件===>父组件
-
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(在B实例对象中添加了一个自定义事件)在B组件中可以调用(事件的回调在A中,并且参数是从B传过来的)
-
绑定自定义事件:
-
第一种方式,在父组件中@自定义组件名=“”
-
第二种
-
<Student ref="student" /> mounted() { //将self事件绑定在Student实例对象上 this.getStudentName是从this.$emit("自定义事件名",发送给父类的数据) this.$refs.student.$on("self", this.getStudentName); },
-
-
若想让自定义事件只能触发一次,可以使用once修饰符或$once方法
-
-
触发自定义事件:this.$emit(“自定义事件名”,发送给父类的数据)
-
解绑自定义事件:this.$off(“自定义事件名”)
-
组件上也可以绑定原生DOM事件,需要使用native修饰符
-
通过this. r e f s . x x x . refs.xxx. refs.xxx.on(“自定义事件”,回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向出问题
八、全局事件总线
- 任意组件间通信
外部x组件中间传递方的要求
-
所有组件都能调用他,从而进行参数的传递/接收vm
-
可以绑定事件 :
$on $off $emit 这个都在vue的原型对象vm上 所以vc可以调用的到
安装全局事件总线
new Vue({
el: '#app',
render: h => h(App),
beforeCreate() {
//安装全局事件总线
Vue.prototype.$bus = this
}
})
使用:
先接收数据,A组件想接收数据,则在A组件中 给$bus绑定自定义事件,事件的回调留在A组件自身
data() {
return {
name: "crz",
age: "20",
};
},
methods: {
sendStudentName() {
// 调用x组件触发hello事件,将student中的数据传给school
this.$bus.$emit("hello", this.age);
},
//用完之后进行解绑,yang'cheng
beforeDestroy() {
this.$bus.$off("hello");
},
},
提供数据
mounted() {
this.$bus.$on("hello", (data) => { // data是从上面传过来的数据
console.log("我是school组件,收到了数据", data);
});
},
使用Vue脚手架
Vue官方提供的标准化开发工具(开发平台)
一、安装步骤
- 全局安装 配置npm淘宝镜像命令: npm config set registry https://registry.npm.taobao.org
- 安装之前一定要先安装node.js
- 配置完淘宝镜像命令后进行全局安装: npm install -g @vue/cli
- 切换到要创建的项目目录,然后使用黑窗口命令创建项目
- vue create xxxx
- 启动项目
- npm run serve
Vue脚手架隐藏了所有的webpack配置,若想要查看具体的webpack配置,请执行:vue inspect > output.js
二、render函数
关于不同版本的Vue:
- vue.js与vue.runtime.xxx.js的区别:
- vue.js是完整版的Vue,包含:核心功能+模板解析器
- vue.runtime.xxx.js是运行版的vue,只包含:核心功能,没有模板解析器
- 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体的内容
三、ref属性
通过this.$refs.(ref)获取当前的DOM节点
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button @click="showdom" ref="btn">点我输出上面的元素</button>
<School ref="sch" />
</div>
</template>
<script>
import School from "./components/School.vue";
export default {
name: "App",
data() {
return {
msg: "欢迎到来",
};
},
methods: {
showdom() {
console.log(this.$refs.title); //真实的don元素
console.log(this.$refs.btn);//真实dom元素
console.log(this.$refs.sch);//School组件的实例对象
},
},
components: {
School,
},
};
</script>
四、props属性
- 设置属性——提高模块的复用率,一些属性不在data中配置,而是写标签后在标签中设置外部属性
<template>
<div class="school">
<h2>座右铭:{{ msg }}</h2>
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
<h2>学校年龄:{{ age1 }}</h2>
<button @click="age1++">点击增加学校年龄</button>
</div>
</template>
<script>
export default {
name: "School",
data() {
return {
msg: "有新校区的河科院",
age1: this.age,
};
},
props: {
name: {
type: String, //name类型是字符串
required: true, //name是必要的
},
age: {
type: Number,
default: 50, //默认值,不填默认50
},
address: {
type: String,
default: "新乡",
},
},
};
</script>
<style>
.school {
background-color: orange;
}
</style>
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button @click="showdom" ref="btn">点我输出上面的元素</button>
<School name="河南科技学院" address="新乡市" ref="sch" /> //在这里配置属性
<School name="新乡医学院" address="红旗区" />
</div>
</template>
<script>
import School from "./components/School.vue";
export default {
name: "App",
data() {
return {
msg: "欢迎到来",
};
},
methods: {
showdom() {
console.log(this.$refs.title); //真实的don元素
console.log(this.$refs.btn); //真实dom元素
console.log(this.$refs.sch); //School组件的实例对象
},
},
components: {
School,
},
};
</script>
五、mixin混入
将vue文件中重复的配置项单独写出来然后分别引入
//为了方便其他模块的引入,一定要暴露-->分别暴露
export const mixin = {
methods: {
showName() {
alert(this.name);
},
},
}
<template>
<div class="school">
<h2>座右铭:{{ msg }}</h2>
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
<h2>学校年龄:{{ age1 }}</h2>
<button @click="age1++">点击增加学校年龄</button>
<button @click="showName">点击显示学校名</button>
</div>
</template>
<script>
//分别暴露 引入时要用{}将文件名包住
import {mixin} from "./mixin.js";
export default {
name: "School",
data() {
return {
msg: "有新校区的河科院",
age1: this.age,
};
},
mixins: [mixin],//注意这里要用数组的形式
};
</script>
<style>
.school {
background-color: orange;
}
</style>
六、plugins插件
相当于大号的mixin,可以将许多重复项放进来,给Vue原型添加方法、过滤器、mixin、自定义指令都可以写进来,然后在引入,使用Vue.use(plugins.js)即可用vm、vc对象进行调用
七、scoped属性
- 让当前代码作用域限制在这一个文件夹中
引入多个vue文件模块化会出现css样式类名冲突的情况,为了避免这种情况可以给style标签添加scoped属性避免冲突
Todo-list案例
一、组件间通信
1.父亲给儿子
案例中例如App(父)共享数据给List(子)
将需要共享的数据(todos)写在App中,然后在List标签中添加:todos=“todos” 将数据与todos进行绑定,然后在List中添加属性(props)todos即完成了父类数据传给了儿子,进行遍历和操作等等操作…
//-----------------------------Vue--------------------
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader :addTodo="addTodo" />
<MyList
:todos="todos"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
/>
<MyFooter
:todos="todos"
:checkAllTodo="checkAllTodo"
:clearAllTodo="clearAllTodo"
/>
</div>
</div>
</div>
</template>
<script>
import MyHeader from "./components/MyHeader.vue";
import MyFooter from "./components/MyFooter.vue";
import MyList from "./components/MyList.vue";
export default {
name: "App",
data() {
return {
//父类存数据
todos: [
{ id: "001", title: "吃饭", done: true },
{ id: "002", title: "睡觉", done: false },
{ id: "003", title: "打游戏", done: true },
],
};
},
methods: {
addTodo(todoObj) {
//操作上面的数据中的todos,添加进去后,在List中遍历渲染
this.todos.unshift(todoObj);
console.log(this);
},
},
components: {
MyHeader,
MyFooter,
MyList,
},
};
</script>
-------------------------List----------------------
<template>
<ul class="todo-main">
<!-- todos遍历出来每一个对象todoObj赋值给todo,在MyItem中进行参数接收然后操作,遍历出来值 -->
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
/>
</ul>
</template>
<script>
import MyItem from "./MyItem";
export default {
name: "MyList",
components: { MyItem },
//子类获取
props:['todos']
};
</script>
2.儿子给父亲
在父类中写好方法进行参数接收,并将方法进行事件绑定,然后在子类中引入属性,进行方法的调用,将想要传递的值作为参数放入方法中 .经过事件绑定,此时的方法已经是vc的方法,在子类中用this即可调用,相当于子类去调用父类方法然后将数据传入.
这种方法可以实现兄弟与兄弟直接数据的传递,但是需要一个中间量放在共同父类中,操作这个中间量进而实现共享,本案例中,父类存储的每一条代办事项对象就是中间量,修改完后再给List进行遍历渲染,从而实现Header和LIst直接的数据共享
------------------------子类Header------------------父类Vue同上---------------------
<template>
<div class="todo-header">
<input
type="text"
placeholder="请输入你的任务名称,按回车键确认"
v-model="title"
@keyup.enter="add"
/>
</div>
</template>
<script>
import { nanoid } from "nanoid";
export default {
name: "MyHeader",
props: ["addTodo"],
data() {
return {
title: "",
};
},
methods: {
add() {
//校验数据是否为空
if (!this.title.trim()) return alert("输入不能为空");
//将用户的输入包装成一个todo对象
const todoObj = { id: nanoid(), title: this.title, done: false };
//走父类App的addTodo方法,将这个新todo对象塞到list表单中
this.addTodo(todoObj);
this.title = "";
},
},
};
</script>
3.兄弟给兄弟()
二、localStorage + 深度监视
进行本地存储时,前面的是否完成项容易不被系统存储,勾选复选框时刷新页面仍然会丢失数据,如果使用深度监视即可避免刷新后数据丢失的情况
watch: {
todos: {
deep: true,
handler(value) {
localStorage.setItem("todos", JSON.stringify(value));
},
},
},
消息订阅与发布
原理:数据接收者(订阅方)数据发送者(发布方)订阅方写一个回调函数,发布方调用后将数据发送过去
一种组件间通信的方式,适用于任意组件间通信
借用第三发库实现pubsub-js
使用步骤:
-
安装pubsub :npm i pubsub-js
-
引入pubsub:import pubsub from‘pubsub-js’
-
接收数据:A组件想要接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
methods(){ demo(data){....} } mounted(){ this.pid = pubsub.subscribe('xxx',this.demo) } //或者直接写在勾子里 要用箭头函数 第一个参数是订阅名字,第二个是传输的数据 mounted() { pubsub.subscribe("hello", (msgName, data) => { console.log("有人发布了hello消息,hello消息的回调执行了", msgName, data); }); },
-
提供数据:pubsub.publish(‘xxx’,数据)
methods: { sendStudentName() { pubsub.publish("hello", 666); }, }
-
最好在beforeDestroy钩子中,用pubsub.unsubscribe(pid)去取消订阅
$nextTick
里面写的函数能够在DOM节点更新完之后执行
过渡与动画
- 作用:再插入、更新或移除DOM元素时,再合适的时候给元素添加样式类名
- 图示:
- 写法:
- 准备好样式
- 元素进入的样式
- v-enter:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
- 元素离开的样式
- v-leave:离开的起点
- v-leave-active:离开过程中
- v-leave-to:离开的终点
- 元素进入的样式
- 使用包裹要过渡的动画是,并配置name属性 appear属性:刷新页面时开始启动动画
- 若有多个元素需要过渡,则需要使用,且每个元素要有指定的key值
- 准备好样式
Vue中应用axios
使用脚手架配置代理服务器
- 方式一:在vue.config配置项中配置
module.exports = {
devServer: {
proxy: 'http://localhost:xxxx' 另一台服务器的端口号
}
}
- 方式二:
module.exports = {
devServer: {
proxy: {
'/api': {
target: '<url>', //请求外部服务器的路径和上面一样的格式 http://localhost:xxxx
pathRewrite:{'^/api':''} //将配置路径清空
ws: true, //用于支持 web
changeOrigin: true
},
}
}
}
插槽
作用:让父组件可以向子组件位置插入HTML结构,也是一种组件间通信的方式,适用于父组件===>子组件
1.默认插槽
需求:三个一样的组件,但是其中两个需要放图片和视频,产生了需求不统一。在不破坏组件结构的情况下使用插槽来完善需求
在自定义标签中写标签
---------------------------------------------Vue组件-------------------------
<div class="container">
<Category title="美食">
<img src="http://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="" />
</Category>
<Category title="游戏">
<ul>
<li v-for="(g, index) in games" :key="index">{{ g }}</li>
</ul></Category
>
<Category title="电影">
<video
controls
src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"
></video>
</Category>
---------------------------------------Category组件--------------------------------------
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<!-- 定义了一个插槽,自定义标签名字里的标签内容插入到这里 -->
<slot></slot>
</div>
</template>
<script>
export default {
name: "Category",
props: [ "title"],
};
</script>
2.具名插槽 具有名字的插槽
要添加多个插槽的情况下,给slot添加name属性,然后在标签引入的时候将对应的名字添加上,让对应内容添加到对应的插槽中
3.作用域插槽
-
理解:数据在组件的自身,但是据根据生成的结构需要组件的使用者来决定
-
编码:
父组件中: <Category> <template scope="xxx"> <ul> <li v-for="g in xxx.games">{{g}}</li> </ul> </template> </Category> 子组件: <template> <div> <slot :games="games"></slot> </div> </template> <script> export default { nameL'Category', props:['title'], data(){ return{ games:['英雄联盟','红色警戒','泡泡堂'] } } } </script>
Vuex插件
- 概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中的多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
- 什么时候使用:
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
原理图:
Vuex的使用
安装:npm i @vuex3
创建一个store文件,内置index.js文件,引入vuex.防止vuex无法被调用,在index.js中将vuex引入
index.js
//创建vuex中最为核心的store
//引入Vue
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
Vue.use(Vuex)
//actions——用于相应组件中的动作
const actions = {}
//mutations——用于操作数据(state)
const mutations = {}
//state——用于存储数据
const state = {}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state
})
main.js引入
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入store
import store from './store/index'
//关闭Vue的生产提示
Vue.config.productionTip = false
//创建vm
new Vue({
el: '#app',
render: h => h(App),
store,
beforeCreate() {
Vue.prototype.$bus = this
},
})
四个map方法
帮助程序员生成State、Getters、Actions、Mutation方法
路由
1.相关理解:
1.vue-router的理解
Vue的一个插件库,专门用来实现SPA应用 npm i vue-router 进行库的安装
2.对SPA应用的理解
- 单页Web应用
- 整个应用只有一个完整的页面(index.html)
- 点击页面中的导航链接不会刷新页面,只会做页面的局部更新 切换时进行组件的切换来显示
- 数据需要通过ajax请求获取
2.路由的理解
- 一个路由就是一组映射(key-value)
- key为路径,value可能是function或component
路由的分类
- 后端路由
- 理解:value是function,用于处理客户端提交的请求
- 工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回相应数据
- 前端路由
- 理解:value是component,用于展示页面的内容
- 工作流程:当浏览器的路径改变时,对应的组件就会显示
3.路由的使用
-
安装vue-router,命令:
npm i vue-router
-
应用插件:
main.js //引入Vue import Vue from 'vue' //引入App import App from './App.vue' //引入VueRouter import VueRouter from 'vue-router' //引入路由器 import router from './router' //关闭Vue的生产提示 Vue.config.productionTip = false //应用插件 Vue.use(VueRouter) //创建vm new Vue({ el: '#app', render: h => h(App), router: router })
-
编写router配置项
//用于创建整个应用的路由器 import VueRouter from 'vue-router' //引入组件 import About from '../components/About' import Home from '../components/Home' //创建并暴露一个路由器 export default new VueRouter({ routes: [ { //如果路径是about,则显示About这个组件 path: '/about', component: About }, { path: '/home', component: Home } ] })
-
实现切换 借助路由库提供的
router-link
标签,内置active-calss
标签控制选择时切换的样式<router-link class="list-group-item" active-class="active" to="/about">About</router-link> <router-link class="list-group-item" active-class="active" to="/about">Home</router-link>
-
指定展示的位置,类似插槽
<router-view></router-view>
注意点:
-
路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹 -
通过切换"隐藏"了的路由组件,默认是被销毁的,需要的时候再去重新挂载
-
每个组件都有自己的
$route
属性,里面存储着自己的路由信息 -
整个应用只有一个router,可以通过组件的
$router
属性获取到
4.多级路由
-
配置路由规则,使用children配置项
routes: [ { //如果路径是about,则显示About这个组件 path: '/about', component: About }, { path: '/home', component: Home, children: [ //通过children来配置子级路由 { // 这里底层代码会给路径前添加/不需要手动添加 path: 'news', component: News }, { path: 'message', component: Message } ] } ]
-
跳转
<router-link class="" active-class="" to="" >Message</router-link>
5.路由的query参数
-
传递参数
<!-- 跳转路由并携带query参数,to的字符串写法 --> <li v-for="m in messageList" :key="m.id"> <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> <!-- 跳转路由并携带query参数,to的对象写法 --> <router-link :to="{ path: '/home/message/detail', query: { id: m.id, title: m.title, }, }" > {{ m.title }} </router-link> </li>
-
接收参数
<template> <ul> <li>消息编号:{{$route.query.id}}</li> <li>消息标题:{{$route.query.title}}</li> </ul> </template> <script> export default { name:'Detail', mounted() { console.log(this.$route) }, } </script>
6.路由器自带的方法
back:浏览器上面的返回上一级
forward:下一级
push:浏览时向栈中加入,可以通过上一级返回
replace:直接将次栈覆盖掉,无法再通过上一级返回
go:里面放数字参数,正数前进x页,负数后退
编程式路由导航
一种高效的路由切换方式,不用router-link标签标注,而是在组件中通过调用router的方法进行路由切换
主要方法如上所示
具体编码API
<template>
<div class="home">
<el-input
class="put"
v-model="username"
placeholder="请输入用户名"
></el-input>
<el-input
class="put"
placeholder="请输入密码"
v-model="password"
show-password
></el-input>
<el-button type="primary" class="right" @click="clearAll()">重置</el-button>
<el-button type="primary" class="right" @click="isLogin()">确定</el-button>
</div>
</template>
<script>
export default {
name: "Login",
data() {
return {
username: "",
password: "",
};
},
methods: {
clearAll() {
this.username = "";
this.password = "";
},
isLogin() {
let userObjs = JSON.parse(localStorage.getItem("userObjs"));
console.log(userObjs);
//判断是否登录成功
let isLogin = true;
for (let i = 0; i < userObjs.length; i++) {
if (
this.username === userObjs[i].username &&
this.password === userObjs[i].password
) {
isLogin = true;
break;
} else {
isLogin = false;
}
}
console.log(this.username, this.password);
console.log(isLogin);
if (isLogin === true) {
alert("登录成功");
this.$router.push({
path: "/todolist",
query: {
username: this.username,
},
});
} else {
alert("登录失败,用户名或密码不正确");
this.username = "";
this.password = "";
}
/* if (this.password !== password) {
alert("用户名密码不正确");
} */
},
},
};
</script>
<style scoped>
.put {
margin-bottom: 15px;
}
.right {
float: right;
margin-left: 15px;
}
</style>
7.缓存路由组件
-
作用:让不展示的路由组件保持挂载,不被销毁
-
具体编码:
<keep-alive include = "News"> //include后面引号内容是组件名字!!! <router-view></router-view> </kepp-alive> 缓存一个:直接写字符串,缓存多个,写数组形式 <keep-alive include = "['News','Message']">
8.路由守卫
控制路由的权限,场景:用户信息显示,是不是该用户登陆。在路由器中去查找localstory中用户的信息是否
- 前置路由守卫
在组件展示之前通过守卫控制权限
router.beforeEach((to,from,next) => {
console.log('@');
})
to:组件来自哪里
from:要去哪里
next():控制是否放行的权限,结合if判断语句
//给组件配置是否需要经过路由,可以通过meta{}里面配置自己想要的属性,值为true则会通过守卫权限判断
-
后置路由守卫
afterEach((to,from)=>{ //next()执行之前会走到后置路由守卫中 })
-
独享守卫:
写在想要拦截的组件里API:befroeEnter()
参数和前置路由一样,里面过滤代码一样,并且可以公用后置守卫 -
组件内路由守卫(API写在vue组件内不是index.js文件中)
beforeRouteEnter
通过路由规则时被调用
beforeRouteLeave
通过路由规则离开时被调用
执行顺序–>
全局守卫>独享守卫>组件内路由守卫
全局前置和后置在启用组件时
考核复盘
Vuex的几个主要模块
状态自管理应用
单页面的状态管理:
- state:数据源(data)
- view:以声明的方式映射到视图(html展示的东西)
- actions:响应在view上的用户输入导致的状态变化(methods,操纵数据的变化)
多页面状态管理(vuex)
- state:数据源
- mutation:状态更新 需要commit提交后更新
- action:代替mutation进行异步操作
生命周期
4对:
- 创建
- 挂载
- 更新
- 销毁
计算属性和方法的性能问题
计算属性自带缓存,只有相关依赖发生改变时才会重新取值。methods方法会在每次渲染页面的时候呗调用执行
cookie
不超过4kb的小型文本文件,可以保存用户在浏览器中的数据——用户登录数据
v-if和v-for的优先级
vue2:v-for优先级大于v-if
vue3:v-if优先级大于v-for
两个指令避免出现在一起
子组件给父组件传值
- 全局事件总线
- 通过给vm实例添加一个新的属性
$bus
,将想要传的数据挂在$bus
上,利用$bus.on
绑定,然后用$emit
使用绑定数据,达到父组件共享子组件数据
- 通过给vm实例添加一个新的属性
- slot插槽
- 子组件通过
slot name=''
定义插槽,内放入子组件数据,通过父组件slot
标签将数据拿过来使用
- 子组件通过
- $emit方法
- 父类自定义方法名,在子组件用
$emit
将想要传的值给父类定义的那个方法名,然后在父类接收并且使用
- 父类自定义方法名,在子组件用
- $ref自定义事件名
- 通过
this.$refs.hello.数据
获取当前的DOM节点,后面加上这个获取当前节点里的数据即可提取到里面的数据
- 通过
全局API
Vue.extend()创建一个子类
Vue.use() 使用插件
Vue.filter() 注册或获取全局过滤器