什么是vue.js
解释:
- 它是一个前端的一个js框架
- 它是用于构建用户界面的渐进式框架
核心:
- 数据驱动
- 双向数据绑定
- 组件化
- 模块化
- MVVM框架
优点:
- 体积小:压缩后的vue只有20K+
- 数据双向绑定:让开发者不在操作DOM,把更多的精力投入到业务逻辑中
- 组件化:增加代码复用性、灵活性、提高开发效率、便于维护
- 生态丰富、学习成本低:市场上有大量、稳定的基于vue.js的ui框架,学习起来比较容易
- 便与第三方库或者已有的项目整合
- 虚拟DOM(VNode)
缺点
SEO难度较大
浅谈Vue、Angulary、React之间的区别
- vue更加轻量,压缩后只有20k+,react压缩后44k,angulary压缩后56k(只是参考值)
- vue入门简单,angulary学习较难,react学习的东西相对较多
- vue和angulary都有内置指令和过滤器并且都支持自定义指令和过滤器
- 在组开发中vue用的是特殊文件格式.vue,react用的是jsx语法;react用的是函数式编程,两者都有相同点就是组件化
- vue用的是DOM模板,而react依赖于虚拟DOM
浅谈什么是虚拟DOM(Virtual DOM)
解释:
虚拟DOM不是真实的DOM,而是一个轻量级的js对象,用js对象模拟真实的DOM,当状态发生改变时,通过diff算法比较出虚拟dom的差异,在通过path算法将差异应用到真实DOM上
优点:
提高渲染性能、无需手动操作真实dom
缺点:
首次渲染DOM时,由于多了一层虚拟DOM计算,会比innerHTML插入慢
vue中的MVVM
Ps:MVC全名modelViewController,model(数据),view(视图),controller(控制器)。当用户操作界面时会向后端发送请求,请求会被路由进行拦截,然后转发给对应的控制器进行处理,控制器会获取数据,最后将数据回显到视图上,这种形式是单向的。
解释:
MVVM它是一种设计模式,源于经典的MVC,它的核心是ViewModul(数据视图层),它的出现促进了前端开发和后端业务逻辑的分离,提高了工作效率
MVVM全名model-view-viewModel:
- M-Model(数据模型)
{
"url": "/your/server/data/api",
"res": {
"success": true,
"name": "IoveC",
"domain": "www.cnblogs.com"
}
}
- V-View(视图层)
<div id="app">
<p>{{message}}</p>
<button v-on:click="showMessage()">Click me</button>
</div>
- VM-ViewModel(new Vue({.....})视图数据层)
var app = new Vue({
el: '#app',
data: { // 用于描述视图状态
message: 'Hello Vue!',
},
methods: { // 用于描述视图行为
showMessage(){
let vm = this;
alert(vm.message);
}
},
created(){
let vm = this;
// Ajax 获取 Model 层的数据
ajax({
url: '/your/server/data/api',
success(res){
vm.message = res;
}
});
}
})
三者之间的关系:
- View可以通过绑定事件的形式影响Model,Model通过绑定数据形式影响View
- ViewModel是将View和Model连接起来的连接器
vue的生命周期
事物从诞生到消亡的整个过程称为生命周期
什么是vue的生命周期:
实例从开始创建、初始化数据、模板编译、挂载DOM、渲染、更新、渲染、卸载等一系列过程,我们称之为生命周期。
各个生命周期的作用:
| 生命周期 | 描述 |
|---|---|
| beforeCreate | 实例创建之前,需要注意的是data和methods还没初始化 |
| created | 实例创建完成,如果想调用methods方法和data中数据最早只能在created操作 |
| beforeMount | 实例挂载之前,模板只是在内存中编译好了,并没有真正挂载到页面,此时页面还是旧的 |
| mounted | 实例挂载完成,如果想操作MOD节点,最早只能在mounted中操作 |
| beforeUpdate | 实例更新之前,此时页面还是旧的,data中的数据是最新的 |
| updated | 实例更新完成,此时data中的数据和页面保持同步,都是最新的 |
| beforeDestroy | 实例销毁之前,此时所有的data、methods、指令、过滤器还可以使用,并没有真正销毁 |
| destroyed | 实例真正被销毁了,此时所有数据、方法、指令、过滤器都不可使用 |
| activated | keep-alive专属,组件被激活时调用 |
| deactivated | keep-alive专属,组件停用是被调用 |
| errorCaptured | 当捕获一个子孙组件错误是调用 |
生命周期示意图:
computed和watch的区别
computed计算属性:
解释:
将数据处理后显示,计算结果会被缓存,除非依赖的状态发生改变才会重新计算
本质写法和简写:
computed: {
// 本质写法
rice: {
get() { // 读取
return this.name + this.lastName
},
set(v) { // 设置 不常用 v指的是修改值
console.log(v)
}
},
// 简写
rice() {
let result = 0;
for (let i = 0; i < this.list.length; i++) {
result += this.list[i].pirc*1
}
return result;
}
}
watch数据监听:
解释:
watch是个对象,以键值对的形式存在,键是被监听者,值可以是函数、函数名、数组、有选项对象(handler/immedieat/deep)
var app = new Vue({
el: "#app",
data:{
num: 27,
defaultChuanYi: "T恤短袖",
yifuArray: ["T恤短袖","夹克长裙","棉衣羽绒服"],
obj: {
age: 14,
sex: "男"
}
},
methods: {
add () {
this.num++
this.yifuArray.push(1)
},
reduce () {
this.num--
},
numName(newVal,oldVal) {
console.log(newVal,oldVal)
}
},
// 1.函数写法
watch: {
num: function (newVal,oldVal) {
if(newVal > 26){
this.defaultChuanYi = this.yifuArray[0]
}else if(newVal > 0 && newVal < 26) {
this.defaultChuanYi = this.yifuArray[1]
}else if(newVal < 0){
this.defaultChuanYi = this.yifuArray[2]
}
}
},
// 2.函数名的写法
watch: {
// num: "numName"
num: {
handler: "numName",
immediate: true
}
},
// 3.包含选项对象的写法 (是否首次绑定时执行handler)
// handler 和 immediate 属性
// immediate 表示在watch中首次绑定的时候是否执行handler,为true,则执行,为false则和一般使用watch一样,当数据变化才执行handler
watch: {
num: {
handler(newVal,oldVal){
console.log(newVal,oldVal)
},
immediate: true
}
},
// 4.deep是否深度监听,true深度 false不深度,常用于对象属性的改变
watch: {
"obj.age": { // 对象
handler(newVal,oldVal) {
console.log(newVal,oldVal)
},
deep:true,
immediate: true
},
yifuArray: { // 数组 (数组的变化不需要深度监听,对象数组需要深度监听)
handler(newVal,oldVal) {
console.log(newVal,oldVal)
},
// deep:true,
immediate: true
}
},
// 5.值为数组时写法
watch: {
num: [
// 函数名
'numName',
// 函数
function numName1() {
console.log(111111111111)
},
// 对象
{
handler() {},
immediate: true
}
]
},
});
// 使用$watch()方法,也就是把我们watch卸载到构造器外部,这样做的好处就是降低我们程序的耦合度
// app.$watch("num",function (newVal,oldVal) {
// if(newVal > 26){
// this.defaultChuanYi = this.yifuArray[0]
// }else if(newVal > 0 && newVal < 26) {
// this.defaultChuanYi = this.yifuArray[1]
// }else if(newVal < 0){
// this.defaultChuanYi = this.yifuArray[2]
// }
// },{deep:true})
两者之间的区别:
- computed支持缓存,watch不支持
- watch支持异步,computed不支持
- computed是依赖的数据发生改变才重新计算,watch是被监听的数据发生改变才会触发相对应回调
- 需要依赖某个属性动态获取值的时候使用computed,对于监听的值需要处理复杂的业务逻辑和异步操作时,可使用watch
低耦合:通俗的说就是你写个组件,这个组件在任何项目任何场景随拿随用,不影响其它功能;高内聚:指的是将功能模块集合在一起(例如用户模块,把用户相关的内容放在一起,比如用户的个人信息、用户收藏之类的)
vue中哪些数组方法可以做到响应式(数据改变,视图随之改变)
- arr.push() 在数组末尾添加一个或多个元素,返回新素组,改变原数组
- arr.pop() 在数组末尾删除一个元素,返回被删除的元素,改变原数组
- arr.unShift() 在数组开头添加一个或多个元素,返回新数组,改变原素组
- arr.shift() 在数组开头删除一个元素,返回被删除的元素,改变原数组
- arr.splice() 数组的添加、删除、替换,返回的是被删除的元素组成的数组 改变原数组
- sort() 排序
- reverse() 反转
Vue.set() 解决某些情况数据变化视图不能随之变化问题
语法:
Vue.set(targer,key,value)
解释:
vue在初始化时会遍历data选项,通过Object.defineProperty()给每个属性加上get、set方法,当你新添加的属性时,这个属性是没有set、get方法的,所以视图不更新
-
当使用数组下标操作数组的时候
1.this.list[0] = 'vue' 直接改变,视图不会更新
2.this.list.splice(1,1,'vue') 使用splice可解决
3.vue.set(targer,value,key) 使用vue.set()可解决
-
给对象添加新属性
1.this.object.name = 'vue' 视图不会随之改变
2.Vue.set(target,key,value) 使用Vue.set() 可解决
-
删除对象中的某个属性
1.this.$delete(this.objName,"name")
-
vuex同理,只是数据是必须提前定义在store中的state里才能达到响应式
常用修饰符
<!--阻止冒泡 相当调用event.stopPropagation()-->
<button @click.stop="doThis"></button>
<!-- 使用事件捕获模式 -->
<button @:click.capture="doThis"></button>
<!-- 阻止默认行为 相当调用event.preventDefault()-->
<button @click.prevent="doThis"></button>
<!-- 即阻止冒泡又阻止默认行为 -->
<button @click.stop.prevent="doThis"></button>
<!-- 事件只会触发一次 -->
<button @:click.once="doThis"></button>
<!-- 当前事件发生在元素本身而不是在子元素的时候才触发回调 -->
<button @:click.self="doThis"></button>
<!-- 对象方式可监听多种事件 -->
<button @on="{ mousedown: doThis, mouseup: doThat }"></button>
<!--组件绑定原生事件.native-->
<my-component @click.native="onClick"></my-component>
对象增强写法
<!--属性的简写-->
let name = 'zhang';
let lastName = 'wang';
const obj = { // es5写法
name:name,
lastName:lastName
}
console.log(obj) // {name: "zhang", lastName: "wang"}
const obj1 = { // es6写法
name,
lastName
}
console.log(obj1) // {name: "zhang", lastName: "wang"}
<!--方法的的简写-->
const methondObj = { // es5写法
run: function () {
}
}
const methondObj1 = { // es6写法
name,
run() {
}
}
为什么v-for要加key属性
解释:
vue会默认遵循就地复用策略,当真实的DOM渲染出来之前,vue会在内存中创建VNode,然后VNode与当前真是DOM之间进行对比,已有的进行复用,不同的进行修改,没有的进行创建,最后应用的真实的DOM上。加上key属性相当于加上了唯一标识,让vue更好的复用(有些情况是让vue更好的不复用)。
作用:
key的主要作用是提高渲染性能,高效更新VNode
v-model 在表单控件或者组件上创建双向绑定
解释:
-
v-model其实是一个语法糖,本质包括两个操作
1.v-bind绑定Value属性
2.v-on绑定input事件
-
元素上使(input文本框为列)
// 两者等价 <input type="text" v-model="message"> <input type="text" :value="message" @input="message = $event.target.value"> -
组件上使用
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input的事件,但是像单选框、复选框等类型的输入控件可能会将value 用于不同的目的。model 选项可以用来避免这样的冲突:
Vue.component('base-checkbox', { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, template: ` <input type="checkbox" v-bind:checked="checked" v-on:change="$emit('change', $event.target.checked)" > ` })现在在这个组件上使用 v-model 的时候:
<base-checkbox v-model="lovingVue"></base-checkbox>这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的属性将会被更新。
注意你仍然需要在组件的 props 选项里声明 checked 这个 prop
全局注册组件和局部注册组件
-
组件:组件就是自定义标签,这些标签是html不存在的
-
组件和指令的区别:组件注册的是标签,指令注册的是已有标签的属性
-
全局组件&局部组件
本质写法
const cpnC = Vue.extend({ template: `<div><p>全局注册</p></div>` }) // 全局注册组件 (cnp 组件名) Vue.component('cnp',cpnC) const app = new Vue({ el: '#app', data: { }, // 局部注册 components: { "cnp": cpnC } })到了2.x可以简写,但本质vue内部还是调用的Vue.extend()
// 全局注册组件 (cnp 组件名) Vue.component('cnp1',{ template: `<div><p>全局注册语法糖</p></div>` }) const app = new Vue({ el: '#app', data: { }, // 局部注册 components: { "cpn2": { template: `<div><p>局部注册语法糖</p></div>` } } });
为什么data选项是个函数
- 因为js中的对象是引用关系,组件中的data是个函数的话,每次返回的会是一个新对象,避免组件复用时相互影响
- 如果组件中的data不是一个函数的的话,vue会直接报错
v-slot插槽
解释:
- 主要作用是决定组件内部显示怎么的内容
- 好处是让封装的组件更有扩展性、灵活性
- 在2.6中我们为具名插槽和作用域插槽引入新的统一语法v-slot,用于替代slot和slot-scope
- v-slot只能用在组件上和template上,不能用在普通元素上,而slot可以用在组件上。(当只要默认插槽时才用在组件上,出现多个插槽必须用在templae上)
- 一个不带name属性的slot元素有个默认名字default
- v-slot的语法糖 "#"
- v-slot:name = "slotProps"和v-slot:name的区别:前者叫作用域插槽,后者叫具名插槽
- 插槽内容可以是任何代码也可以是组件
2.6以前的用法 (slot slot-scope):
<body>
<!-- 1.普通插槽 -->
<!-- 父组件模板 -->
<div id="app">
<cpn>
<p>11111111111</p>
</cpn>
</div>
<!-- 子组件模板 -->
<template id="cpnTemp">
<div>
<div>子组件标题显示</div>
<slot></slot>
</div>
</template>
<!-- 2.具名插槽 -->
<!-- 父组件模板 -->
<div id="app">
<cpn>
<p slot="slot1">xxxx我是slot1</p>
<p slot="slot2">xxxx我是slot2</p>
<p slot="slot3">xxxx我是slot3</p>
</cpn>
</div>
<!-- 子组件模板 -->
<template id="cpnTemp">
<div>
<div>子组件标题显示</div>
<slot name="slot1">我是slot1</slot>
<slot name="slot2">我是slot2</slot>
<slot name="slot3">我是slot3</slot>
</div>
</template>
<!-- 3.作用域插槽 -->
<!-- 父组件模板 -->
<div id="app">
<cpn>
<!-- 用在普通元素上 -->
<div slot="slot1" slot-scope="messagePro">
<p>{{messagePro.message}}</p>
</div>
<!-- 用在template上 -->
<template slot="slot1" slot-scope="{message}">
<div>
<p>{{message}}</p>
</div>
</template>
</cpn>
</div>
<!-- 子组件模板 -->
<template id="cpnTemp">
<div>
<div>子组件标题显示</div>
<slot name="slot1" :message="message">默认内容</slot>
</div>
</template>
</body>
<script>
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn: {
template: '#cpnTemp',
data() {
return {
message: '这是子组件传递给父组件的数据'
}
}
}
}
});
</script>
2.6以后用法 (v-slot):
<body>
<!-- 1.普通插槽-->
<!-- 父组件 -->
<div id="app">
<cpn></cpn>
</div>
<!-- 子组件 -->
<template id="cpnTemp">
<div>
<div>子组件</div>
<slot>我是默认内容</slot>
</div>
</template>
<!-- 2.具名插槽-->
<!-- 父组件 -->
<div id="app">
<cpn>
<template v-slot:header>
<div>
<p>这个是___hearder内容</p>
</div>
</template>
<template v-slot:default>
<div>
<p>这个是___section内容</p>
</div>
</template>
<template #footer>
<div>
<p>这个是___footer内容</p>
</div>
</template>
</cpn>
</div>
<!-- 子组件 -->
<template id="cpnTemp">
<div>
<div>子组件</div>
<header>
<slot name="header">我是头部内容</slot>
</header>
<section>
<slot>我是中间内容</slot>
</section>
<footer>
<slot name="footer">我是底部内容</slot>
</footer>
</div>
</template>
<!-- 3.作用域插槽 (插槽内容由子组件提供)-->
<!-- 父组件 -->
<div id="app">
<!-- v-slot用在template上 -->
<cpn>
<template v-slot:header="{message,messagetow}">
<div>
<p>{{message}}</p>
<p>{{messagetow}}</p>
</div>
</template>
<template #default="slotProps">
<div>
<p>{{slotProps.message}}</p>
</div>
</template>
</cpn>
<!-- 用在组件上 当只有默认插槽时(一个插槽时)使用在组件上,当出现多个插槽必须写template上 -->
<!-- <cpn #default="slotProps">
<p>{{slotProps.message}}</p>
</cpn> -->
</div>
<!-- 子组件 -->
<template id="cpnTemp">
<div>
<div>子组件</div>
<header>
<slot name="header" :message="message" :messagetow="messagetow">我是头部内容</slot>
</header>
<section>
<slot :message="message">我是中间内容</slot>
</section>
<!-- <slot :message="message">当只有默认插槽时(一个插槽时)使用在组件上,当出现多个插槽必须写template上</slot> -->
</div>
</template>
</body>
<script>
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn: {
template: '#cpnTemp',
data() {
return {
message: '这是子组件传递给父组件的数据message',
messagetow: '这是子组件传递给父组件的数据messageTow'
}
}
}
}
});
</script>
runtime-compiler和runtime-only的区别
- 两者区别主要在main.js文件中
// runtime-compiler
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})
// runtime-only
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
- runtime-only构建的项目体积更小,运行速度更快,性能会更高点
组件的渲染过程:
先将template解析成ast(抽象语法树),ast编译成render函数,通过render函数创建虚拟dom,最终渲染真实的DOM。
可以看出runtime-only比runtime-compiler少了一个过程,所有runtime的性能更好点
浅谈async和awite的原理和使用
解释:
async和awite其实就是异步等待的意思
原理:
当函数加上async、awite时,这个函数其实返回的就是Promise对象,即使我们调用的是异步代码,它也会变成类似同步,等异步代码执行完在执行同步
使用:
<script>
const app = new Vue({
el: '#app',
data: {
totlPirc: 0,
lastName: ''
},
methods: {
// 模拟异步请求
addRun() {
return new Promise((resolve,rejict)=>{
setTimeout(() => {
resolve('异步结果')
},2000)
})
},
// async、await
async getList() {
console.log('我在前执行');
this.data = await this.addRun();
console.log('我在后执行')
console.log(this.data)
}
},
created() {
console.log('组件被创建执行');
this.getList()
console.log('craated里不用等待');
console.log(this.getList()) // 这个async返回的其实是个Promist对象
// console.log(this.data) 拿不到数据
},
updated() {
console.log(this.data)
},
});
</script>
父组件如何监听子组件的生命周期
使用@hook:mounted(每一个生命周期)= 'Event'
// Parent.vue
<child @hook:mounted="doSomething" ></child>
doSomething() {
console.log('父组件监听到 mounted 钩子函数 ...');
},
// Child.vue
mounted(){
console.log('子组件触发 mounted 钩子函数 ...');
}
vue的响应式原理
data变化更新view,vue是如何做到的呢。其实vue是通过数据劫持配合发布-订阅者模式实现的
具体实现总结以下几点
-
实现一个监听器Observer
当把data选项传递给vue时,vue会将data进行遍历,然后利用Object.defineProperty给每个属性加上get、set方法,当数据被使用会触发get方法,当数据被修改会触发set方法,这样就能监听到数据了
-
实现一个解析器compiley
用于解析vue模板的指令,初始化视图,给对应的节点绑定更新函数,当数据发生变化,收到通知,会调用更新函数更新数据
-
实现一个订阅者watcher
它是Observer和compiler之间通信的桥梁,主要任务是订阅Observer中属性值变化的消息,当收到消息后,就会通知解析器
-
实现一个订阅器Dep
主要用于收集watcher,进行统一管理
总结:当数据被修改时通知对应的订阅器Dep,Dep通知对应的watcher,watcher在通知compiler,compiler调用对应更新函数更新数据。
.sync、v-mode (父子组件通信,都是单项的,很多时候需要双向通)
-
.sync修饰符
他会自动的被扩展为一个自动监听父组件属性的监听器(v-on)
<comp :foo.sync="foo "></comp> 等价于 <comp :foo="bar" @update:foo="foo = $event"></comp> this.$emit( 'update:foo', newValue ) -
v-mode (参考以上的v-mode)
axios
axios:
axios是一个基于Promise的HTTP库,主要用于网路请求
特点:
- 支持Promise
- 请求拦截、响应拦截
- 能转化请求的数据和响应的数据
- 安全性较高
组件化思想
组件
组件就是自定义标签,这些标签是html不存在的
思想
将一个完整的页面拆分成许多个组件,每个组件实现对应的功能,而每一个组件又可以细分,最后进行组合
优点
增加复用性、灵活性、提高工作效率、便于维护
模块化
举个例子:开发项目的时候会有多人开发,会引入多个js文件,这样会导致
变量冲突问题,虽然可以用函数自运行的方法解决,但是会引发其他问题,比 如代码不能够复用,这个时候模块化就很好的解决这类问题
- 模块化的核心是导入和导出
- 常见的模块化有AMD、CMD、CommonJs、ES6的modules
以CommonJs和ES6的modules举例
CommonJs 导出
module.exports = {
num,
ji
}
CommonJs 导入
let {num,ji} = require('./math');
es6的导出
export const result = 0;
export default function() {
}
ES6的导入
import {result,info} from './math'
import name from './math'
import * as math from './math'
export 和 export default 两者区别:
a.export 和 export default两者均可导出常量、函数、文件、模块
b.一个文件中export可以有多个,export default只能一个
c.通过export导出,导入时需要加 {} ,而export default不需要
优点
- 避免代码冲突,造成不必要的麻烦
- 提高工作效率
- 提高维护性
浅谈服务端渲染 SSR
解释
简单的来说就是整个页面在服务端渲染好,然后返回给客服端展示
优点
更好的SEO、首屏加载更快(缓慢的解决办法:模块化、按需加载、占位图)
缺点
服务器压力大、学习成本高
什么是SPA单页面富应用
解释
整个网站只有一个html页面,当页面初始化时会向服务器一次性请求所有资源(如:html,css,js),部分页面按需加载(如:路由懒加载),然后在通过路由机制改变html的页面内容。
优点
用户体验好、避免页面重新加载、减少服务器压力
缺点
初始化时间较长、SEO难度较大
组件通信-父传子-Props选项
<body>
<div id="app">
<div>
<p>父组件显示</p>
<p>{{object}}</p>
<p>{{defaultString}}</p>
<cpn :init="object" :default-string="defaultString"></cpn>
</div>
</div>
<template id="cpnTemplate">
<div>
<p>子组件显示</p>
<button @click="modify()">修改</button>
<p>{{init}}</p>
<p>{{defaultString}}</p>
</div>
</template>
</body>
<script>
const cpnTemplate = {
template: '#cpnTemplate',
props: {
init: {
type: Object,
default(){
return {
name: '默认'
}
}
},
defaultString: {
type: String,
default: '默认Sring'
}
},
// 验证子组件是否能直接修改父组件传递的值
methods: {
modify() {
// 如果是对象或数组可以直接修改
this.init.name = '王'
// 如果是字符串直接报错
this.defaultString = '直接修改String类型报错'
}
},
}
const app = new Vue({
el:'#app',
data: {
object: {
name: '小张',
age: 18
},
defaultString: 'string'
},
components: {
'cpn': cpnTemplate
}
});
// porps的验证
Vue.component('my-component', {
props: {
// 必须是数字类型
prop1: Number,
// 必须是字符串或者数字类型
prop2: [String, Number],
// 布尔,默认true
prop3: {
type: Boolean,
default: true
},
// 数字且必传
prop4: {
type: Number,
required: true
},
// 如果是数组或者对象,默认必须是一个函数返回
prop5: {
type: Object,
default: function () {
return {}
}
},
// 自定义一个校验函数
prop6: {
validator: function (value) {
return value > 10
}
}
// 注:type类型可以为Number、String、Boolean、Object、Array、Function,Symbol
}
})
// 总结:
// 如果父组件向子组件传递的参数是对象或者数组,子组件可以直接修改接受的参数,并且父组件也会被修改
// 如果传递的参数是字符串,直接修改会报错
// 不推荐子组件直接修改父组件传递过来的参数,避免这个参数在多个子组件中使用造成不必要的麻烦
</script>
父组件访问子组件方式 refs、children
<body>
<div id="app">
<p>父组件</p>
<button @click="cpnAdd()">父组件访问子组件</button>
<cpn ref="cpn"></cpn>
<cpn ref="cpn1"></cpn>
</div>
<template id="cpn">
<div>
<p>子组件</p>
<p>{{num}}</p>
<button @click="add()">add</button>
</div>
</template>
</body>
<script>
// 父
const app = new Vue({
el: '#app',
data: {
},
methods: {
cpnAdd() {
// 1.$refs 常用 默认是空对象,且指向子组件集合 {cpn: VueComponent, cpn1: VueComponent}
this.$refs
this.$refs.cpn.add()
// 2.$children 不常用 是个数组,且指向子组件集合 [VueComponent, VueComponent]
this.$children
this.$children[0].num
}
},
// 子
components: {
cpn: {
template: '#cpn',
data() {
return {
num: 1
}
},
methods: {
add() {
this.num++
}
},
}
}
});
</script>
组件通信-子传父-this.$emit()
<body>
<div id="app">
<div>
<cpn @feilei="feileiData"></cpn>
</div>
</div>
<template id="cpnTemplate">
<div>
<ul>
<li v-for="(item,index) in list"
:key="item.id"
@click="childrenData(item)">
{{item.name}}
</li>
</ul>
</div>
</template>
</body>
<script>
// 子组件
const cpnTemplate = {
template: '#cpnTemplate',
data() {
return {
list: [
{id: 1, name: '热门推荐'},
{id: 2, name: '人民日报'}
]
}
},
methods: {
childrenData(item) {
this.$emit('feilei',item)
}
},
}
// 父组件
const app = new Vue({
el:'#app',
data: {
object: {
name: '小张',
age: 18
},
defaultString: 'string'
},
components: {
'cpn': cpnTemplate
},
methods: {
feileiData(item) {
console.log(item)
}
},
});
// 子组件通过this.$emit('envet',parameter)自定义事件,父组件监听此事件
</script>
子组件访问父组件的方式 parent 、root
<body>
<div id="app">
<p>父组件</p>
<p>{{num}}</p>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<p>子组件</p>
<button>子组件访问父组件的方式$parent</button>
<cpn1></cpn1>
</div>
</template>
<template id="cpn1">
<div>
<p>孙子组件cpn1</p>
<button @click="parentAdd">孙子组件cpn1访问父组件cpn的方式$parent</button>
</div>
</template>
</body>
<script>
const app = new Vue({ // 父组件
el: '#app',
data: {
num: 1
},
components: { // 子组件
cpn: {
template: '#cpn',
data() {
return {
chenrui: '小陈'
}
},
components: { //孙组件
cpn1: {
template: '#cpn1',
methods: {
parentAdd() {
// 1.this.parent 子组件访问父组件的方式
// console.log(this.$parent)
// 2.this.$root // 访问根组件
// console.log(this.$root)
}
},
}
}
}
}
});
</script>
注意:
1.尽量不要让子组件直接访问父组件的数据,这样耦合度会很高
2.不要通过$parent直接修改父组件的数据,因为父组件的数据可能多个子组件使用,导致出现不必要问题
3.低耦合通俗的说就是一个组件在任何复杂的项目中都何用即拿即用,高耦合相反
非父子组件通信
事件总线:
1.main.js文件中全局实例化$bus事件总线
Vue.prototype.$bus = new Vue()
2.发送事件总线
this.$bus.$emit('Event',params)
3.监听事件总线
this.$bus.$on('Event',callBack)
vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import {PRICE} from './mutations-type'
// 1.什么是vuex
// vuex是状态管理工具,简称仓库
// 2.什么时候使用vuex
// 当多个组件需要使用同一个状态的时候,例如登录状态、用户名称、头像
// 3.vuex的搭建
// @1.安装vuex并且使用vue.use()进行调用
// @2.创建store(xuex)对象 new Vuex.Store({})
// @3.将store(vuex)对象挂载到vue实例上
// 4.vuex的状态管理官网图理解
// 5.vuex的五个核心概念
export const store = new Vuex.Store({
// state: 用于存储数据 this.$store.state.
state: {
producttata: [
{name: '张一',price: '200'},
{name: '张二',price: '300'},
{name: '张三',price: '400'},
{name: '张四',price: '500'}
]
},
// getters用于获取数据 类似computed
// 获取方式:this.$store.getters. 、...mapGetters([])
// state是默认的第一参数,getters是第二个
getters: {
reductionProduct(state,getters){
let product = state.producttata;
let changeProduct = product.map((val,index,arr) => {
return {
name: '__' + val.name,
price: val.price / 2
}
})
return changeProduct
}
},
// 用于修改数据 (同步处理 当你触发事件修改数据的时候使用mutations)
// this.$store.commit('事件类型',params)
// this.$store.commit({'type',payload})
// 使用常量来代替mutations中的事件类型 列如:[PRICE]方法
// mutations中的方法必须是同步的,否则devtools无法检测数据的变化,不利于调试
mutations: {
[PRICE](state,payload) {
state.producttata.forEach( value => {
value.price -= payload
})
}
},
// 类似mutations,但它是用来代替mutations进行异步操作
// actives里的方法可以返回Promise(return new Promise)对象,而this.$sotre.dispatch('name',payload)等于当前这个promise对象
actions: {
// price(context,num) {
// setTimeout(() => {
// context.commit(PRICE,num)
// },1000)
// },
price(context,payload) {
return new Promise((resolve,reject) => {
setTimeout(() => {
context.commit(PRICE,payload)
resolve()
},1000)
})
.then((res) => {
console.log('修改成功')
})
.catch((error) => {
console.log('修改失败')
})
}
},
// 模块的意思
// 因为vuex使用的是单一状态树,当我们状态特别多的时候,vuex允许我们将store分割成模块,
// 单一状态树:也叫单一数据源,整个项目中只创建一个store,便于管理、维护以及调试
// 每个模块都有自己的state,getters,mutations,actives,modules :例如moduleA
// 在组件上使用的方法和上面大致相同
modules: {
moduleA: {
state: {},
getters: {},
mutations: {},
actions: {},
modules: {}
}
}
})
vue-router参数传递(params、query)
params (传递单个参数)
// 传递
<router-link :to="/user/+id">用户</router-link>
// 在路由中配置
{
path: '/user/:id',
component: User
}
// 获取
this.$route.params.id
query (传递多个参数)
// 方式一
<router-link :to="{
path:'/profile',
query:{
name:'xiaosan',
age:age
}
}">跳转</router-link>
// 方式二
this.$router.push({
path:'/profile',
query:{
name:'xiaosan',
age:age
}
})
// 获取:
this.$route.query.name
路由懒加载
场景
通常我们会在路由中定义很多页面,打包后这些页面会放在一个js中,导致页面过大,如果一次性向服务器请求这个页面,请求时间过长,甚至用户界面会出现短暂的空白
作用
主要作用是将不同的路由对应的组件打包成一个个代码块,当哪个路由被访问就加载哪个组件
写法
const path = () => import('/path.vue')
keep-alive
作用
keep-alive是vue提供的内置组件,主要用于保持组件的状态,避免组件重新加载,有缓存能力
//使用方法一
<keep-alive exclude="componentName1,componentName2,...">
<router-view></router-view>
</keep-alive>
两个属性:include、exclude
include: 哪些组件可以被缓存
exclude:哪些组件是不被缓存的
//使用方法二
{
path: '/category',
name: 'category',
component: Category,
meta: {
keepAlive: false // 不被缓存
}
}
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
两个函数:
activated() // 组件被激活时调用
deactivated() 组件被停用时调用
必须被keep-alive包含住的组件才可以被触发调用
router-link除了to之外的几个属性
tag:将router-link渲染成指定元素
<router-link to="/home" tag="div">首页</router-link>
repalce: 浏览器的前进后退功能不能使用
<router-link to="/home" replace>首页</router-link>
active-class:
a.修改router-link默认的class名router-link-active
b.例如:<router-link to="/home" active-class="active" >首页</router-link>
c.统一修改在路由实例中添加linkActiveClass: 'active'
对vue项目进行哪些优化
- 区分v-if、v-show的使用场景
- 区分computed和watch的使用场景
- 使用v-for时必须加key属性
- 使用图片懒加载
- 使用路由懒加载
- 使用防抖和节流
- 减少冗余代码
- 提取公共代码
- 第三方插件按需加载
- 事件的销毁
Vue 组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。 如果在 js 内使用 addEventListene 等方式是不会自动销毁的,我们需要在组件销毁时手动移除这些事件的监听,以免造成内存泄露
<script>
created() {
addEventListener('click', this.click(), false)
},
beforeDestroy() {
removeEventListener('click', this.click(), false)
}
</script>