1、Vue的优点和缺点
Vue的优点:
-
组件化开发,减少代码编写量
-
数据双向绑定
-
响应式界面
-
Vue使用路由不会刷新页面
-
体积小,轻量化
-
虚拟DOM
-
运行效率高
-
生态丰富、学习成本低
Vue的缺点:...
2、Vue生命周期
Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程
vue生命周期的作用
生命周期中有多个事件钧子,更好的控制整个Vue实例的过程中的各种逻辑
vue八个生命周期阶段
-
beforeCreate 创建前
-
created 创建后
-
beforeMount 载入前
-
mounted 载入后
-
beforeUpdate 更新前
-
updated 更新后
-
beforeDestroy 销毁前
-
destroyed 销毁后
第一次页面加载会触发的钩子
beforeCreate、created、beforeMount、mounted
DOM渲染在mounted周期中就已经完成
父子组件生命周期顺序
-
父组件---beforeCreate
-
父组件---created
-
父组件---beforeMount
-
子组件---beforeCreate
-
子组件---created
-
子组件---beforeMount
-
子组件---mounted
-
父组件---mounted
3、v-if 和 v-show 区别
1、实现方式:v-if是通过控制dom节点的存在与否,来控制元素的显隐; v-show是通过设置DOM元素的display样式,block为显示,none为隐藏;
2、编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换;
3、编译条件:v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存后,然后再切换的时候进行局部卸载);v-show是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留;
4、性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;
频繁切换,使用v-show较好;运行时条件很少改变,则使用v-if较好
4、v-if 和 v-for 优先级
v-for 的优先级要比 v-if 的优先级高
vue.js 源码种10997行
if (e1.staticRoot && !el.staticProcessed) {
return genstatic(el,state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
}else if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el1.if && !el.ifProcessed) {
return genIf(el, state)
}else if (el.tag === 'template' &&!el.slotTarget && !state.pre) {
return genchildren(el, state) || 'void 0'
}else if (el.tag === 'slot') {
return genSlot(el, state)
} else {'...'}
v-if 、v-for 不要写在同一个节点上,性能很差,如要配合使用,v-if 写在父节点上
5、props 和 data优先级
源码体现 :props ==>methods ==> data ==> computed ==>watch
6、Vue组件通讯
-
父传子:props(不可修改)、this.$parent(可修改)、依赖注入(向下多级也可接收)、插槽
-
子传父:$emit、插槽、this.$children
-
兄弟通信:$emit、bus.js(新建bus工具类,利用 $emit 和 $on)、Vuex
-
跨级通信:Bus、Vuex、provide/inject API、$attrs/$listeners
父传子:props
//父:
<Son :msg="str"></Son>
//子:
props:{
msg: {
type:String, // 声明类型
require:true, //声明是否必需
default:'default' // 声明默认值
},
}
父传子:插槽
//默认插槽
父:father.vue
<son>你好</son>
子:son.vue
<div>
...
<slot/> //不同父组件,传递不同值,展现不同结果
...
</div>
//具名插槽
父:father.vue
<son>
<div slot='one'></div>
<div slot='two'></div>
</son>
子:son.vue
<div>
...
<slot name='one'/>
<slot name='two'/>
...
</div>
//作用域插槽(vue2.6之后出现)
父:father.vue
<son>
<template v-slot:one>
<div></div>
</template>
<template v-slot:two>
<div></div>
</template>
</son>
子:son.vue
<div>
...
<slot name='one'/>
<slot name='two'/>
...
</div>
子传父:插槽
//父:father.vue
<son>
<template scope='params'>
<div>{{params.one}}</div>
</template>
<vue slot-scope='params'>
{{params.two}}
</div>
</son>
//子:son.vue
<div>
...
<slot :one='one' :two='two'/>
...
</div>
one:'str'
two:[ {a:4} ]
子传父:$emit
//子:
<button @click="cName">改变父组件的name</button>
methods: {
cName(){
this.$emit( 'hChange', 'Jack')
//触发父组件中事件并传参
}
}
//父:
<child @change="hChange"></child>
methods:{
hChange(option) {
this.name = option
}
}
兄弟间通信:$emit
//创建bus作为中转
import Vue from "vue" ;
export default new Vue;
//A组件
<button @click='btn'>按钮</button>
return { Str:"数据" }
methods:{
btn(){
bus.$emit( 'selectItem', this.Str )
}
}
//B组件
created(){
bus.$on( 'selectItem' ,(val)=>{
console.log( val );
})
}
-
自定义事件:子向父通信
在父组件中,为子组件绑定一个自定义事件,可以用@也可以用$on,并且回调函数存在于父组件中。在子组件中用$emit触发自定义事件,$emit第一个参数是事件名,第二个参数是要传递的参数。
-
vuex:任意组件间的通信
数据会存放在state里面,可以在actions中发送ajax请求数据,在mutations中可以对数据进行处理。所有的组件都可以通过mapState拿到state中的数据。
-
全局事件总线:任意组件间的通信
在main.js入口函数中,实例化vue时,在beforeCreate中注册全局事件总线$bus,那所有的组件实例对象都可以访问到$bus里的数据。
-
消息订阅与发布:任意组件间的通信
7、computed methods watch
computed:计算属性,有缓存,其自定义的方法“xx”有返回值,可以直接{{xx}}
使用,无需data-return返回,实时计算,多个属性影响一个建议使用computed
methods:方法属性,无缓存,进入页面就会触发,允许html{{ xx()}}
方式执行
watch:监听属性,需要监听属性“xx”,以方法名的形式写入,参数newVal,oldVal等,当数据或路由发生改变时才会触发,单个属性影响一个使用watch
8、双向数据绑定原理
通过Object.defineProperty劫持数据发生的改变,如果数据发生改变了(在set中进行赋值的),触发update方法进行更新节点内容“{{str}}” ,从而实现了数据双向绑定
9、常用方法
-
ref
//获取dom节点 <div ref='box'></div> this.$refs.box
-
v-bind
动态绑定,简写【:】,单向绑定
-
v-model
双向绑定
-
v-on
组件事件监听器,简写【@】
-
v-slot
简写【#】
10、keep-alive
缓存组件,使用keep-alive会多俩个生命周期(activated、deactivated),提升性能
11、nextTick
用于获取最新dom的数据
当dom更新完毕,用于执行内部代码。使用普通方法可能获取到的值不是最新的,这是vue底层渲染机制导致的。nextTick() 通过异步的方式等待所有dom和数据更新完毕,再去获取值,保证获取到最新的值
使用场景:插件等。如new Swiper插件,获取当前元素的宽度或者高度,等dom都加载完毕再去获取宽度和高度
12、diff 算法
当数据发生变化时,vue更新节点
渲染真实DOM的开销是很大的,修改了某个数据,直接渲染到真实dom上会引起整个dom树的重绘和重排,如何只更新修改的那一小块dom而不更新整个dom,diff算法
先根据真实DOM生成虚拟DOM,当虚拟DOM某个节点的数据改变后会生成一个新的 vue节点,然后 新节点 和老节点 作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使 老节点 的值为 新节点
diff 的过程就是调用名为 patch
的函数,比较新旧节点,一边比较一边给真实的DOM打补丁
Vue的diff算法是对新旧两条虚拟DOM进行逐层比较并更新真实DOM
diff算法是平级比较,不考虑跨级的情况,采用 深度递归 + 双指针 的方式进行比较
-
先比较是否是相同节点
-
如果是相同节点比较属性(key、tag、input->type),并复用老节点
-
然后比较子节点,以先对比两边,再交叉对比,再乱序对比的方式进行比较(头头、尾尾、头尾、尾头、乱序)
13、打包上线
修改路径:修改css、js资源访问路径,默认为“/”,修改为“./”,例vue-cli修改方式:
//根目录新建vue.config.js文件 module.exports = { publicPath: './', //修改默认路径 devServer:{ //设置propos代理,开发阶段解决跨域 propos:'后端路径' } }
import axios from 'axios' export default { $axios(options) { let apiUrl = null; if (process.env.VUE_APP_ENV == 'dev') { apiUrl = options.url; } else { apiUrl = process.env.VUE_APP_BASE_API + options.url; } return axios({ url: apiUrl }) } }//判断开发生产环境
前端自测项目采用hash模式,url路径默认带“#”字符
打包上线采用history模式 ,不带“#”,会出现空白页,告知后端需做重定向处理
Vue全家桶
1、Vue-Cli 构建
创建项目:vue2、vue3
打包项目:...
2、Vue-Router 路由
路由模式:history、hash
1、表现形态不同 history : http: / / localhost:8080/ about hash: http:/ / localhost :8080/# / about(端口号后)
2、跳转请求( http: / / localhost:8080/id===>不存在的路径“id”) history 发送请求(404) hash不会发送请求
3、打包后前端自测要使用hash,如果使用history会出现空白页
this.$router.push({
name:“路由名字”,
params:{},
query:''
})
name 属性指定了目标路由的名称。定义路由时给路由配置对象设置的 name 属性。 params 属性用于传递路由参数,可以是一个对象,包含要传递的参数键值对。这些参数将被包含在 URL 中,可以在目标页面通过 $route.params 来获取。 query 属性用于传递查询参数,可以是一个字符串。这些参数将以查询字符串的形式附加在 URL 后面,可以在目标页面通过 $route.query 来获取。
params 和 query 都是在路由中传递参数的方式,但它们有一些区别,主要体现在以下几个方面:
传递方式:
-
params:通过 URL 的路径来传递参数,例:/users/123,以路径的形式体现
-
query:通过 URL 的查询字符串来传递参数,例:/users?id=123,以键值对和字符串的形式体现
参数格式:
-
params:通常使用对象形式传递参数,可以包含多个键值对。参数将直接作为 URL 的一部分,用于表示资源的唯一标识或者路由层级关系。
-
query:通常使用字符串形式传递参数,以查询字符串的形式附加在 URL 后面。参数是以键值对的形式存在,用于传递额外的信息、过滤条件等。
使用场景:
-
params:适合用于表示动态路由和页面间的层级关系。例如,在一个博客应用中,/posts/:id 可以表示不同文章的路由,其中 :id 可以作为 params 传递具体的文章编号。
-
query:适合用于传递额外的、可选的参数,如搜索关键字、过滤条件、排序方式等。例如,/search?q=apple&category=fruits 可以通过 query 传递搜索关键字和分类。
以对象方式传参时,如果我们传参中使用了params,只能使用name,不能使用path,如果只是使用query传参,可以使用path 。
路由跳转
...
路由传值
-
显式
http://localhost:8080/about?a=1
传:
this.$router.push({
path: '/about',
query: { a: 1 }
})
接: this.$route.query.a
-
隐式
http://localhost:8080/about
传:
this.$router.push({
name'About',
params:{ a: 1 }
})
接: this.$route.params.a
路由导航守卫
1、全局
beforeEach、beforeResolve、afterEach
2、路由独享
beforeEnter
3、组件内(少用,维护成本高)
beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
3、Vuex 状态管理
Vue状态管理工具,采用集中式存储管理应用的所有组件的状态
Vuex属性
State、Getters、Mutations、Actions、Modules
-
State-->data(数据)
-
Getters-->Computed(计算)
-
Mutations-->Methods(方法),同步
-
Actions-->Mutations,可写方法,提交的参数不同。提交的是Mutations,而不是直接变更状态,异步方法写在这里
-
Modules 把以上4个属性再细分,让仓库更好管理。建立不同的js文件,对不同类别的数据,方法等进行分类,然后引入到同一个js当中
Vuex是单向还是双向数据流
单向数据流,Vuex能被其他组件使用,不能被改,只能getter,无Setter
Vuex中的mutaitons和actions区别
-
mutaitons:都是同步事物
-
actions: 可以包含任意异步操作
写入方法,在调试中,使用浏览器Vue插件查看数据变化可知
Vuex如何做持久化存储
本身无法持久化存储,解决方法:
-
localStorage
-
插件(底层localStorage,更方便)
4、Axios 请求
请求
请求封装
请求拦截
请求优化
Vue操作
1、Axios和进度条
npm install axios@0.21.1
npm install nprogress@0.2.0
/*
对axios进行二次包装
1. 配置通用的基础路径和超时
2. 显示请求进度条
3. 成功返回的数据不再是response, 而直接是响应体数据response.data
4. 统一处理请求错误, 具体请求也可以选择处理或不处理
*/
import axios from 'axios'
import NProgress from 'nprogress'
// 引入进度条样式
import 'nprogress/nprogress.css'
// 配置不显示右上角的旋转进度条, 只显示水平进度条
NProgress.configure({ showSpinner: false })
//创建axios实例[创建出来的实例即为axios,只不过可以配置一些东西]
const service = axios.create({
baseURL: "/api", // 基础路径
timeout: 15000 // 连接请求超时时间
})
//请求拦截器:在发请求之前可以检测到,可以干一些事情
service.interceptors.request.use((config) => {
// 显示请求中的水平进度条
NProgress.start()
// 必须返回配置对象
return config
})
//响应拦截器:服务器的数据已经返回了,可以干一些事情
service.interceptors.response.use((response) => {
// 隐藏进度条
NProgress.done()
// 返回响应体数据
return response.data
}, (error) => {
// 隐藏进度条
NProgress.done()
// 统一处理一下错误
alert( `请求出错: ${error.message||'未知错误'}`)
// 后面可以选择不处理或处理
return Promise.reject(error)
})
//对外暴露二次封装的axios
export default service
2、前端代理解决跨域
根目录下的 vue.config.js 中配置,proxy 为通过新建代理服务解决跨域问题 封装 axios 时设置 baseURL 为 api ,所以所有的请求都会携带 /api
vue.config.js
module.exports = {
//关闭eslint
lintOnSave: false,
devServer: {
// true 则热更新,false 则手动刷新,默认值为 true
inline: false,
// development server port 8000
port: 8001,
//代理服务器解决跨域
proxy: {
//会把请求路径中的/api换为后面的代理服务器
'/api': {
//提供数据的服务器地址
target: 'http://xxx',
}
}
}
}
3、全局组件
全局的配置都需要在main.js中配置,在任一页面中直接使用,不需要导入声明
import Home from '@/pages/Home';
Vue.component(Home.name,Home);
4、代码改变页面自动刷新
根目录下vue.config.js文件设置
module.exports = {
//关闭eslint
lintOnSave: false,
devServer: {
// true 则热更新,false 则手动刷新,默认值为 true
inline: true,
port: 8001,
}
}
5、undefined细节
访问undefined的属性值会引起红色警告,可以不处理,但是要明白警告的原因。
赋值的时候,在后面加一个||条件。即当属性值undefined时,会返回||后面的数据,这样就不会报错。 如果返回值为对象加||{},数组:||[ ]
6、失焦事件
blur与change事件在绝大部分情况下表现都非常相似,输入结束后,离开输入框,会先后触发change与blur,唯有两点例外。
(1) 没有进行任何输入时,不会触发change 在这种情况下,输入框并不会触发change事件,但一定会触发blur事件。在判断表单修改状态时,这种差异会非常有用,通过change事件能轻易地找到哪些字段发生了变更以及其值的变更轨迹。
(2)输入后值并没有发生变更 这种情况是指,在没有失焦的情况下,在输入框内进行返回的删除与输入操作,但最终的值与原值一样,这种情况下,keydown、input、keyup、blur都会触发,但change依旧不会触发。
@click.prevent
,它可以阻止自身默认事件的执行。