一、简介
一套用于构建用户界面的渐进式框架。
核心思想是数据驱动:数据映射图,数据的修改会自动引起试图的变化。
双向数据绑定:数据改变会引起试图的变化,试图的变化也会引起数据的变化,这就是双向数据绑定。
二如何理解Vue渐进式框架
Vue框架生态本身是包含很多东西的:
- 核心
-
- 声明式渲染
- 组件系统
- vue-router (路由)
- vuex (状态管理)
- 构建系统
-
- webpack
- vue-cli
- vite
一开始不必使用上框架的全部东西,根据需要自行选择即可。
安装
可百度Vue官网Vue.js
Vue的基本使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<!--
1. 定义一个挂载点
2. 引入 Vue js
3. 实例化 Vue
-->
<div id="app">
<h1>Hello Vue</h1>
<p>My name is {{ name }}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
name: '张三'
}
})
</script>
</body>
</html>
声明式渲染
<div id="app">
{{ message }}
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
</script>
表达式渲染
<h2>{{ msg + name }}</h2> //hello帅威
<h2>{{ age + 10 }}</h2> //28
<h2>{{ age > 18 ? '大于' : '不大于' }}</h2> //不大于
<h2>{{ msg.split('').reverse().join('') }}</h2> //olleh
<script>
new Vue({
// 配置对象 api 选项
// 主要的两个 el data
el: '#app',
data: {
// 数据存放的位置 key: value
msg: 'hello',
name: '帅威',
age: 18,
sex: '男'
}
})
// new Vue({
// el: '#root',
// data: {
// msg: 'world'
// }
// })
</script>
可以在#app:标签里面使用Vue
小问题:在我们使用开饭版Vue是控制台会多出提示 这是因为我们是在开发模式中运行Vue
解决这个小提示
<script>
// 阻止提示
Vue.config.productionTip = false
</script>
Vue常用的指令
什么是指令:
本质就是Vue自定义的标签属性
列:格式以 "v-" 开头的,例如:v-text v-html v-if v-for v-show v-bind
作用:
当表达式的值改变时,将其产生的连带影响,响应式的作用于DOM。
指令:v-text
v-text: 将数据的值绑定到标签内,innerText
指令: v-html:
作用:将数据绑定到元素的内容上,innerHTML
区别:
能不能渲染html标签的区别。v-html可以,v-text不可以
注意事项:
1. 一般不要使用 v-html 。因为你不知道数据是什么内容,可能会产生 xss(跨站脚本攻击(也称为XSS)指利用网站漏洞从用户那里恶意盗取信息。) 攻击。
2. 只有在你明确的知道这个数据是可以信任的时候,才去使用 v-html。
指令:v-bind
作用:将数据动态绑定到标签的属性上。
可以动态改变标签里面的属性值
v-bind 简写::
简写方式:
v-bind:xxx => :xxx
问题:通过插值表达式的语法使用的时候,页面在初始化时有一个闪烁的效果
现象的原因:
1. 网络问题,主要原因
2. 实例化也需要时间,次要原因
指令:v-cloak
作用:解决闪烁的问题
方法:
1. 将元素使用 v-cloak 指令,这个指令不需要绑定数据
2. 需要设置样式
[v-cloak] {
display: none
}
条件渲染
v-if
v-if="xxx" xxx 是一个数据,定义在 data 中的数据 key
v-else
1. 没有值,参考 v-cloak
2. 需要配套着 v-if v-else-if 去使用
3. 跟着 v-if 或者 v-else-if 的下一个兄弟元素
v-else-if
1. 有值,v-else-if="xxxx"
2. 需要配置着 v-if 使用
3. 跟着 v-if 或者 v-else-if 的下一个兄弟元素
v-show
1. 没有配套的,是个孤儿
2. v-show="xxx"
v-if 与 v-show的区别
1. v-if 有配套的 v-else v-else-if。 而 v-show 没有
2. v-if 才是真正的条件渲染,条件为 true, 元素渲染,否则元素直接给干掉了
3. v-show 只是简单的控制的元素样式,display 来实现显示隐藏的。
4. 一般来说,当一个元素会频繁的切换显示隐藏的时候,推荐使用 v-show 。性能更好,节省了元素渲染的开销
5. 否则一般使用 v-if 。 当vue初始化完成之后,初始数据为 false 的时候性能更好。
6. v-if 可以配合 template 元素去使用,而 v-show 不可以。
因为 v-show 要控制元素,而template并没有生成dom元素
template 元素 模板
是 vue 实现的一个自带的 组件(自定义的标签)
作用:做包裹使用
特点:vue 渲染的时候,不会生成 真实的 dom 节点
需要代码整洁高效,又需要不影响到现有的布局与样式时,就可以使用
v-for 循环
v-for="xx in yy"
yy 是数据,一般是个数组格式
xx 是循环这个 yy 的时候的每一项
v-for=”(item, index) in list”
list 是数据,一般是个数组格式
item 是数据中的每一个项目
index 是下标
v-for="(value, key, index) in obj" 对象格式
obj 是对象格式
value 是 key:value中的value
key 是 key:value中的key
index 是 下标
key的使用
当使用 v-for 时,必须要跟着一个动态的 key 属性
1. key 不能写死,需要保证循环的每一项都是一个唯一的key
2. key 一般使用数据的 id ,不推荐使用 index 下标
v-for时,为什么需要使用key。 diff 算法
1. vue 在数组更新的时候,为了更高效的去复用之前已经渲染出来的 dom需要给他提供一个位置的标记,就是key
2. 为什么不推荐 index 下标呢,因为当使用下标的时候,没有高效一说
不用key: 没法根据特有的属性去判断是否需要重新创建标签。
当v-if 和v-for同时使用时
v-on 指令
v-on:xxx="yyy"
xxx 是指令的参数
click
dbclick
...
yyy 是指令的值,这块一般是个函数
1. 可以直接对现有数据做操作 num++
2. 使用 methods 选项中定义的 函数
v-on简写: @
Vue组件注意事项
- 组件没有 el 选项。因为后续调用组件在哪里,这个组件的挂载点就是哪里
- 必须有 template 或者 render 选项,用来规定组件的模板内容
- data 选项必须是一个函数返回对象的形式
- 组件名不能是现有的 html 标签名,也不能是其他已经注册过的组件名
- 全局注册组件时,必须要放置在 new Vue 之前
- 组件名可以使用短横线写法与驼峰写法。但是调用组件时需要使用短横线写法。下列三种情况下可以不听这个规则
-
- 使用 template 字符串模板
- 使用 <script type="text/x-template"></script>
- 使用 .vue 后缀的单文件组件中时
- 组件的 template 模板,必须只能有一个根元素。
- prop的名字可以是用短横线与驼峰写法。但是调用组件时设置这个prop的时候,需要使用短横线写法。下列三种情况下可以不听这个规则
-
- 使用 template 字符串模板
- 使用 <script type="text/x-template"></script>
- 使用 .vue 后缀的单文件组件中时
- prop是不允许修改,应为要遵循单向数据流这个规定
- 父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
- 调用组件时,写在组件标签内的内容,默认是不会被渲染的
事件修饰符
vue给我们的事件做了一些方便我们使用的小功能。比如快速阻止默认行为,快速阻止事件冒泡
v-on:xx事件.yyy => .yyy 就是修饰符
.prevent 阻止默认行为 (常用)
.stop 阻止冒泡 (常用)
.capture 将事件触发模式,修改为捕获阶段,(不常用)
.once 一次,只触发一次事件,触发完之后就不生效了 (不常用)
.self 自身,触发事件的元素与绑定事件的元素 target 一致时,才触发(不常用)
.passive 明确告诉浏览器,我绝对不会去阻止你的默认行为。(常用于移动端长列表滚动时)
绑定按键键盘触发事件
1. 绑定键盘事件,使用keyup
2. 只有回车键keyup的才触发? 就需要用到按钮修饰符 .enter
v-on:xxx 绑定xxx事件
xxx:
onclick => click
onkeyup => keyup
onblur => blur
onfoucs => foucs
....
计算属性
1. 写在哪里,computed 选项中
2. vue有个建议,不要将复杂的逻辑写到模板中(html)
3. 技术属性中使用到的数据,可以理解为是这个计算属性的依赖数据
计算属性的特点:
1. 当依赖发生变化的时候,计算属性的函数会重新执行。效果是页面自动更新
2. 依赖数据没有发生变化,函数不会重新执行,有缓存。
计算属性一般情况下不能修改
可以通过 get可以获取属性
set可以设置属性
watch
作用:监听数据的变化,然后去干些事情
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
生命周期
1. 在不同的阶段做一些事情。
2. vue会在一些阶段完成的时候,主动帮我们触发一些函数。
3. 这些函数就叫生命周期函数、生命周期回调函数、生命周期钩子函数
vue2.x 中有 8 个生命周期钩子函数
三个阶段
1. 挂载阶段
beforeCreate
created (常用)
1. 初始数据请求
beforeMount
mounted (常用)
- 1. 初始数据请求
- 2. 绑定全局事件
- 3. 定时器
- 4. 操作真实DOM (实在没办法的时候使用)
2. 更新阶段
beforeUpdate
updated
3. 销毁阶段
beforeDestroy (常用)
- 1. 收尾工作
- 2. 解绑一些全局事件
- 3. clear清理工作
destroyed
生命周期图例:
组件的使用
Vue中组件是可复用的 Vue 实例,且带有一个名字(组件名)。
因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项
组件使用分三个步骤:
1. 定义组件
Vue.extend
2. 注册组件
- 全局注册:Vue.component(组件名称, 第一步中定义的组件)
3. 使用组件
将第二步中的组件名称,拿到页面中当成自定义标签使用即可。
组件作用
- 组件:用来实现局部功能效果的代码集合(html、css、js)。
- 组件化:当应用中的页面功能都是组件的方式来编写的,那这个应用就可以叫做一个组件化的应用。
- 他们的作用:
-
- 避免变量污染
- 提供代码的复用率
- 提高代码的可维护性
- 依赖关系清晰
组件的定义:
const person = Vue.extend({
// options
template: '<div>我是组件</div>'
})
// 简写方式,直接写对象
const person = {
// options
template: '<div>我是组件</div>'
}
组件定义的两种方式:
1. template定义组件模板
template: `
<div>
<h1>姓名:张三</h1>
<p>年龄:18</p>
</div>
`,
2.render 定义组件模板 createElmenet document.createElmenet
1. render 需要定义成一个函数
2. render 函数会接受一个 createElement 的参数,这个参数也是一个函数
3. 需要 return createElment 创建的虚拟元素(节点)
4. 这里的 createElmenet 返回是 虚拟DOM VDOM
5. 每个节点叫做,虚拟节点 VNode
return createElement("元素名称", "元素属性", "元素内容");
template与render的区别
1. template 更人性化,render 使用太麻烦了。
2. 作用都一样
3. 我们推荐使用 template
4. 其实 我们使用 template Vue 内容会将 template 的内容转换成render
注意事项
- 组件没有 el 选项。因为后续这个组件在那里调用就挂载在那里。
- 必须提供 template 选项 或 render 选项,设置组件的模板内容。
- 在Vue2.x 中,模板内容必须是单个根元素。
- data 选项必须是一个函数,返回一个对象的格式。 因为组件是要复用的,避免 data 数据存在引用关系。
组件的注册
1.全局注册 - 使用 Vue.component 方法
Vue.component(componentName, {
template: `组件模板内容`,
data() {
return {
// 组件的data数据
}
}
})
2.局部注册 - 使用 components 配置项
new Vue({
/** ... */
components: {
// key: value
// key - 组件名
// value - 定义的组件
},
});
注意事项
- 组件名不能使用现有的 html 标签名,或已经注册的组件名。
- 可以配置 name 选项,指定组件在开发者工具中显示的名字。
- 全局注册组件时,必须要放置在 new Vue 之前。
- 组件名可以使用 kebab-case(短横线写法)与 PascalCase(大驼峰写法) 。
-
- 但是调用组件时需要使用短横线写法。下列三种情况下例外:
-
-
- 使用
template
字符串模板 - 使用
<script type="text/x-template"></script>
- 使用 .vue 后缀的单文件组件中时
- 使用
-
组件的使用
将注册好的组件名,当做自定义标签使用即可。
组件的prop
prop传递值
1. 将组件当成函数来看
2. props 选项定义的是函数的形参
2.1 定义了的形参,可不可以不传递
3. data 选项定义的数据是函数自身的数据
注意事项
1. props 需要首先在 组件中通过 props 选项定义
2. props 选项支持 [] 简单格式写法。还有 {} 的写法
['name', 'age']
{
'name': String // String|Number|Function|Boolean|Object|Symbol|Date|Array
'age': {
type: String // String|Number|Function|Boolean|Object|Symbol|Date|Array
required: true|false
default: ''
}
}
3. 设置 prop 默认值的时候,如果默认值是一个对象或者数组格式,不能直接写,需要使用 函数返回的方式。
4. prop的名字
4.1 name 单个单词
4.2 myName 小驼峰
注意: 当使用小驼峰时,模板中传递prop的时候,需要转换成 短横线写法。跟组件名的规则类似,同样在下列三种情况下可以不转换:
1. template 字符串
2. <script type="text/x-template" id="xxx"></script>
3. 后缀名为 .vue 的单文件组件中
5. 组件接收到的prop不允许修改
1. 遵循单向数据流这个规则
如何修改prop
组件接受到 prop 不允许修改,但是可以找到数据提供方,让它去修改
触发自定义事件 原生 click input blur
this.$emit(eventName, payload)
eventName - 自定义事件的名称: 比如 myClick
payload - 携带的一些参数
配置着 v-on 在 组件标签上 绑定这个自定义事件
自定义事件的 $event 这个变量的值,不是事件对象,而是触发这个事件时,传递的 payload
使用VueCLI脚手架
初始化脚手架
说明
- Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)
- 最新的版本是 4.0.8
- 文档:Vue CLI
具体步骤
- 如果下载缓慢请配置 npm 淘宝镜像:npm config set registry http://registry.npm.taobao.org
- 全局安装@vue/cli:npm install -g @vue/cli
- 切换到你要创建项目的目录,然后使用命令创建项目:vue create xxxx
- 选择使用vue的版本
- 启动项目:npm run serve
- 暂停项目:Ctrl+C
脚手架的结构
.文件目录
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
└── package-lock.json: 包版本控制文件
render函数
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
el:'#app',
// 简写形式
render: h => h(App),
// 完整形式
// render(createElement){
// return createElement(App)
// }
})
总结:
1.vue.js
与 vue.runtime.xxx.js
的区别:
vue.js
是完整版的 Vue,包含:核心功能+模板解析器vue.runtime.xxx.js
是运行版的 Vue,只包含核心功能,没有模板解析器
2.因为 vue.runtime.xxx.js
没有模板解析器,所以不能使用 template
配置项,需要使用 render
函数接收到的createElement
函数去指定具体内容
非prop属性
组件是个标签,标签就可以传递属性,如果传递的属性,在组件内部没有定义为prop.那么这些属性叫做非prop的属性, 非prop特性
非prop的属性有特点
1. 会继承到组件的根元素身上
2. 这些继承的效果是替换的. class与style例外
3. 这些属性可以在组件内部通过 $attrs 去获取到 class与style例外
4. 有些时候,可能不希望它自动继承到根元素身上. 在组件内, 配置inheritAttrs: false,
禁用非prop属性的继承
inheritAttrs: false,
注意:class style除外
自定义事件
$attrs 接受到非prop特性,class与style除外 {}
$listeners 接受到绑定的事件 {}
v-model默认属性
model: {
prop: "checked", // v-model 的属性是 xxx ,默认是 value
event: "change", // v-model 的事件是 xxx, 默认是 input
},
在组件上改变自定义组件为原生事件
1. 组件上绑定的事件都是自定义事件,
2. 如果需要绑定原生事件,需要添加一个修饰符 .native (原生)
3. 加上 .native 绑定的原生事件,默认会绑定到组件的根元素身上。
组件之间通信的方式,有哪些 (七大)
prop 父传递数据给子组件
$emit 子组件触发自定义事件
兄弟组件: 数据提升,提升到相同的父组件身上
v-model: 父子双向
.sync 修饰符:父子双向
$refs 父子
$parent 子父
$root 根组件
$children 所有子组件的集合[]
provide/inject 层级很深的时候
事件总线 EventBus
终极解决方案:状态管理器(Vuex)
案例:A组件要通知B组件
B组件在 created 中使用 eventBus 绑定一个事件
$on(eventName, () => {})
A组件在 合适的时机 使用 eventBus 触发B组件中绑
定的那个事件名 $emit(eventName)
.sync 修饰符 是 v-bind 使用的,在满足某种条件的时候可以简写成 .sync 修饰符的形式
vue-router(vue路由器)
vue 的一个插件,专门用来实现基于 vue 的 单页面应用(SPA)。
1、单页面应用 与 多页面应用
1.SPA 单页面应用(Single Page Application)
概念:基于前端路由而起,整个网站只有一个HTML页面。通过监听地址栏中的变化,实现页面的局部内容更新,同时支持浏览器的前进与后退操作。
2.MPA 多页面应用(Multi Page Application)
概念:整个网站有多个HTML页面,页面跳转需要整页整体刷新。
2、路由的理解
1、什么是路由(route)
- 路由的本质就是一种映射关系(key - value)
- 根据不同的url请求,返回对应不同的资源。那么url地址和真实的资源之间就有一种对应的关系,这种对应的关系就是路由
2、什么是路由器(router)
- 管理路由的东西
3、路由的分类
后端路由
- 概念:根据不同的URL请求,返回不同的内容
- 本质:URL请求地址与服务器资源之间的对应关系
前端路由
- 概念:监听URL地址的变化,显示不同的页面元素组件
- 本质:URL请求地址与组件之间的对应关系
vue-router 的使用步骤
1. 项目必须安装 vue-router
2. 引入 vue-router 插件
3. 注册它 Vue.use(VueRouter)
4. 定义 路由规则
5. 实例化 路由对象
6. 将实例化的路由对象配置到 new Vue 的 router 选项上。
7. 在 App.vue 中放一个坑用于承载路由组件 <router-view>
// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
})
// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app')
// 现在,应用已经启动了!
// 不要忘了在页面中放置一个 <router-view /> 用来渲染路径匹配到的(路由)组件
路由组件
根路由打交道,且是某个path路径对应关系的组件,咱们就叫它路由组件
1. 不需要手动使用
2. 它会自动渲染到 路由视图组件(router-view) 中。
3. 在合适的时机,某个路由组件就会渲染到 router-view这个坑中
(路由组件)可以放在views文件夹中
注意:这个 views 文件夹,还可以换成 pages
vue-router 的安装
使用脚手架创建的老项目:vue add router
新项目创建时:脚手架勾选上
导航的两种方式
声明式: router-link 组件
使用的是 a 标签方式 \ router-link 组件方式 这两种方式可以叫做 声明式导航(声明式跳转\a标签跳转)
样式设置:
router-link 高亮效果,借助与它自带的一个 router-link-active 类名 这个类名会自动添加或移除
编程式
用js控制跳转的方式,叫做 编程式导航(编程式跳转\js跳转)
this.$router.push(增加)
历史记录的默认模式, 追加模式
[
'#/',
'#/home',
'#/about'
]
this.$router.replace(替换)
历史记录的替换模式
[
'#/',
'#/home' push
'#/about'
]
默认模式: / -> home -> about
替换模式: / -> about
replace 模式
- 默认: 每次跳转都添加了一个历史记录
- replace: 当前的历史会被覆盖掉
替换会把之前访问的路径替换掉
<!--
声明式导航 replace
-->
<RouterLink to="/home">首页</RouterLink>
<RouterLink to="/about" replace>关于页面</RouterLink>
<!--
编程式导航 replace
-->
<button @click="$router.push('/home')">首页</button>
<button @click="$router.replace('/about')">关于页面</button>
命名路由
在路由规则集合中加上name
声明式导航 使用 name 的跳转 :to="{ name: 'xxx' }"
<RouterLink to="/home">首页</RouterLink>
<RouterLink :to="{ name: 'abc' }">首页</RouterLink>
<RouterLink :to="{ name: 'about' }">关于页面</RouterLink>
编程式导航 使用 name 的跳转 { name: 'xxx' }
<button @click="$router.push('/home')">首页</button>
<button @click="$router.push({ name: 'abc' })">首页</button>
<button @click="$router.replace({ name: 'about' })">关于页面</button>
定义路由规则时,多配置一个 name 选项, 后续跳转就可以直接应用这个 name 选项.
路由传递参数
路由传参 1. 路径后面通过?号传递参数
<router-link :to="`/detail?id=${item.id}&name=${item.name}`">
路由传参 2. 对象形式,通过 query 属性传递
<li v-for="item in goodList" :key="item.id">
<!--
路由传参 1. 路径后面通过?号传递参数
-->
<router-link :to="`/detail?id=${item.id}&name=${item.name}`">
{{ item.name }}
</router-link>
<!--
路由传参 2. 对象形式,通过 query 属性传递
-->
<router-link
:to="{
name: 'detail',
query: {
id: item.id,
name: item.name,
},
}"
>
{{ item.name }}
</router-link>
<div @click="goDetail(item.id, item.name)">{{ item.name }}</div>
</li>
路由传参 3. 字符串
<router-link :to="`/detail/${item.id}/${item.name}`">
{{ item.name }}
</router-link>
路由传参 4 对象形式,通过 params 属性传递
<router-link
:to="{
name: 'detail',
params: {
id: item.id,
name: item.name
},
}"
>
{{ item.name }}
</router-link>
动态路由匹配
- 占坑
- ? - 可有可无
- * - 放到最后一个,一般用来做404页面
通配符 * : 不管路径是什么,都可以匹配这条 * 的规则。
需要放置在最后一条规则,相当于兜底。
动态路由适用场景
比如写用户的信息页面时,页面的结构都一样,只是用户的名称不一样,这个时候就可以使用动态路由
路由规则的props
注意: props 需要你在组件中通过 props 选项先定义,不然这里传递过去的,组件中将是 $attrs 接收到的。
props 选项的 第一种使用方式 布尔值
props: true,
props 选项的 第二中使用方式 对象式, 静态传递属性给当前规则的组件
props: {
a: '10',
b: '20',
c: true,
id: 100,
},
props 选项的 第三种使用方式 函数式
props: function (route) {
console.log('====', route)
return {
// 期望属性 a 是变化的 id 而不是写死的 10
// a: '10',
// b: '20'
// a: route.params.id
// 满足页面需要的代码
id: route.params.id,
name: route.params.name
}
},
总结:
果参数传递了太多的话,为了省去 this.$route.params 或者 this.$route.query 的多次编写.
三种写法
- 布尔写法 props: true 参数传递了什么,组件的$attrs中就有什么.如果组件配置了props选项,就在props中
- 对象写法 props: {} 对象里面写什么,组件的$attrs中就有什么.如果组件配置了props选项,就在props中
- 函数写法 props: function(route) { return {} } 返回的对象里面写什么,组件的$attrs中就有什么.如果组件配置了props选项,就在props中
一样不怎么使用,因为工作中,一般只需要一个id的参数,然后通过id调用接口获取更多的数据.
组件复用问题
当详情页面组件创建完成之后,拿到id去获取后端的接口数据
猜你喜欢案例时,组件会被复用,导致生命周期的钩子函数不会重复执行。带来的问题是:页面数据会有问题。
解决方案:
监听$route
watch: {
// 第一种方案:监听 $route
$route() {
this.getData();
},
},
二种方案:使用 beforeRouteUpdate 钩子函数 导航守卫
beforeRouteUpdate(to, from, next) {
console.log("to: ", to);
console.log("from: ", from);
// to -> 去哪里
// from -> 从哪里来的
// next -> 是一个函数,如果不执行这个函数,将不会放行....
this.getData();
next();
},
总结:
/detail/1 => /detail/2 这时使用的都是 HelloDetail.vue 这个组件,为了高效,这个组件会被复用,也就是生命周期的钩子函数不会重复执行.
带来的问题是: ajax获取数据的代码不会被执行.页面会渲染出错误的内容
解决方案:
1. 监听 $route
2. 使用 beforeRouteUpdate 这个 导航守卫, to from next一定要执行 next()
路由的嵌套
实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如:
借助 vue-router
,使用嵌套路由配置,就可以很简单地表达这种关系。
接着上节创建的 app:
<div id="app">
<router-view></router-view>
</div>
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
routes: [{ path: '/user/:id', component: User }]
})
这里的 <router-view>
是最顶层的出口,渲染最高级路由匹配到的组件。同样地,一个被渲染组件同样可以包含自己的嵌套 <router-view>
。例如,在 User
组件的模板添加一个 <router-view>
:
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`
}
要在嵌套的出口中渲染组件,需要在 VueRouter
的参数中使用 children
配置:
const router = new VueRouter({
routes: [
{
path: '/user/:id',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 <router-view> 中
path: 'posts',
component: UserPosts
}
]
}
]
})
要注意,以 /
开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径
你会发现,children
配置就是像 routes
配置一样的路由配置数组,所以呢,你可以嵌套多层路由。
重定向
重定向也是通过 routes
配置来完成
重定向的目标也可以是一个命名的路由
甚至是一个方法,动态返回重定向目标
注意导航守卫并没有应用在跳转路由上,而仅仅应用在其目标上。在下面这个例子中,为 /a
路由添加一个 beforeEnter
守卫并不会有任何效果。
路由模式有两种
1. hash 模式
- 默认是 hash 模式
- url 地址上有 # 号
- 监听地址变化的原理是:hashChange 事件
- 支持良好
2. history 模式
- url 地址上没有 # 号
- 监听地址变化的原理是:HTML5 中新增的 history 的 API 叫做 popstate 事件
- 看上去美了点
- 上线到服务器,需要运维配置一些信息。
?如何切换模式?
在 new VueRouter 的时候,传递 mode 选项
默认 hash
导航守卫
也称为路由守卫、路由跳转钩子函数
在路由跳转的各个不同阶段会自动执行的一些函数。这些函数中可以处理一些逻辑 允许还是不允许。
主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
注意:记住参数或查询的改变并不会触发进入/离开的导航守卫
可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate
的组件内守卫。
全局前置守卫
你可以使用 router.beforeEach
注册一个全局前置守卫:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
每个守卫方法接收三个参数:
-
to: Route
: 即将要进入的目标 路由对象 -
from: Route
: 当前导航正要离开的路由 -
next: Function
: 一定要调用该方法来 resolve 这个钩子。执行效果依赖next
方法的调用参数-
1. next() 正常跳转到 to
-
2. next(false) 阻止了跳转
-
3. next(path || {}) 跳转到别处
-
全局后置守卫
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
router.afterEach((to, from) => {
// ...
})
路由独享守卫
你可以在路由配置上直接定义 beforeEnter
守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
组件内的守卫
最后,你可以在路由组件内直接定义以下路由导航守卫:
beforeRouteEnter
beforeRouteUpdate
(2.2 新增)beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
性能优化 - 路由懒加载
优化:把首页不需要用到的资源给拆出去,等用户访问到具体路由的时候,再去加载需要的资源文件。
命名视图
给 坑(router-view) 取名字
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view
没有设置名字,那么默认为 default
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components
配置 (带上 s):
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})
vue的两种版本:1.完整版 2.运行时版
完整版: 包含有 运行时版本 + 编译器功能
运行时版本: 只有 运行时版本
1. 不支持编译 template 选项
问题:
1. template 选项的使用,会报错
2. 当前项目,如何证实它就是用的 运行时版本呢
原因:
1. 当前项目中使用的时 运行时版本,没有编译器功能。不会将 template 字符串编译。
2. node_modules 中 vue 的 package.json 中定义的module 字段的值
- package.json 中 main 字段与 module 字段的区别
- main: 给 commonjs 模块化系统使用的
- module: 给 ES6 模块化系统使用的
Vuex
Vuex是什么
一款 vue 官方团队提供的 插件 (使用时要Vue.use(xxx)). 用于处理数据共享的插件.
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
1.作用:能处理 vue 项目中 数据共享的 事情
2. 版本问题 : 最新的版本是 vuex4.x
3.xxx vue 2.x
4.xxx vue 3.x
Vuex执行流程图
流程原理梳理:
1. 组件派发(dispatch)动作(action).
2. 动作(action)提交(commit)突变(mutation)
3. 突变(mutation)修改(update)数据(state)
4. 数据(state)引起组件的重新渲染(render)
简短流程
1. 组件直接提交突变
2. 突变修改数据(state)
3. 数据(state)引起组件的重新渲染(render)
核心概念
1. state 存放数据的位置
2. mutations 存放突变的位置
3. actions 存放动作的位置
store 仓库. 上述三个东西,都在仓库中. 且 store 还有一些api供组件使用
store.state 拿仓库的数据
store.dispatch() 派发动作
store.commit() 提交突变
....
Vuex 必须在项目中使用吗?
没必要
需要根据自己所写的项目需求来使用
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式 (opens new window)就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
vuex 的使用步骤
安装
1. 新项目 vue create xxx 时勾选上 vuex 即可.
2. 老项目 vue add vuex 来安装. (不推荐) 需要保证代码是干净的.
3. 老项目 npm install vuex@3
1. 引入 vuex
import Vuex from 'vuex'
2. 注册这款插件
Vue.use(Vuex)
3. 实例化 Vuex 的仓库的实例对象
const store = new Vuex.Store({
// 仓库的相关配置
state: {},
mutations: {},
actions: {},
getters: {},
moudles: {},
})
4. 将 store 与 vue 实例关联起来
new Vue({
store: store
})
仓库的数据有了,如何拿到页面(组件)中使用呢?
使用了vuex之后,并且相关的配置都ok之后,所有组件都能有一个 $store 对象,这个对象就是仓库的实例对象.
也就是 你 new Vuex.Store() 得到的那个东西
1. 直接使用 $store.state.xxx 去使用 (不推荐)
2. 使用 计算属性 去拿仓库的数据 (推荐)
3. 使用 vuex 中的 mapState 辅助函数 (推荐)
...
为什么组件中使用时,推荐使用计算属性而不要直接$store.state.xxx的方式
1. 用计算属性时,可以修改名字
2. 利用计算属性不能修改的特点, 避免直接通过 $store.state.xxx 的方式去修改它.
解决template模板下面有波浪号问题
template 有波浪号。jsconfig.json文件夹里面,加: template 有波浪号。jsconfig.json文件夹里面,加:"jsx": "preserve",
Mutation
mutations: {
// key: value
// key - 突变的名字
// value(state, payload) 突变对应的处理函数
// state 是当前仓库store的state数据
// payload 提交这个突变时,传递过来的参数
changeMsg(state, payload) {
state.msg = payload;
},
}
action
actions: {
// key: value
// key 动作的名字
// value(context, payload) 动作对应的处理函数
// context 是当前仓库store的上下文对象,理解为这玩意里面有 state、commit、dispatch、...
// == $store state、rootState、getters、rootGetters、commit、dispath
// payload 派发这个动作时,传递过来的参数
abc(context, payload) {
console.log("abc ===", context);
console.log("abc ===", payload);
},
},
什么要多出一个 action 流程
1. mutation 只能同步的修改 state 。不要使用异步的方式。
2. action 可以异步,异步之后,提交 mutation 来修改 state
getter
相对与对当前的 state 做一层 计算属性,基于当前的某个或多个state得到一个新的值,可以直接拿到组件中使用的。
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。
Getter 接受 state 作为其第一个参数:
const store = createStore({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos (state) {
return state.todos.filter(todo => todo.done)
}
}
})
Getter 会暴露为 store.getters
对象,你可以以属性的形式访问这些值:
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
Getter 也可以接受其他 getter 作为第二个参数:
getters: {
// ...
doneTodosCount (state, getters) {
return getters.doneTodos.length
}
}
store.getters.doneTodosCount // -> 1
我们可以很容易地在任何组件中使用它:
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。
rootState getters第三个参数
rootState 是 根模块的 state 数据
module
当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = createStore({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
Vuex表单v-model的使用
双向绑定的计算属性
<input v-model="message">
// ...
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
ui组件库
市面上很大大公司或大团队提供的vue的组件集合.包含有很多组件,就叫组件库
PC端
element-ui 饿了么团队
iview
移动端
vant 有赞
mintui
按需引入与完整引入的两种方式
1.完整引入
完整引入会将组件库中所有的代码都引入进项目. 带来的问题是会让我们项目的构建产物非常大.
可能我们这个项目只是有到了组件库中的一部分组件,完整引入没必要...
2.按需引入
按需引入只引入项目中需要使用到的组件,其他组件不用就不引入.能减少我们项目构建产物的大小.
修改组件库中的样式
1. 审查元素,找到它的类名,然后自己写样式去替换即可.
2. 如果要修改的元素不是组件的根元素,且当前我们的样式写了有 scoped 属性,那么覆盖的样式将不生效.
原因是: scoped 生成的样式上有属性选择器. 使用的第三方组件除了根元素外其它元素都没有添加上这个属性名(data-v-xxxx) ,所以样式不生效
解决方案是:
1. 多写一个 style 不加 scoped ,在这个 style 里面,专门写需要修改第三方组件的样式.
2. 最好保险一点,自己加多一些类名的控制