Vue2 学习笔记

188 阅读6分钟

前引:写这篇文章的目的是为了让自己回头看的时候能快速捡起当初的一些知识,同时也分享出来共大家学习讨论

一、vue的介绍

1.引导

框架和库有什么区别?

框架改变了编码思想,而库不改变只是多个函数封装的集合

vue的编码思想

面向数据,自身只考虑数据vue会帮你进行dom操作

2.前端MVC

image.png

封装好数据和视图,根据业务逻辑调用这些封装来完成事件

3.vue的MVVM

image.png

  • Model是一个个{}对象存放着数据
  • 简单来说VM层将数据库里的数据和绑定在视图上的数据进行同步

二、数据绑定

1.数据绑定

接收的数据类型

number/string/boolean/array/json/undefined/null/NaN

  • undefined/null/NaN 不渲染
  • 其他默认转字符
  • 视图中的{{name}}找不到匹配项,则{{name}}整体转为空字符串''

绑定方式

  • 插值 {{name}}
  • 指令 v-text="绑定内容" / v-html="绑定html类型内容"
  • 属性 :src="url"(让src属性的属性值可以替换成绑定的url) / :[proname]="数据"(将属性名替换成绑定的proname的值)

2.列表渲染

语法

<li v-for="值 in 数据">{{值}}</li>

<li v-for="值 of 数据">{{值}}</li>

<li v-for="(值,索引) in 数组">{{值}}/{{索引}}</li>

<li v-for="(对象,索引) in|of 数组">{{对象.key}}/{{索引}}</li>

<li v-for="(值,键) in 对象">

<li v-for="(数,索引) in 数字">

<li v-for="(单字符,索引) in 字符">

3.条件渲染

语法 v-show v-if

对比

image.png v-show通过给dom添加display:none来让元素脱离文档流但dom依赖存在,而v-if是增删dom性能消耗大

思考

代码扁平化是什么?

减少模块层次,尽量平铺

为啥指令的形式会被xss攻击?

可以注入html内容引入不同功能

三、事件绑定

1.语法

v-on

v-on:click='函数名' @ @click='函数名'

2.vm中的

添加个methods:{},里面放函数方法,推荐简写为=>函数名(){}

3.特殊

  • 不传参默认会传递事件对象
  • 使用箭头函数this会丢失

四、双向绑定

1.场景

可以用用户产生数据的地方

2.语法

v-model
v-model='数据名'

3.扩展

原生js来实现双向绑定
vue2:Object.defineProperty
vue3:proxy和反射

五、非响应式情况

1.情况

  • 数组使用非变异方法
  • 修改数组的长度时
  • 修改数据的根索引时
  • 给对象添加不存在的属性时

2.解决

尽量避免这些情况

六、key的问题

1.产生原因

vue的虚拟dom算法是尽可能减少dom移动,所以就会用修补/重用相同类型的元素的情况

vm中对dom的操作是先对虚拟dom操作到最后才渲染到视图上

image.png

2.语法

:key=""

七、模板表示式

1.语法

{{num + 9}}

{{`${str} me`}}

{{str.split(' ')}}

{{bl ? '处' : '非处'}}

2.注意

不能使用编程语句 -> {{var a = 10}}

思考

如何理解事件对象?

操作视图时记录了一系列信息的对象

数组使用非变异方式返回的是新数组,只要接收新数组来覆盖原数组就能解决

八、计算属性

1.场景

需要对data中的数据重新计算的时候

2.语法

{{函数名}}
在vm中,computed:{函数名(){return...}}

3.注意

  • 其是缓存形式,访问data中的数据不变就调用缓存,变化后会再次执行
  • 当成属性用

4.computed VS method

image.png

九、属性检测

1.场景

需要在数据变化时执行异步或开销较大的操作时使用(因为计算属性时同步的)

2.语法

在vm中

watch:{

1.数据名:'method函数名' //数据名==data的key

2.数据名:函数体(newValue,oldValue){}

3.数据名:{

handler(newValue,oldValue){},

deep: true //深度检测 针对符合类型

immediate: true //首次运行

}

3.注意

  • 一般的浅操作可以使用,数据名:函数体(newValue,oldValue){}
  • 替换是浅操作,修改是深操作
  • 旧值将与新值相同,因为它们的引用指向同一个对象/数组,vue不会保留变更之前值的副本

4.计算属性 VS 函数 VS 属性检测

image.png

十、样式操作

1.语法

:class="数据|属性|变量|表达式"
:style="数据|属性|变量|表达式"

2.属性值类型支持

字符/对象/数组

<div :class="{active:true,t1:false}"></div>
<div :style="[{css属性名:值},{'xx-xx-xx':值}]"></div>

image.png

十一、指令

1.系统指令

概述

扩展了html语法功能,区别了普通html属性

v-pre

  • 使用vue语法可以不被解析,{{数据}}
  • 原生的<pre>标签还是会解析vue语法

v-once

只渲染一次数据,之后数据改变不重新渲染

v-cloak

防闪烁,防止vue加载过慢暴露出{{数据}}的格式,v-cloak在vue识别到后会将其删除,而刚开始属性选择器选中v-cloak属性将其标签隐藏

css:[v-cloak]{display:none}
html:<div v-cloak></div>

2.自定义指令

概述

指令是个函数|对象,用来操作dom,里面的this返回window

全局

html: v-指令名 / v-指令名="'str'"
js: Vue.directive("指令名",function(el,binding){})

局部

html:v-指令名 / v-指令名="'str'"
vm:directive:{ 指令名(el,binding){} }

image.png

image.png

image.png

3.要点

  • 可以设置在全局也可以设置局部
  • el 返回的是绑定的元素
  • binding记录了一些绑定元素的信息

4.拖拽指令

// 拖拽

Vue.directive("drag",function(el,binding){

el.onmousedown = function(ev){

// clientX 点击位置距离屏幕左端的距离,offsetLeft 元素距离屏幕左端的距离

// ev 是事件对象

// disX,disY 是鼠标点击点距离元素左边距和顶点的距离

let disX = ev.clientX - el.offsetLeft;

let disY = ev.clientY - el.offsetTop;

document.onmousemove = function(ev){

el.style.left = ev.clientX - disX + 'px';

el.style.top = ev.clientY - disY + 'px';

}

document.onmouseup = function(){

document.onmousemove = document.onmouseup = null;

}

return false;

}

})

思考

需要特定执行时期的函数,叫钩子函数?(对)

十二、axios

1.基本形态

axios.请求姿势(url,{配置}).then(成功回调(res)).catch(失败回调(res))

2.访问本地json

成功返回的数据

image.png

image.png

3.其他

将axios提到vue全局中使用
//main.js
import axios from 'axios';
Vue.prototype.axios = axios; (挂载到vue原型链上)

十三、跨域

1.概述

浏览器有同源策略,就是两个网站 协议/域名/端口号 都一致才能互相调用请求接收数据

2.允许跨域

image.png

3.解决方法

后端解决

部分接口允许

image.png

全部接口允许

image.png

前端解决

jsonp

浏览器装插件 正向代理

开发环境做反向代理

理解:本地服务器发送的请求被代理服务器截获包装后发向真实的服务器(将不同的域名转换成相同的)

image.png

配置步骤
1.在vue.config.js中配置代理服务器
2.在请求接口处取别名

image.png

image.png

思考

1. npm

  • -S:部署到服务器上也需要用到的包
  • -D:只在本地开发的时候要用到的包

2.如何理解node接口和普通http接口?

由不同的软件开发的接口

十四、构建环境

1.全局安装脚手架

npm install -g @vue/cli

2.初始化开发环境

vue create 目录

3.选择配置

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

4.运行

npm run serve

十五、组件

1.理解

需要复用的部分才拆出来变成组件

2.调用组件

<组件名></组件名>
<组件名 />

3.定义组件

<template> //html

</template>


<script> //js
    export default {
        // 选项
    }
</script>


<style> //css

</style>

4.注册组件

全局注册

在main.js中注册
import 组件变量名 from "...";
Vue.component("组件名",组件变量名);

局部注册

APP.vue中注册
import 组件变量名 from './components/组件文件名.vue';
components:{ //选项
组件名:组件变量名 //√
}

5.组件数据

限制

data选项必须是一个函数,且要有返回object,作用域独立

语法

data(){ return{} }

6.组件避讳

  • 组件名不可和html同名
  • 组件没有el选项,只有根实例存在el
  • 组件的模板一定要有根元素(就是template下只能有一个标签)
  • 组件的data是个函数,需要返回对象

7.书写风格

  • 组件变量名: 大驼峰(XxxXxx)
  • 组件名: 烤肉串(xxx-xxx) 或者 大驼峰(XxxXxx)
  • 组件文件名: 烤肉串(xxx-xxx) 或者 大驼峰(XxxXxx)

8.css规则

原理

是把每个组件的样式抽出来插入到网页中,顺序不定

解决办法

  • 自己重命名(用BEM风格)
  • 自动重命名(css模块化)<div :class="$style.box"> <style module>
  • 独立样式作用域(推荐)<style scoped> // 会自动给标签添加上个属性,变成属性选择器

其他

1.es6模块化的输入输出

输入 import a from './mod/a.js

输出 export default any 默认输出只导出一次

2.key === string

用不用引号括起来都行

3. 关掉eslint方法

image.png

思考

如何理解组件就是一个对象,需要暴露,输出 ?为啥对象就要暴露?这里的export是导出给main.js中vue实例,再返回回来,这样就只需要创建一次实例?

因为模块化开发每个组件都是独立都作用域,每个都要将自己做好的虚拟dom暴露出来给到APP.vue整个成完整的虚拟dom,再给到vue的根实例进行渲染出真实的dom

APP.vue下 <template>下只能有一个标签?

对的,规定

十六、生命周期

image.png

image.png

image.png

十七、组件通讯基础

1.父子

概述

父组件通过属性绑定将数据传给子,子用props接收

语法

父
<子:自定义属性="父数据"></..>

子
props:['自定义属性']

<div>
    {{自定义属性}}
</div>

注意

props是只读的,不推荐改

props命名:
props: ['postTitle']
<xx :post-title="..."

2.子父

概述

在父组件中给子组件标签添加自定义事件接收,子组件用特定方法传递

语法

<template>
    ..
    < @自定义事件="父方法"></..>
    ..
</template>

<script>
export default {
methods:{
    父方法(接受数据){处理}
    }
}
</script><script>
this.$emit('自定义事件',子.数据名)
</script>

3.集中管理

概述

把数据放到放到根实例的data中

语法

new Vue({
data:{ a:1 }
}

使用 $root.数据名

思考

image.png

为啥组件标签上用不了v-mode?感觉很多vue中可以给html添加的属性,无法作用于自定义的组件标签上

单向数据流只能通过父子、子父、集中式管理来传递数据,子组件可以绑定@input,再通过emit发送子组件获取的内容给父组件v-model绑定的数据

十八、插槽

1.作用

将dom或组件插入到另一个组件中

2.调用组件时传递

image.png

3.组件内部接收

image.png

默认内容当外部有插入东西时被覆盖

十九、ref

1.理解

相当于标签中的id,定位dom或组件,来进行一些操作

2.使用

this.$refs.

二十、第三方组件

1.单组件

在npm上找开发文档跟着操作

2.组件库

pc端、后台管理

  • element-ui 饿了么 √
  • iview 个人
  • ant design 蚂蚁金服 √

elementUI 安装

npm i element-ui-S

整体引入

//main.js 下

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);

引入自定义主题

把导入的css改成自己本地下载下来的

移动端、客户端

  • vant2 有赞 电商 √
  • mint-ui 饿了么
  • vue-material
  • muse-ui
  • VUX
  • cube-ui
  • vonic
  • Vue-Carbon
  • YDUI

vant2 安装 npm i vant@latest-v2 -S

引入

//main.js

import Vant from 'vant';
import 'vant/lib/index.css';
Vue.use(Vant);

引入自定义主题

//vue.config.js

css: {
loaderOptions: {
less: {
// 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
lessOptions: {
modifyVars: {
// 或者可以通过 less 文件覆盖(文件路径为绝对路径)@blue是主题色
hack: `true; @import "./src/assets/css/vant-theme.less";`,//vant官网copy下来修改后的less变量文件
                },
            },
        },
    },
},
//main.js

import Vant from "vant";
import "vant/lib/index.less";

注意

vant是采用375的设计稿,px转rem时需要设置一下

image.png

通用

  • bootstrap5/4
  • ameizi

其他

1.组件的思想

尽量把能控制的东西交给外面

2.修改组件样式依照下列顺序

1. 修改主题 *
2. 使用props 权重**
3. 添加 class/style ,影响的是组件template的根元素
4. 审查元素,查询相关样式名,修改编译后的样式 + scoped ***
5. 样式穿透

css解决: .a >>> .b { /* ... */ } 深度选择器
Sass解决: .a{ /deep/ .b{} }

3.自定义属性传值简写

  • 字符串:type="data"
  • 布尔值:border
  • 一般变量::laber="laberData"

二十一、路由

1.概述

用来SPA页面跳转(单页面跳转)

2.单页VS多页

image.png

  • 单页面模式:相对比较有优势,无论在用户体验还是页面切换的数据传递、页面切换动画,都可以有比较大的操作空间
  • 多页面模式:比较适用于页面跳转较少,数据传递较少的项目中开发,否则使用cookie,localstorage进行数据传递,是一件很可怕而又不稳定的无奈选择

3.基本使用

安装

npm i vue-router@3 -S(vue2)

引入注册

// src/main.js

import router from './plugins/router.js'
new Vue({
router
})

配置路由

// 引入vue
import Vue from 'vue';

// 引入路由包
import VueRouter from 'vue-router';

// 安装插件到vue上
Vue.use(VueRouter);

// 引入组件
import Home from '../pages/Home';
import User from '../pages/User'

// 路由配置
let routes = [
{ path:'/home',component:Home },
{ path:'/user',component:User },
{ path:'/',redirect:'/home' }
]

// 路由实例
let router = new VueRouter({
mode:'history',
routes
})

// 导出路由实例,让他去控制vue根
export default router;

展示区

<router-view>展示区</router-view> 用来替换不同的pages

声明式跳转

理解

实现在页面点击跳转到不同的页面,不用在地址栏上跳转

语法 <router-link to="/home" tag='li' active-class='css类名'>声明式跳转</router-link>

注意

router-link 组件属性
tag='li' 指定编译后的标签
active-class='类名' 指定激活后的样式 模糊匹配
exact-active-class='类名' 指定激活后的样式 严格匹配router-link和router-view组件是vue-router插件提供的

重定向

理解

当访问到某一地址的时候,让它自动定向到指定地址上

语法

//src/plugins/router.js > routers

{
path: '/', //默认页
redirect: '/home' //配置型跳转
},

404

理解

路由的匹配是之上而下的,最后设置个任意地址都指向nopage页面

语法

{
path: '*',
component: NoPage组件
}

路由嵌套

语法

image.png

image.png *{path: ':动态变量名',component: 组件名}

路由传参

理解

将?后面的参数传给子路由页面

语法

// 组件中
<router-link to='xx/参数?a=1&b=2'></..>
<router-link :to='{name:'名字',params:{id:参数},query:{a:2,b:3}}'></..>

命名路由

理解

给路由配置的绑定一个名字,使在动态传参的时候能匹配到该地址

语法

//src/plugins/router.js => routes

{path: '/home',component: Home, name:'名字'}, //route 一条路由的配置

组件接参

理解

接收组件中$route带的信息

语法

//template
{{$route.params/query/path}}

//script
this.$route.params/query

编程式跳转

理解

通过$router里的方法进行路由跳转

语法

this.$router.push(string|obj)

this.$router.push({name:'...'}) //添加一个路由 (记录到历史记录)

this.$router.replace({name:'...'}) //替换一个路由 (不记录到历史记录)

this.$router.go(-1|1)|back() //回退/前进

与声明式跳转的区别

声明式是通过组件标签进行跳转,而编程式是通过js进行跳转

路由模式

语法

// src/plugins/router.js

let router = new VueRouter({ //插件路由对象
routes,
mode:'hash'//哈希模式 location.href
mode:'history'//历史记录 history.pushState
});

路由元信息

理解

在$route中带一些数据,方便之后使用

语法

定义路由的时候配置'meta'字段
//src/plugins/router.js
{
path: '/home',
component: Home,
meta: { requiresAuth: true }
}

访问'meta'字段
this.$route.meta
to.meta from.meta

路由守卫

理解

对进入与离开设定一些权限和业务

全局守卫 位置

router.js

卫兵

前置
router.beforeEach((to, from, next) => {
next(false);//走不了
next(true);//走你
next('/login')//走哪
next({path:'/detail/2',params:{},query:{}})//带点货
// 守卫业务
if(to.path=='/login' || to.path=='/reg' || to.path=='/register'){
//判断是不是登录了
//axios请求 携带 token
next()
}else{
next('/login');
}
})

后置
router.afterEach((to,from)=>{
//全局后置守卫业务
})

参数

to: 目标路由 $route
from: 当前路由 $route
next() 跳转 一定要调用

路由独享守卫 概述

在注册路由的时候引用

只有前置

// src/plugins/router.js -> routes

{
path: '/user',
component: User,
beforeEnter: (to,from,next)=>{ //路由独享守卫 前置
console.log('路由独享守卫');
if(Math.random()<.5){
next()
}else{
next('/login')
        }
    }
},

组件内部守卫 概述

在组件内部的script里设置,例如弹出支付组件前要先判断你有没有登录

前置

beforeRouteEnter (to, from, next) {//前置 运行在beforeCreate 之前
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
}

后置

beforeRouteLeave (to, from, next) {//后置 运行在beforeDestory之前
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}

组件被复用

beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
}

滚动行为

理解

单页面只有一个滚动条,不同页面高度不一样,导致滚动条移动的固定百分比在不同的页面移动的像素距离是不同的

语法

// src/plugins/router.js

const router = new VueRouter({
scrollBehavior (to, from, savedPosition) {
    //计算位置
    return { x: 0, y: 0 }
    }
})

其他

image.png

有这个符号代表引入router成功

思考

哈希模式和历史记录模式有什么区别?

格式不同,使用的api不同,兼容性不同

二十二、mock

1.概述

JSON-Server 是一个Node模块,运行Express服务器,你可以指定一个json文件作为api的数据源

2.基本操作

安装json-server yarn add json-server -S

一个在前端本地运行,可以存储json数据的server

配置json文件的数据源

{
"course": [
{
"id": 1000,
"course_name": "马连白米且",
"autor": "袁明",
"college": "金并即总变史",
"category_Id": 2
},
{
"id": 1001,
"course_name": "公拉农题队始果动",
"autor": "高丽",
"college": "先了队叫及便",
"category_Id": 2
        }
    ]
}

将数据源文件托管成一个web服务

image.png

二十三、mockjs库

1.概述

用mockjs里的动态方法生成模拟数据

2.使用

// 用mockjs模拟生成数据
var Mock = require('mockjs');
module.exports = () => {
// 使用 Mock
var data = Mock.mock({
'course|227': [
{
// 属性 id 是一个自增数,起始值为 1,每次增 1
'id|+1': 1000,
course_name: '@ctitle(5,10)',
autor: '@cname',
college: '@ctitle(6)',
'category_Id|1-6': 1
}
],
'course_category|6': [
{
"id|+1": 1,
"pid": -1,
cName: '@ctitle(4)'
}
]
});
// 返回的data会作为json-server的数据
return data;
};

二十四、状态管理

1.场景

多个组件间需要共享状态的时候

vuex

1.使用步骤

安装 npm i vuex@3.6.2 -S

引入

src/pulgins/vuex.js(注册vuex)

import Vue from 'vue';
import Vuex from 'vuex';
// 安装插件
Vue.use(Vuex);
import state from '../store/state';
let store = new Vuex.Store({
state
})
export default store;

src/store/state.js(创建仓库)

let state = {
count: 10
}
export default state

main.js(给vue引入仓库)

import store from './plugins/vuex'
new Vue({
render: h => h(App),
store
}).$mount('#app')

2.成员

vuex相关成员

image.png

状态管理store实例相关成员

image.png

3.角色分工

image.png

image.png

4.简易交互流程图

image.png

思考

用了vuex组件内部不用写业务,通过actions和getters来进行业务处理?那这两个算什么东西,另外开辟一个js文件?

另开辟js文件来处理

image.png

mapMutations是函数返回的是对象,包含mutations 里的add函数

二十五、组件通讯

1.贴层传递

子得父

$parent.父数据
(父更新,子更新)

父得子

$children[n].子数据
(子更新,父不会更新)

2.隔层传递

attrs/attrs/listeners 父传子

// A -> B... ->C->D
1. A中用属性绑定传值
2. B...C中都给下一紧贴层绑 v-band"$attres"
3. D中用props正常接收

子传父

// A <- B... <-C<-D
1. A中用@自定义事件="函数名"来接收D传来的数据
2. B...C中都给下一紧贴层绑 v-on"$listeners"
3. D中用$emit('自定义事件',数据)来发送数据

provide/inject 父传子

1. 父用provide:{}来提供变量
2. 子用inject:[]来接收变量

3.集中式管理

订阅发布模式 公共总线

//src//bus.js
import Vue from 'vue'
const bus = new Vue()
export default bus;

//组件内部
import bus from '...';
bus.$emit('事件',数据) //发布
bus.$on('事件',(接){处理}) //订阅
bus.$off('事件') //取消订阅

订阅要在发布之前

$root

web存储

通过把数据存储在客户端浏览器本地的行为,cookie、localstroage、session

状态管理

在浏览器下层,应用上层,打造一个"全局变量",利用vuex插件管理

数据库

利用本地,或者远端的数据库存储

二十六、动态组件

1.概述

让多个组件使用同一个挂载点,类似选项卡切换组件

2.语法

<component v-bind:is="which_to_show"></component>

data:{
      which_to_show:'first'
},


components:{
        first:{
            template:'<div>这是子组件1<div>'
        },
        second:{
            template:'<div>这是子组件2<div>'
        },
        third:{
            template:'<div>这是子组件3<div>'
        },
    }

二十七、缓存组件

1.概述

  • keep-alive 包裹了目标组件,对目标组件缓存,后期不会触发卸载挂载,但会触发actived/deactived
  • keep-alive 不给属性时,默认内部出现过得组件,都会被缓存,子集无需包裹

2.语法

<keep-alive
:include="/组件名|组件名2/" 加入一部分
:exclude="['组件名','组件名2']" 排除一部分
:max = "数字" 最多可缓存的组件数,一旦这个数字达到了,时间戳最早出现的被卸载(遗忘)
>
..目标组件..
</keep-alive>

3.注意

其作用的钩子函数是,actived激活,deactived失活

二十八、组件懒加载

1.概述

拆分路由的加载时间

2.语法

// src/plugins/router.js 路由配置
{
path: '/home',
component: ()=>import("../pages/Login.vue")
}

持续更新中...