Vue框架的优点:
-
轻量级框架
-
双向数据绑定。也即响应式数据绑定。vue.js会自动响应数据变化。
-
组件化。Vue.js通过组件,把一个单页应用中的各种模块拆分到一个一个单独的组件(component)中。使用步骤:
- 先在父级应用中写好各种组件标签(占坑),
- 如果要传参数的话,在组件标签中写好要传入组件的参数(就像给函数传入参数一样,这个参数叫做组件的属性)
- 然后再分别写好各种组件的实现(填坑)。
-
视图、数据、结构分离。也就是MVVM结构。可以让数据的更改更简单。
-
采用虚拟DOM。
虚拟DOM,是一个轻量级的JavaScript对象。 工作流程:
- 根据数据生成虚拟DOM =>
- 根据虚拟DOM生成真实DOM =>
- 数据改变 =>
- 生成新的虚拟DOM树 =>
- 对比(和上一轮的虚拟DOM) =>
- 虚拟DOM树积累一定量的改变,将改变一次性应用到真实DOM上
基本知识
基本
el和data的两种写法
//写法1
new Vue({
el: '#root'
data:{
name:'sxy'
}
});
//写法2
const v = new Vue({
data(){ //ES6对象内函数的简写,省略 : function
return{
name:'sxy';
}
}
});
v.$mount('#root');//更灵活,比如可以异步地挂载。$mount是Vue原型对象上的方法
计算属性与监视
- 定义:要用的属性不存在,要通过已有的属性计算得来
- 计算属性computed中的get
- 当读取计算属性值时,get就会被调用,且get的返回值就是计算属性值
- 初次读取计算属性值时,以及计算属性值所依赖的属性值发生变化时。get会被调用
computed和watch之间的区别: 1.computed能完成的功能,watch都可以完成。 2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
MVVM模型
View - ViewModel - Model
DOM结构 - Vue实例 - 数据对象
DOMListeners 工具监听View中 DOM的变化,并会转给Model
Data Bindings 工具监听Model数据变化,并将其更新给View
数据代理
通过一个对象代理对另一个对象中属性的操作
//最简单的一个数据代理
let obj = {x:100};
let obj2 = {y:200};
Object.defineProperty(obj2, 'x', {
get(){
return obj.x;
},
set(value){
obj.x = value;
}
})
Vue内部指令
v-on: 绑定事件
v-on:click="function"简写 @click="function"
在Vue中,阻止默认行为除了JS原生的e.preventDefault()方法外,还可以 @click.prevent="function"。也就是事件修饰符
事件修饰符
常用:prevent 阻止默认事件、stop 阻止时间冒泡、once 事件只出发一次
不常用:capture 使用事件的捕获模式、self 只有event.target是当前操作的元素时才触发事件、passive 事件的默认行为立即执行,无需等待事件回调执行完毕
键盘事件
e.keyCode //每个键盘对应的编码(JS原生)
<input type="text" placeholder="按下回车" @keyup.enter="function"> //.enter 即按下回车键后执行
- Vue提供的常用的几个按键别名:
回车 enter
删除 delete (捕获“删除”和“退格”键)
推出 esc
空格 space
换行 tab
上 up、下 down、左 left、右 right
- Vue为提供别名的按键,可以使用按键原始的key值绑定,要转为kebab-case
v-model
不同输入框 v-model 不同的使用方法
<div id="root">
<form>
<!-- 普通的input框输入即value -->
账号:<input type="text" v-model="account">
密码:<input type="password" v-model="password">
<!-- 单选框,需要设定value -->
性别:
男 <input type="radio" name="sex" v-model="sex" value="male">
女 <input type="radio" name="sex" v-model="sex" value="female">
<!-- 勾选框(多选),hobby初始值必须配制成数组,否则勾选框会连坐 -->
爱好:
学习 <input type="checkbox" v-model="hobby" value="study">
吃饭 <input type="checkbox" v-model="hobby" value="eat">
睡觉 <input type="checkbox" v-model="hobby" value="sleep">
<!-- 下拉框 -->
城市:
<select v-model="city">
<option value="bj">北京</option>
<option value="cd">成都</option>
<option value="hz">杭州</option>
</select>
<!-- 多行输入,和单行输入一致 -->
其他信息:
<textarea v-model="other"></textarea>
<!-- 虽然也是checkbox,但不需要数组,直接收true还是false就行 -->
<input type="checkbox" v-model="agree">
阅读并接受用户协议
</form>
</div>
<script>
new Vue({
el:'#root',
data:{
account:'',
password:'',
sex:'',
hobby:[],
city:'',
other:'',
agree:''
}
})
</script>
v-model 修饰符
v-model.number。收集的数据直接转成number类型,一般和输入框的原生apitype="number"配合使用。后者用于筛选是否是数字v-model.lazy。一般用于多行输入,默认时是实时收集的。添加修饰符后只有再失去焦点才会收集数据v-model.trim。会自动删除收集数据开头和结尾的空格。
v-bind
数据的单向绑定。
v-for时的:key.
v-for
<body>
<div id="root">
<ul>
<li v-for="(student, index) in students" :key="index">
{{student.name}} {{student.age}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
students:[
{id:'01', name:'sxy', age:'23'},
{id:'02', name:'chr', age:'24'},
{id:'03', name:'xys', age:'25'},
]
}
})
</script>
v-if v-else v-show
两者都会导致页面的重绘和重排,但v-show只是改变dom的css,而v-if控制的是添加和删除dom,所以v-if在重绘重排前还进行了添加或删除dom元素的操作。
其它
v-text
<div id="root">
<div>{{name}}</div> 差值语法
<div v-text="name"></div> v-text写法
</div>
<script>
new Vue({
el:'#root',
data:{
name:'sxy'
}
})
</script>
差值语法更灵活。v-text会替换整个标签中的内容,无法添加。但是差值语法可以。
v-html
相比于v-text,v-html支持结构的解析。即可以填写html标签。
<div id="root">
<div v-text="<h3>name<h3>"></div> v-html写法
</div>
v-cloak
- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删除掉v-cloak属性
- 配合css就可以在Vue实例还没有创建时,比如网速太慢。不让未解析的比如差值语法直接显示出来。
v-once
- 和v-cloak一样都是一种属性。添加v-once属性的节点在初次动态渲染之后,将会被视为静态内容。
- 之后数据再发生任何改变都不会造成结构的更新。常用于性能优化。
自定义指令
函数式写法和对象式写法
<body>
<div id="root">
<!-- 使用自定义指令实现数字乘以10 -->
<div v-multiple="n"></div>
<hr>
<!-- 使用自定义指令实现加载后自动获取输入框焦点,并且还具有v-bind的功能 -->
<input type="text" v-fbind="n">
</div>
</body>
<script>
new Vue({
el:'#root',
data:{
n:1,
},
directives:{
//函数式写法
//接收两个参数,分别是使用自定义指令的DOM标签和绑定对象。绑定对象里面包含value,即自定义指令的值。
//函数会在以下两个时间点被调用。指令和元素被成功绑定时(即刚开始的时候)。模版重新解析时。
multiple(element, binding){
element.innerText = binding.value * 10;
},
//对象写法在需要更精确地卡时间节点时使用。
//其中有三个常用的生命周期函数
fbind:{
//指令和元素成功绑定时(第一次)
bind(){
},
//指令所在元素被插入页面时
inserted(){
},
//指令所在模版被重新解析时
update(){
}
}
}
})
</script>
生命周期
特殊的生命周期
keep-alive
-
会缓存不活动的组件的状态
-
作用:避免多次重复渲染降低性能
-
配置项
- include:字符串或正则表达式,只有匹配名称的组件会被缓存
- exclude:字符串或正则表达式,任何匹配的组件都不会被缓存
- max:数字,最多可以缓存多少组件实例
-
使用方法
- 将router-view组件用
<keep-alive></keep-alive>包裹住,在里面写配置项 - 也可以在路由配置中为组件添加meta元信息,配置项为
keepAlive: true. 然后再在<keep-alive></keep-alive>里用v-if判断
- 将router-view组件用
nextTick
语法:this.$nextTick(回调函数)
作用:在下一次DOM更新结束后执行其指定的回调
nextTick 是微任务还是宏任务
nextTick优先是微任务
如果当前运行环境不支持微任务的话,还是会选择宏任务的
和updated()的区别
- 使用场景不同。有时候只需要下一次更新DOM时触发。回调函数写在
updated()里会导致每次数据改变、DOM更新,回调函数都会被触发。 - 触发顺序不同。响应式数据发生变化时,Vue会立刻将更新的全部微任务按照顺序(
beforeUpdate() update() updated())插入到微任务队列中。之后Vue对虚拟DOM树进行更新。之后虚拟DOM树和真实DOM树进行对比更新。但此时浏览器不会立即渲染更新后的真实DOM树,而是会先清空微任务队列。在顺序执行微任务时,在updated()执行结束后会立即执行nextTIck(), 即便后面还有别的微任务。
组件
基本知识
实现应用中局部功能代码的资源的集合。复用及嵌套。相较于模块化,依赖关系不混乱,复用率高。
定义(创建)组件 => 注册组件 => 使用组件(写组件标签)
- 定义组件
使用Vue.extend创建(可以省略),和创建Vue实例时几乎一样。(组件是可复用的 Vue 实例)区别是:
- 不写el。最终所有的组件都要经过一个vue实例的管理,由vue实例的el决定挂载在哪个容器
- data必须写成函数。如果写成对象形式,数据存在引用关系。写成函数,实现一个闭包。这样组件中的数据就是私有的。对象的形式只能是公有
- 每个组件必须有且只有一个根元素,如用一个div包裹这些子元素
- 注册组件
- 局部注册。创建vue实例的时候传入components选项,在其中进行注册。k:v形式
- 全局注册,Vue.component() 注册,k:v形式
VueComponent
- 组件本质上是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
- 只要写了组件标签,Vue解析时会自动创建对应组件的实例对象。即自动执行 new VueComponent(options)
- 每次使用Vue.extend,都会生成一个新的VueComponent
- 组件创建时,data函数、methods函数、watch函数... 它们的this指向都是VueComponent实例对象。
- VueComponent.prototype.proto === Vue.prototype. 正常情况是会直接指向Object对象的,这样做是为了让组件实例可以访问到Vue原型上的属性方法
组件间传参
props配置项
让组件接受外部传过来的数据。通常用于父向子组件通行。但也可以通过父组件给子组件传递函数类型的props实现子给父传递数据。
使用方法:在组件实例中加一个配置项props用于接收参数,然后在组件标签里用 v-bind:绑定参数。
用props配置项实现子给父组件传参方法:父组件准备一个回调函数传给子组件,子组件调用。代码演示见下方自定义事件
自定义事件
自定义事件
emit, 可以实现子给父通信,和兄弟组件通信。
使用方法:在父组件使用子组件时使用(v-on, @)绑定一个自定义事件。也就是给子组件的组件实例绑定自定义事件。
在子组件内触发自定义事件 this.$emit('自定义事件名', 数据) 。
自定义事件的回调函数在父组件中,在这个回调函数里就可以接收子组件传的数据了.
子给父传参,props和自定义事件方法代码演示
两者的相同:父组件定义一个回调函数,交给子组件。子组件调用。
两者的区别:父组件传递回调函数的方法和子组件调用的方法不同。
- props:子组件专门使用props配置项,接收来自父组件传递的回调函数。调用时直接调用
- 自定义事件:父组件定义的回调函数没有传给子组件,而是直接绑定给子组件作为一个自定义事件的回调。所以子组件通过$emit触发绑定在子组件实例上的自定义事件,此时回调函数触发。
父组件
<template>
<div>
<Child1 v-bind:getChildParams1 = "getChildParams1"/> props方法实现子父传参
<Child2 v-on:getChildParams2="getChildParams2"/> 自定义事件方法实现子父传参(写法一)
<Child3 ref="test"/> 自定义事件方法实现子父传参(写法二)
</div>
</template>
<script>
import Child from '...'
export default {
...,
methods:{
getChildParams1(params){
console.log(params);
},
getChildParams2(params){
console.log(params);
}
},
/* 写法二,更灵活,可以异步地绑定自定义事件 */
mounted(){
this.$refs.test.$on('getChildParams2', this.getChildParams2)
},
}
</script>
子组件
<template>
<button @click="sendParams1">给父组件传参(props方法)</button>
<button @click="sendParams2">给父组件传参(自定义事件方法)</button>
</template>
<script>
export default{
...,
data(){
return{
param: '参数';
}
}
props:['getChildParams'], //这里就是区别所在
methods:{
sendParams1(){
this.getChildParams1(this.param)
},
sendParams2(){
this.$emit('getChildParams2', this.param)
},
}
}
</script>
全局事件总线
$bus 全能。
使用方法:
安装全局事件总线:
new Vue({
...
beforeCreated(){
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
}
})
使用事件总线:和自定义事件类似
插槽
父给子组件,在子组件中使用<slot>标签接收父组件的数据
- 默认插槽,直接在子组件
<slot>标签中写入的就是默认值。父组件传值就会覆盖 - 具名插槽,就是给
<slot>标签加了name属性
其它
vuex
pubsub-js:vue 几乎不用,react 常用 全能
路由
基础知识
- 路由 route 是一组key(路径)-value(组件)的对应关系
- 多个路由由路由器 router 管理
- 路由存在的目的是为了实现单页面应用(single page web application)SPA。
基本使用代码演示
//新建文件专门用于保存整个应用的路由器:src/router/index.js
import VueRouter from 'vue-router'
//引入组件
import About from '...';
import Home from '...';
//创建并暴露一个路由器
export default new VueRouter({
routes:[
{
path: '/about',
component: About
},
{
path:'/home',
component: Home
}
]
})
//main.js中配置
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
//引入写好的路由器
import router from '...'
Vue.use(VueRouter)
new Vue({
el: '#app',
router:router
})
<router-link to="/about">A</router-link>
<router-link to="/home">H</router-link>
<!-- 指定组件的呈现位置 -->
<router-view></router-view>
其它注意事项
路由组件和一般组件
- 使用方法不同,一般组件直接用组件标签放置。路由组件通过指定标签位置
- 一般在项目中,components文件夹放置一般组件,pages文件夹放置路由组件
路由组件的生命周期
被切换走的路由组件会被销毁,在下次使用时会重新创建、挂载。keep-alive可以解决这个问题造成的性能损耗。
路由组件的route 和 router 属性
$route 属性里面存储自己的路由信息
$router 属性存储整个路由器的信息
每个路由组件的route属性都是独有的不相同。但router属性是被所有路由组件公用的。
嵌套路由
也叫多级路由。一级路由、二级路由、三级路由.....
export default new VueRouter({
routes:[
{
path: '/about',
component: About
},
{
path:'/home',
component: Home,
//二级 路由
children:[
{
path:'news',
component:News,
},
{
path:'message',
component:Message,
}
]
}
]
})
<!-- 在Home.vue中 -->
<router-link to="/home/news">N</router-link>
<router-link to="/home/message">M</router-link>
<!-- 指定组件的呈现位置 -->
<router-view></router-view>
路由传参
query 参数:/home?k=v&kv=,不需要占位。 通过问号分隔,多组参数通过&分隔。 此时路由组件的$route属性中的query参数就有值了。
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`"></router-link>
<!-- 写法二 对象式写法 -->
<router-link :to="{
path:'...',
query:{
id:m.id,
title:m.title
}
}"></router-link>
params 参数:属于路径的一部分,在配置路由的时候需要占位.
<router-link :to="`/home/001/001"></router-link>
export default new VueRouter({
routes:[
{
//这里path需要改。即添加占位
path:'/home/:id/:title',
component: Home,
}
]
})
路由导航
声明式路由导航
就是上面演示的<router-link></router-link>标签
router-link的replace属性
控制路由跳转时操作浏览器历史记录的模式。push模式是追加记录,replace是替换当前记录。
默认是push模式,历史记录依次回退。<router-link :replace="true">开启replace模式,历史记录不能回退
编程式路由导航
声明式导航,<router-link></router-link>标签实际渲染时会转换成<a>标签。有时需要用别的标签实现路由跳转。以及实现异步。
<button @click="pushTest(m)">编程式路由导航(push)</button>
<button @click="replaceTest(m)">编程式路由导航(replace)</button>
<script>
export default {
...,
methods:{
pushTest(m){
this.$router.push({
path:"...",
query:{
id:m.id,
title:m.title
}
})
},
replaceTest(m){
this.$router.replace({
path:"...",
query:{
id:m.id,
title:m.title
}
})
}
}
}
</script>
路由独有的两个生命周期
keep-alive
-
会缓存不活动的组件的状态
-
作用:避免多次重复渲染降低性能
-
配置项
- include:字符串或正则表达式,只有匹配名称的组件会被缓存
- exclude:字符串或正则表达式,任何匹配的组件都不会被缓存
- max:数字,最多可以缓存多少组件实例
-
使用方法
- 将router-view组件用
<keep-alive></keep-alive>包裹住,在里面写配置项 - 也可以在路由配置中为组件添加meta元信息,配置项为
keepAlive: true. 然后再在<keep-alive></keep-alive>里用v-if判断
- 将router-view组件用
生命周期函数(activated)
使用keep-alive缓存组件后,mounted 和 destroyed 生命周期函数失去了原本的作用(因为组件不会被销毁、重新挂载了)。
- activeted(). 组件被激活了。
- deactivated(). 组件失活了。
路由命名
什么时候需要为路由配置name属性(命名)
路由重定向
路由守卫
全局前置路由首位
-
触发
- 初始化时(不需要进入)会被调用
- 每次路由切换前会调用
-
参数
- to 原本要去的地方
- from 从哪个组件来
-
判断是否放行
- 根据to, from的path属性判断是否放行
- 大量路由组件需要进行守卫时,可以路由信息中配置元信息meta。判断时直接判断是否具有meta属性
const router = new VueRouter({
routes:[
{
...
meta:{guard: true}
}
]
})
//配置全局前置路由守卫
router.beforeEach((to, from, next)=>{
if(to.path == '/home/news' || to.path == '/home/message'){
//if(to.meta.guard) 元信息判断法
if(localStorage.getItem('id') == '管理员'){
//如果身份验证通过,放行
next();
}else{
alert('...')
}
}else{
//其它路由不需要守卫
next();
}
})
全局后置路由守卫
-
触发
- 初始化时(不需要进入)会被调用
- 每次路由切换后会调用
-
参数
- 没有next
router.afterEach((to, from)=>{
document.title = to.meta.title || '默认名称';
})
独享路由守卫
beforeEnter()配置在路由信息中,其它逻辑和全局前置路由守卫一致
const router = new VueRouter({
routes:[
{
...
meta:{guard: true},
beforeEnter:((to, from, next)=>{
if(to.path == '/home/news' || to.path == '/home/message'){
if(localStorage.getItem('id') == '管理员'){
//如果身份验证通过,放行
next();
}else{
alert('...')
}
}else{
//其它路由不需要守卫
next();
}
})
}
]
})
组件路由守卫
其它
Vuex数据管理库
vuex 状态管理库
vuex 是官方提供的一个插件,集中式管理项目中组件共用的数据。如果是小项目,不需要使用 vuex。如果是大项目,组件多、数据多,数据维护难度大,使用 vuex。
- state:存储数据的地方。初始化
- mutations:修改数据的唯一方法
- actions:可以异步地执行mutation。(AJAX请求)
- getters:计算属性
- modules:将store分割成小的modules
使用方法:在vue的mounted函数中,使用this.$store.dispatch派发Vuex的actions。actions中发送AJAX请求,异步地调用mutation。mutation用服务器返回的数据修改state中的数据。然后在组件computed计算属性中捞取数据
基本使用方法:
import Vue from "vue";
import Vuex from "vuex";
//需要使用插件一次
Vue.use(Vuex);
const state = {
count: 1
};
const mutations = {
ADD(state){
state.count++;
}
};
const actions = {
add({commit}){
commit("ADD");
}
};
const getters = {};
//Vuex是一个对象。它包含一个Store方法,也是一个构造函数,用于初始化Vuex仓库。
//对外暴露Store类的一个实例
export default new Vuex.Store({
state,
mutations,
actions,
getters
});
//在组件中
import {mapState} from 'vuex';
...
computed:{
...mapState(['count'])
},
methods:{
add(){
//派发action
this.$store.dispatch('add');
},
},
Vuex和Pinia的区别
-
pinia 没有 mutations,actions的使用不同(pinia支持同步异步),getters的使用是一致的
-
pinia 没有总出口全是模块化,需要定义模块名称,当多个模块需要协作的时候需要引入多个模块,vuex是有总出口的,在使用模块化的时候不需要引入多个模块
-
pinia 在修改状态的时候不需要通过其他api,vuex需要通过commit,dispatch去修改
\