目录
- 一、基础语法
- 二、组件进阶
- 三、Vue3.0
一、基础语法
1. 基础注意事项
- 首先要创建一个vue实例
import Vue from 'vue'
new Vue({
el: '#root', //用于指定当前vue实例为哪个容器服务,值为css选择器字符串
})
- vue实例和容器是一一对应的,一个vue实例只能接管一个容器
- root容器中的代码称为模板
2. 阻止vue在启动时生成生产提示
Vue.config.productionTip=false
3. 模板语法
data中的数据发生改变,页面中用到改数据的地方会自动更新,
1)插值语法
{{js表达式}}
用 于解析标签体内容,可以直接读取到data中的所有属性。
2)指令语法
v-xxx
用于解析标签(标签属性、标签体内容、绑定事件等),可以直接读取到data中的所有属性。
4. data的两种写法
对象式
data:{
name:'zfj',
age:25
}
函数式
data(){
return{
name:'zfj',
age:25
}
}
5. 数据代理
通过Object.defineProperty()
把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性。
Object.defineProperty()
getter和setter
let number = 18
let person = {
name:'张三',
sex:'男',
}
Object.defineProperty(person,'age',{
// value:18,
// enumerable:true, //控制属性是否可以枚举,默认值是false
// writable:true, //控制属性是否可以被修改,默认值是false
// configurable:true //控制属性是否可以被删除,默认值是false
//当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get(){
console.log('有人读取age属性了')
return number
},
//当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
set(value){
console.log('有人修改了age属性,且值是',value)
number = value
}
})
6. 数据绑定
v-bind
单向绑定属性,数据只能从data流向页面,例如:
v-bind:herf=" "
//简写
:herf=" "
v-model
双向绑定,一般应用在表单元素上,例如:
v-model:value=" "
//v-model:value可以简写为 v-model,默认收集value值
v-modle 收集表单数据
<input type="text"/> //v-model收集的是value值,用户输入的就是value值。
<input type="radio"/> //v-model收集的是value值,且要给标签配置value值。
<input type="checkbox"/> //没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
// 配置input的value属性:
//(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
//(2)v-model的初始值是数组,那么收集的的就是value组成的数组
v-model的三个修饰符:
.lazy
:失去焦点再收集数据
.number
:输入字符串转为有效的数字
.trim
:输入首尾空格过滤
7. 绑定样式v-bind:
class样式
<div :class="xxx"> </div> //xxx可以是字符串,对象,数组
style样式
<div :style="xxx"> </div> //xxx可以是对象,数组
new Vue({
data:{
//对象写法
styleObj:{
fontSize: '40px',
color:'red',
},
//数组写法
styleArr:[
{
fontSize: '40px',
color:'blue',
},
{
backgroundColor:'gray'
}
]
}
})
8. 事件处理
v-on
绑定事件,监听dom事件,例如:
v-on:click=" "
//简写
@click=" "
事件的回调配置在methods(与data平级)中:
<button @click="fn1"> </button> //无参,会默认将原生事件event传进去
<button @click="fn1($event)"> </button> //与上一个效果一致
<button @click="fn2($event,2,3)"> </button> //传参
<button @click="fn2(2,3)"> </button> //传参,参数只有2,3,没有$event
methods:{
fn3:function(){
......
},
fn1(event){
......
},
fn2(event,a,b){
......
}
}
注意:
- methods中配置的函数,this指向为vm或组件实例对象;
- methods中配置的函数,不要用箭头函数,否则this就不是vm了,而是window;
事件修饰符
@click.prevent=" " //阻止默认事件
.stop //阻止事件冒泡
.once //事件只触发一次
.capture //使用事件的捕获模式,即在捕获阶段就处理事件
.self //只有 event.target 是当前操作的元素才是触发事件
.passive //事件的默认行为立即执行,无需等待事件回调执行完毕
//修饰符可以连写
@click.prevent.stop=" "
键盘绑定
@keyup
按键松开后触发事件
@keyup.enter //回车
.delete //删除和退格键
.esc //
.space //空格
.up //
.down //
.left //
.right //
@keydown
按键按下即触发
//以上均可使用
.tab //必须配合keydown使用
系统修饰键
ctrl、alt、shift、meta(win键)
- 配合keyup使用时,按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发;
- 配合keydown使用:正常触发事件。
9. 计算属性
computed
通过已有属性计算得来的属性
new Vue({
data:{
a:1
},
computed: {
sum: {
//get有什么作用?当有人读取sum时,get就会被调用,且返回值就作为sum的值
//get什么时候调用?1.初次读取sum时。2.所依赖的数据发生变化时。
get() {
return this.a++ //此处的this是vm
},
//set什么时候调用? 当sum被修改时。
set(value) {
this.a = value
}
}
}
})
//简写
computed:{
sum:function(){
return this.a++
}
}
//或
computed:{
sum(){
return this.a++
}
}
computed和methods区别
- 所有计算属性实现的功能都可以由methods方法实现,
- 然而,计算属性是基于响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值。
这就意味着只要 a 还没有发生改变,多次访问 sum 计算属性会立即返回之前的计算结果,而不必再次执行函数。 - 相比之下,每当触发重新渲染时,调用 methods 将总会再次执行函数
10. 侦听属性
watch:
- 当监视的属性发生变化时,回调函数自动调用;
- 监视的属性必须存在才能进行监视;
- 两种写法
const vm = new Vue({
el:'root',
data:{
isHot:true,
},
//写法一:
watch:{
isHot:{
immediate:true, //初始化时,调用handler一下
//当isHot发生改变时,触发
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
}
}
//简写:
watch:{
isHot(newValue,oldValue){
//当isHot发生改变时,触发
console.log('isHot被修改了',newValue,oldValue)
}
}
})
//写法二:
vm.$watch('isHot',{
immediate:true, //初始化时让handler调用一下
//handler什么时候调用?当isHot发生改变时。
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
})
深度监视
- Vue中的watch默认不监测对象内部值的改变;
- 配置
deep:true
可以监测对象内部所有值的改变;
const vm = new Vue({
el:'#root',
data:{
numbers:{
a:1,
c:{
d:2
}
},
watch:{
//监视多级结构中某个属性的变化
'numbers.a':{
handler(){
console.log('a被改变了')
}
}
//监视多级结构中所有属性的变化
numbers:{
deep:true,
handler(){
console.log('numbers改变了')
}
}
}
})
11. 条件渲染
v-if
<h1 v-if="表达式"> </h1> //表达式值为真时,内容才会被渲染
<h1 v-else-if="表达式"> </h1>
<h1 v-else="表达式"> </h1>
注意:不展示DOM元素直接被移除。
template
一个不可见的包裹元素,不影响结构
<template v-if="n === 1">
<h2>你好</h2>
<h2>尚硅谷</h2>
<h2>北京</h2>
</template>
v-show
<h2 v-show="表达式">hello</h2>
注意:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉,元素总会被渲染。
适用于切换频率较高的场景。
使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。
12. 列表渲染
v-for
//遍历数组
v-for="item in xxx" :key="yyy" //in 也可用 of
v-for="(item,index) in xxx" :key="yyy" //index为当前索引
//遍历对象
v-for="value in xxx" :key="yyy" //in 也可用 of
v-for="(value,key) in xxx" :key="yyy" //key为键名
v-for="(value,key,index) in xxx" :key="yyy" //index为索引
遍历列表时key的作用
为了能追踪每个节点的身份,从而重用和重新排序现有元素。
- vue首先根据初始数据生成虚拟DOM(虚拟DOM上一定有key);
- 将虚拟DOM转为真实DOM,渲染到页面;
- 用户在真实DOM中输入数据;
- vue根据新的数据生成新的虚拟DOM;
- 新的虚拟DOM和旧的虚拟DOM进行对比,对比规则:
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。 - 旧虚拟DOM中未找到与新虚拟DOM相同的key:
创建新的真实DOM,随后渲染到到页面。
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
注意:key默认为index
列表过滤(computed, filter)
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers() {
return this.numbers.filter((number)=> { //computed返回计算后的结果
return number % 2 === 0 //filter返回一个新数组,不改变原数组
})
}
}
列表排序
data: {
numbers: [ 4, 5, 1, 3, 2 ]
},
computed: {
evenNumbers() {
//过滤
const arr= this.numbers.filter((number)=> {
return number % 2 === 0
})
//排序
arr.sort((p1,p2)=>{ //arr.sort(参数),参数必须是函数
return p2-p1 //后减前是降序,前减后是升序
})
return arr
}
}
Vue监测数据的原理
(1)监测对象{ }中的数据
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。
- data ==> vm._data:
get/set
【响应式:数据改变,页面也跟着改变】 - data中的所有属性都会被匹配一个get和set
数据改变时,相应的set会被调用,set被调用就会重新解析模板。
- 读取data中对象上不存在的属性时,为undefined,不会报错,也不会显示undefined。
- 对象中后追加的属性,Vue默认不做响应式处理;
如需给后添加的属性做响应式,请使用如下API:
set()
//API-1
Vue.set(target,'key','val') //往target的身上追加属性key
//API-2
vm.$set(target,'key','val')
注意:set()只能给data中的对象追加属性,不能给data或者vm追加属性
(2)监测数组[ ]中的数据
Vue不能通过索引更改数组
vue没有为数组中的每个元素匹配get和set,数组中的每个元素不是通过set和get监视的;
修改数组的方法:
// 这些方法会改变原数组
push() //在尾部添加元素
pop() //删除最后一个
shift() //删除第一个
unshift() //在头部添加一个
splice(索引,长度,'替换内容') //删除、添加、替换
sort() //排序
reverse() //反转数组
注意:这些都是被Vue包裹过的方法,是对Array原型对象上的方法进行了包裹
13. 其他内置指令
v-text
向其所在的节点渲染文本内容
<div v-text="xxx"></div>
注意:v-text会替换掉节点中的内容,{{xx}}则不会
v-html
指定节点中渲染包含html结构的内容,可以识别 html结构
<div v-html="xxx"></div>
注意:有安全性问题,容易导致XSS攻击,不要用在用户提交的内容上!
- v-html会替换掉节点中所有的内容,{{xx}}则不会。
- v-html可以识别html结构。
- 在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞。请仅在内容安全可信时再使用 v-html,并且永远不要使用用户提供的 HTML 内容。
v-cloak
避免网速慢,出现未经解析的 {{xxx}} 的DOM;用于隐藏尚未完成编译的 DOM 模板。
<h2 v-cloak>{{xxx}}</h2>
视频:Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
官网:v-cloak 会保留在所绑定的元素上,直到相关组件实例被挂载后才移除。
v-once
v-once所在节点在初次动态渲染后,就视为静态内容了,以后数据的改变不会引起v-once所在结构的更新
<h2 v-once>初始化的n值是:{{n}}</h2>
v-pre
跳过其所在节点的编译过程(即vue解析过程),加快编译
<h2 v-pre>zfj</h2>
14. 自定义指令
使用时:v-指令
//局部指令
new Vue({
data:{
},
directives:{
//写法一:对象形式
big1:{
a(element, binding){},
b(element, binding){}
},
//写法二:函数形式
big2(element,binding){ // element是绑定的真实DOM元素;binding是绑定的值以及所有的信息
}
}
})
//全局指令
Vue.directive('指令名',function(element,blinding){}) //函数形式
Vue.directive('指令名',{}) //对象形式
big何时被调用?
- 指令与元素成功绑定时(一上来);
- 指令所在的模板被重新解析时
常用的3个回调函数:
bind
:指令与元素成功绑定时调用。
inserted
:指令所在元素被插入页面时调用。
update
:指令所在模板结构被重新解析时调用。
注意:directives中的this指向windows
命名规则:指令名如果是多个单词,要使用kebab-case
命名方式,不要用camelCase命名,且定义时要加''
15. 过滤器filters
//局部过滤器,与data平级
filters:{
fn(参数){
return ...
}
}
//注册全局过滤器
Vue.filter('过滤器名',function(参数){
return ...
})
//使用过滤器
{{ xxx | 过滤器名}} //或
v-bind:属性 = "xxx | 过滤器名"
注意:并没有改变原本的数据, 是产生新的对应的数据
16.生命周期
new Vue({
data:{},
//常用生命周期函数
mounted(){ //Vue完成模板的解析并把【初始的】真实DOM元素放入页面后(挂载完毕)调用mounted
},
beforeDestroy(){
}
})
注意:生命周期函数中的this指向是vm 或 组件实例对象
17. 非单文件组件
组件是可复用的Vue实例。
创建及注册组件
//1.创建组件
//Vue.extend()创建的是一个组件构造器
const 组件=Vue.extend({ //此处不是组件名
//template可以配置组件结构
template: `
<div></div>
`,
//data必须写成函数形式(避免组件被复用时,数据存在引用关系)
data(){
return ...
}
})
//可以简写为:
const 组件={
......
}
//2.注册组件
new Vue({
el:'#root',
data:{
msg:'你好啊!'
},
//局部注册
components:{
组件名:组件, //此处才是组件名
//或
组件
}
})
//全局注册,可以在多个vue实例中使用
//将上面创建的组件构造器注册为一个组件,并给它起一个组件的标签名
Vue.component('组件名',组件)
全局组件创建和注册的语法糖:
Vue.component('组件名',{
template: `
<div></div>
`
})
//局部组件类似
组件名命名规则
- 一个单词:首字母大小写均可
- 多个单词组成:
kebab-case
或CamelCase
VueComponent
- 组件本质是一个名为VueComponent的构造函数,是由Vue.extend生成的;
- 写
<school></school>
或<school/>
,Vue解析时会帮我们创建组件的实例对象,即执行new VueComponent()
; - 每次调用Vue.extend,返回的都是一个全新的VueComponent;
- 组件中的所有函数的this指向均是组件实例对象;
VueComponent.prototype.__proto__ === Vue.prototype
此内置关系让组件实例对象可以访问到Vue原型上的属性和方法。
补充:原型对象
- 只要是函数,就一定有
fn.prototype
属性,即显示原型属性; - 构造函数创建的实例对象上有一个
obj.__proto__
属性,即隐式原型属性; - 这两个属性都指向同一个对象,即原型对象;
- 实例的隐式原型属性永远指向自己缔造者的原型对象;
- 通过显示原型属性给原型对象添加属性,实例对象可以访问到原型对象的属性;
18. 单文件组件
【vscode安装vetur插件,用来编译.vue文件】
.vue文件:
<template>
//======组件的结构=======
<div>
......
</div>
</template>
<script>
//引入其他组件
import 其他组件名 from ' '
//组件交互相关代码(数据,方法等)
export default { //默认暴露
name: '本组件名', //定义组件名
data() {
return {
......
}
},
methods: {
......
},
components:{
其他组件名
}
}
</script>
<style>
/*组件的样式*/
......
</style>
template
<template> </template> //可以包裹元素,但不生成实际结构
19. 父组件和子组件
父传子
- 通过 props
<!--父组件-->
<template>
<div>
<h2>父组件</h2>
<!--给子组件发送数据-->
<Child v-bind:fatherMessage="fatherMessage"></Child>
</div>
</template>
<script>
import Child from './views/Child';
export default{
components: {
Child,
},
data() {
return {
// 定义一个数据
fatherMessage: '我是来自父组件的数据',
};
},
};
</script>
----------------------------------------------------------------
<!--子组件-->
<template>
<div>
<h3>我是子组件</h3>
<!--展示数据-->
<span>{{fatherMessage}}</span>
</div>
</template>
<script>
export default{
// 接收父组件传过来的数据
props: ['fatherMessage'],
};
</script>
子传父
- 通过父组件给子组件传递函数类型的props实现
(父给子传一个函数,子在合适的时候调用这个函数)
<!--父组件-->
<template>
<div class="app">
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"/>
</div>
</template>
<script>
import School from './components/School'
export default {
name:'App',
components:{School},
data() {
return {
}
},
methods: {
getSchoolName(name){
console.log('App收到了学校名:',name)
},
},
}
</script>
-----------------------------------------------------------------
<!--子组件-->
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="sendSchoolName">把学校名给App</button>
</div>
</template>
<script>
export default {
name:'School',
props:['getSchoolName'],
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
methods: {
sendSchoolName(){
this.getSchoolName(this.name)
}
},
}
</script>
- 通过父组件给子组件绑定一个自定义事件实现
<!--父组件-->
<template>
<div>
<h2>父组件</h2>
<br>
<!--接收数据和方法-->
<Child @childEvent="parentMethod"></Child>
</div>
</template>
<script>
import Child from './Child';
export default{
components: {
Child,
},
data() {
return {
parentMessage: '我是来自父组件的消息',
};
},
methods: {
// 使用方法展示数据
parentMethod({ name, age }) {
console.log(this.parentMessage, name, age);
},
},
};
</script>
<style scoped>
</style>
---------------------------------------------------------------------
<!--子组件-->
<template>
<div>
<h3>我是子组件</h3>
</div>
</template>
<script>
export default{
mounted() {
// 触发子组件实例身上的绑定事件,用$emit发送数据和方法
this.$emit('childEvent', { name: 'zhangsan', age: 10 });
},
};
</script>
<style scoped>
</style>
- 通过ref
<!--父组件-->
<template>
<div>
<h2>父组件</h2>
<br>
<!--ref主动寻找子组件组件-->
<Child-one ref="child"></Child-one>
</div>
</template>
<script>
import ChildOne from './ChildOne';
export default{
components: {
ChildOne,
},
mounted(){
// 使用this.$refs调用子组件方法即可,用法:this.$refs['child'].xxx()
console.log(this.$refs['child']);
},
};
</script>
<style scoped>
</style>
二、组件进阶
1. Vue脚手架 CLI
安装
全局安装(只需安装一次): npm install -g @vue/cli
创建项目:vue create 项目名
启动项目:npm run serve
(运行main.js文件)
分析脚手架结构
main.js
//该文件是整个项目的入口文件
import Vue from 'vue' //引入Vue
import App from './App.vue' //引入App组件(所有组件的父组件)
Vue.config.productionTip = false //关闭vue的生产提示
//创建Vue实例对象---vm
new Vue({
el:'#app',
render: h => h(App) //render函数完成了这个功能:将App组件放入容器中
})
App.vue
App组件(所有组件的父组件)
<template>
<div>
<Student></Student>
</div>
</template>
<script>
//引入组件
import Student from './components/Student'
export default {
name:'App',
components:{
Student
}
}
</script>
./components/Student.vue
子组件
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
name:'张三',
age:18
}
}
}
</script>
./public/index.html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <!--让IE浏览器以最高的渲染级别渲染页面-->
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <!-- 开启移动端的理想视口 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <!-- 配置页签图标 ; <%= BASE_URL %> 即public路径-->
<title><%= htmlWebpackPlugin.options.title %></title> <!-- 配置网页标题 -->
</head>
<body>
<noscript> <!-- 当浏览器不支持js时noscript中的元素就会被渲染 -->
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- 容器 -->
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
2. render函数
render(createElement){ //参数createElement是一个函数,用来创建具体的DOM元素
return createElement(App)
}
//简写:
render:h=>h(App)
-
vue.js与vue.runtime.xxx.js的区别:
(1) vue.js是完整版的Vue,包含:核心功能+模板解析器。
(2) vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。 -
因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。
3. ref 属性
得到真实DOM元素或组件实例对象,ref 需要在dom渲染完成后才会有
ref="xxx"
this.$refs.xxx
4. props
prop:属性
作用:让组件接收父组件传过来的数据。
//简单数组式声明
props:['name','age']
//对象式声明,指定值的类型
props:{
name:String,
age:Number,
}
//限制类型、限制必要性、指定默认值
props: {
name: {
type: String, //name的类型是字符串
required: true, //name是必要的
},
age: {
type: Number,
default: 99, //默认值
},
//自定义类型校验函数
propA: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
}
注意:props的值不可修改
5. mixin 混入
作用:把多个组件共用的配置提取成一个混入对象。
mixin.js
export const myMixin1 = {
data:{
},
methods:{
}
},
export const myMixin2 = {
data:{
},
methods:{
}
}
组件名.vue
//引入一个hunhe
import {myMixin1,myMixin2} from '../mixin.js'
export default {
name: "组件名",
data:{
},
mixins:[myMixin1,myMixin2],
}
注意:如果混合和自己的配置有冲突,以自己的为主;但是对于生命周期函数,两者都要,且混合的在前,自己的在后。
全局混合
main.js
import {myMixin1,myMixin2} from './mixin.js'
Vue.mixin(myMixin1)
Vue.mixin(myMixin2)
6. 插件
plugins.js
插件是一个对象。
export default {
install(Vue,options){ //第一个参数是Vue构造函数,options是其他所需参数
//添加全局方法
Vue.myMethod=function(){
...
}
//添加全局指令
Vue.directive('指令名',{
bind(element,binding){
...
},
...
})
//定义混入
Vue.mixin({
data() {
...
},
...
})
//给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.myMethod1 = function(){
...
}
}
}
main.js
//引入插件
import plugins from './plugins'
//使用插件
Vue.use(plugins,其他参数)
Vue.use()的一个原则就是执行对象的install这个方法
7. scoped
作用:限制作用域。
<style scoped>
/* scoped 限制此样式只作用当前组件 */
...
</style>
8. 浏览器本地存储
localStorage
特点:用户主动删除或清空缓存后数据才会消失
//存储数据
localStorage.setItem('key1','hello!!!') //字符串
localStorage.setItem('key2',666) //数字
localStorage.setItem('key3',JSON.stringify({name:'张三',age:18})) //对象
//读取数据
localStorage.getItem('key1') //如果key对应的value获取不到,那么 getltem的返回值是nul
localStorage.getItem('key2')
JSON.parse( localStorage.getItem('key3') ) //对象
//删除数据
localStorage.removeItem('key')
//清空数据
localStorage.clear()
sessionStorage
特点:浏览器一关闭,数据就没了。
//与上面相同
9. 组件的自定义事件
通过父组件给子组件绑定一个自定义事件实现(用于子给父传递数据):
//父组件
//第一种写法,使用@或v-on
<Child @my-event="doSomething"/>
methods:{
doSomething(param, ...params){ //...params:第一个参数给param,其他的赋给一个数组
}
}
//第二种写法,使ref
<Child ref="student"/>
mounted() {
//此处的this指向的是子组件实例
this.$refs.student.$on("atguigu", this.getStudentName); //绑定自定义事件
// this.$refs.student.$once('atguigu',this.getStudentName) //绑定自定义事件(一次性,只能触发一次)
},
//子组件
methods:{
//触发绑定的事件
doSomething(){
this.$emit('my-event',参数)
},
//解绑自定义事件
unbind(){
this.$off('my-event') //解绑一个
this.$off(['my-event1','my-event2']) //解绑多个自定义事件
this.$off() //解绑所有的自定义事件
},
//销毁了当前组件的实例,销毁后所有此组件实例的自定义事件全都不奏效。
death(){
this.$destroy()
}
}
绑定原生的DOM事件
以click为例
<Child @click.native="doSomething"/>
10. 全局事件总线
main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
el:'#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线
},
})
11. 消息订阅与发布
一种组件间通信的方式,适用于任意组件间通信。
步骤:
- 安装pubsub:
npm i pubsub-js
- 引入:
import pubsub from 'pubsub-js'
(pubsub是个对象) - 提供数据
methods: { sendStudentName(){ pubsub.publish('my-event',数据) //发布消息 } }
- 接收数据:A组件想接收数据,则在A组件中订阅消息
mounted() { this.pubId = pubsub.subscribe('my-event',(msgName,data)=>{...}) //订阅消息;第一个参数msgName是消息名,后面的参数才是数据 }
- 取消订阅
beforeDestroy() { pubsub.unsubscribe(this.pubId) }
12. $nextTick
在下一次DOM更新结束后(即整个视图都渲染完毕)再执行回调函数
vm.$nextTick(function(){
})
13. 过渡与动画
动画
<template>
<div>
<transition name="xxx" appear> //添加一个appear属性,一开始就有动画
<h1 v-show="isShow">...</h1>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
.xxx-enter-active{
animation: atguigu 0.5s linear;
}
.xxx-leave-active{
animation: atguigu 0.5s linear reverse;
}
//动画
@keyframes atguigu {
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>
过渡
<template>
<div>
<transition name="xxx" appear> //添加一个appear属性,一开始就有动画
<h1 v-show="isShow">...</h1>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
/* 进入的起点、离开的终点 */
.xxx-enter, .xxx-leave-to{
transform: translateX(-100%);
}
.xxx-enter-active, .xxx-leave-active{
transition: 0.5s linear;
}
/* 进入的终点、离开的起点 */
.xxx-enter-to, .xxx-leave{
transform: translateX(0);
}
</style>
v-enter
:进入的起点
v-enter-active
:进入过程中
v-enter-to
:进入的终点
v-leave
:离开的起点
v-leave-active
:离开过程中
v-leave-to
:离开的终点
多个元素过渡
<transition-group> </transition-group>
,且每个元素都要指定key
值。
使用动画库中的动画
https://animate.style/
先安装:npm install animate.css --save
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group
appear
//
name="animate__animated animate__bounce"
//进入动画
enter-active-class="animate__swing"
//离开动画
leave-active-class="animate__backOutUp"
>
<h1 v-show="!isShow" key="1">你好啊!</h1>
<h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>
</div>
</template>
<script>
//引入
import 'animate.css'
export default {
name:'Test',
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
//不需要写东西
</style>
14. 配置代理
跨域
跨域是“浏览器”对axios做出的同源策略的限制;请求发出去了,但是响应被拦截了。
同源是指协议、域名、端口都相同。
解决跨域问题:
cors
:服务器返回数据,携带一些特殊的响应头(是后端进行设置,前端什么都不用做)jsonp
:只能解决 get 跨域请求问题(利用< script>标签的src属性,通过指向一个需要访问的地址,由服务端返回一个预先定义好的 Javascript 函数的调用,并且将服务器数据以该函数参数的形式传递过来)- 代理服务器(端口号和浏览器的端口号相同):
注意:服务器和服务器之间的请求没有跨域限制
vue.config.js
module.exports = { pages: { index: { //入口 entry: 'src/main.js', }, }, lintOnSave:false, //关闭语法检查 //开启代理服务器(方式一) devServer: { proxy: 'http://localhost:5000' //此处端口号是请求的服务器的端口号 }, //开启代理服务器(方式二) devServer: { proxy: { //加上前缀就一定走代理 '/api': { //请求前缀,在发送请求的地方端口号后面加上此前缀:axios.get('http://localhost:8080/api/请求数据') target: 'http://localhost:5000', pathRewrite:{'^/api':''}, //将字符串’/api‘替换为'' // ws: true, //用于支持websocket // changeOrigin: true //用于控制请求头中的host值(即请求来自于哪里) }, '/api2': { target: 'http://localhost:5001', pathRewrite:{'^/api2':''}, // ws: true, //用于支持websocket // changeOrigin: true //用于控制请求头中的host值 } } } }
注意:方式一:只有本地没有的资源才会走代理,本地有的就不会把请求转发给服务器了;方式二可解决这两个问题
15. 插槽
默认插槽
让父组件可以向子组件指定位置插入html结构
//父组件中:
<Category>
<div>html结构1</div>
</Category>
//子组件Category中:
<template>
<div>
//定义插槽
<slot>插槽默认内容...</slot>
</div>
</template>
具名插槽
有多个插槽
//父组件中:
<Category>
<template slot="center"> //写法一(2.6.0已废弃),也可以不用template
<div>html结构1</div>
</template>
<template v-slot:footer> //写法二(2.6.0),但只有在template标签上才可以
<div>html结构2</div>
</template>
</Category>
//子组件Category中:
<template>
<div>
<!-- 定义插槽 -->
<slot name="center">插槽默认内容...</slot> //添加name属性
<slot name="footer">插槽默认内容...</slot>
</div>
</template>
作用域插槽
数据在子组件的自身,但根据数据生成的结构需要组件的使用者(父组件)来决定。(让父组件的插槽内容访问子组件中的数据)
//父组件中:
//必须用template
<Category>
<template scope="随意取名字"> //写法一
<ul>
<li v-for="g in 随意取名字.games" :key="g">{{g}}</li>
</ul>
</template>
</Category>
<Category>
<template slot-scope="随意取名字"> //写法二,在vue2.6.0已废弃
<h4 v-for="g in 随意取名字.games" :key="g">{{g}}</h4>
</template>
</Category>
<Category>
<template v-slot:default="随意取名字"> //vue2.6.0新写法
<h4 v-for="g in 随意取名字.games" :key="g">{{g}}</h4>
</template>
</Category>
//子组件Category中:
<template>
<div>
<slot :games="games"> </slot> //把"games"传给了插槽的使用者
</div>
</template>
<script>
export default {
name:'Category',
//数据在子组件自身
data() {
return {
games:['a','b','c','d']
}
},
}
</script>
三、Vuex
vuex是一个插件
概念:在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
1. 原理
2. 安装vuex
npm i vuex
(默认安装4版本)
npm i vuex@3
(安装3版本)
注意:vue2只能用vuex3版本;vue3要用vuex4版本
3.使用
创建文件:src/store/index.js
index.js:
//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//1.准备actions——用于响应组件中的动作
const actions = {
...
}
//2.准备mutations——用于操作数据(state)
const mutations = {
...
}
//3.准备state——用于存储数据
const state = {
...
}
//4.用于将state中的数据进行加工
const getters={
}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
对象中的key和value重名时,可以简写
:
{
actions:actions,
state:state,
}
可以简写为:
{
actions,
state,
}
main.js:
......
//引入store
import store from './store'
......
//创建vm
new Vue({
el:'#app',
render: h => h(App),
store
})
4. mapState,mapGetters,mapMutations,mapActions
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
computed: {
//借助mapState生成计算属性:sum、school、subject(对象写法)
...mapState({sum:'sum',school:'school',subject:'subject'}),
//借助mapState生成计算属性:sum、school、subject(数组写法)
...mapState(['sum','school','subject']),
},
computed: {
//借助mapGetters生成计算属性:bigSum(对象写法)
...mapGetters({bigSum:'bigSum'}),
//借助mapGetters生成计算属性:bigSum(数组写法)
...mapGetters(['bigSum'])
},
methods:{
//靠mapActions生成:incrementOdd、incrementWait(对象形式)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
//靠mapActions生成:incrementOdd、incrementWait(数组形式)
...mapActions(['jiaOdd','jiaWait'])
}
methods:{
//靠mapActions生成:increment、decrement(对象形式)
...mapMutations({increment:'JIA',decrement:'JIAN'}),
//靠mapMutations生成:JIA、JIAN(对象形式)
...mapMutations(['JIA','JIAN']),
}
注意:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
5. 模块化、命名空间
-
目的:让代码更好维护,让多种数据分类更加明确。
-
修改
index.js
const countAbout = {
namespaced:true,//开启命名空间
state:{x:1},
mutations: { ... },
actions: { ... },
getters: {
bigSum(state){
return state.sum * 10
}
}
}
const personAbout = {
namespaced:true,//开启命名空间
state:{ ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
countAbout,
personAbout
}
})
- 开启命名空间后,组件中读取state数据:
//方式一:自己直接读取
this.$store.state.personAbout.list
//方式二:借助mapState读取:
...mapState('countAbout',['sum','school','subject']),
- 开启命名空间后,组件中读取getters数据:
//方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])
- 开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
- 开启命名空间后,组件中调用commit
//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
四、路由
2. 安装vue-router
vue-router是一个插件
npm i vue-router
(默认安装4版本)
npm i vue-router@3
(安装3版本)
注意:vue2只能用vuex3版本;vue3要用vuex4版本
3. 使用
1. 编写router配置项:
//引入VueRouter
import VueRouter from 'vue-router'
//引入路由组件
import About from '../components/About'
import Home from '../components/Home'
//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
//暴露router
export default router
2. 实现切换(active-class可配置高亮样式)
(最终会转换成a标签)
<router-link active-class="active" to="/about">About</router-link>
3. 指定展示位置
<router-view></router-view>
4. 几个注意点
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息。 - 整个应用只有一个router,可以通过组件的
$router
属性获取到。
3.嵌套路由(多级路由)
1. 配置路由规则,使用children配置项:
routes:[
{
path:'/about',
component:About,
},
{
path:'/home',
component:Home,
children:[ //通过children配置子级路由
{
path:'news', //此处一定不要写:/news
component:News
},
{
path:'message',//此处一定不要写:/message
component:Message
}
]
}
]
2. 跳转(要写完整路径):
<router-link to="/home/news">News</router-link>
4.路由的query参数
1. 传递参数
<!-- 跳转并携带query参数,to的字符串写法 -->
<router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
<!-- 跳转并携带query参数,to的对象写法 -->
<router-link
:to="{
path:'/home/message/detail',
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
2. 接收参数:
$route.query.id
$route.query.title
5.命名路由
1. 作用:可以简化路由的跳转。
2. 使用:
//1.给路由命名:
{
path:'/demo',
component:Demo,
children:[
{
path:'test',
component:Test,
children:[
{
name:'hello' //给路由命名
path:'welcome',
component:Hello,
}
]
}
]
}
// 2. 简化跳转:
<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>
<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>
<!--简化写法配合传递参数 -->
<router-link
:to="{
name:'hello',
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
6.路由的params参数
1. 配置路由,声明接收params参数
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
component:Message,
children:[
{
name:'xiangqing',
path:'detail/:id/:title', //使用占位符声明接收params参数
component:Detail
}
]
}
]
}
2. 传递参数
<!-- 跳转并携带params参数,to的字符串写法 -->
<router-link :to="/home/message/detail/666/你好">跳转</router-link>
<!-- 跳转并携带params参数,to的对象写法 -->
<router-link
:to="{
name:'xiangqing',
params:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
特别注意:路由携带params参数时,若使用to的对象写法,则不允许使用path配置项,必须使用name配置!
3. 接收参数:
$route.params.id
$route.params.title
7.路由的props配置
作用:让路由组件更方便的收到参数
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// props:{a:900}
//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
// props:true
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props($route){
return {
id:$route.query.id,
title:$route.query.title
}
}
}
8. <router-link>
的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
. - 如何开启
replace
模式://添加属性replace <router-link replace>News</router-link>
9.编程式路由导航
-
作用:不借助
<router-link>
实现路由跳转,让路由跳转更加灵活 -
具体编码:
//以下函数都是写到methods中的 //$router的两个API this.$router.push({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.replace({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.forward() //前进 this.$router.back() //后退 this.$router.go() //可前进也可后退,可写参数(步数)
10. 缓存路由组件
-
作用:让不展示的路由组件保持挂载,不被销毁。
-
具体编码:
<!-- 缓存多个路由组件 --> <keep-alive :include="['News','Message']"> <!-- 缓存一个路由组件 --> <keep-alive include="News"> //此处是组件名 <router-view></router-view> </keep-alive>
11.两个新的生命周期钩子
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
- 具体名字:
activated(){...}
:路由组件被激活时触发。
deactivated(){...}
:路由组件失活时触发。
12.路由守卫
-
作用:对路由进行权限控制
-
分类:全局守卫、独享守卫、组件内守卫
-
全局守卫:
//全局前置守卫:初始化时执行、每次路由切换前执行 router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 meta:{isAuth:true} if(localStorage.getItem('school') === 'zjut'){ //权限控制的具体规则 next() //放行 }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() //放行 } }) //全局后置守卫:初始化时执行、每次路由切换后执行 router.afterEach((to,from)=>{ console.log('afterEach',to,from) if(to.meta.title){ //meta:{isAuth:true,title:'新闻'} document.title = to.meta.title //修改网页的title }else{ document.title = 'vue_test' } })
-
独享守卫:
beforeEnter(to,from,next){ console.log('beforeEnter',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'zjut'){ next() }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() } }
-
组件内守卫:
//进入守卫:通过路由规则,进入该组件时被调用 beforeRouteEnter (to, from, next) { if(to.meta.isAuth){ //判断是否需要鉴权 if(localStorage.getItem('school')==='zjut'){ next() }else{ alert('学校名不对,无权限查看!') } }else{ next() } }, //离开守卫:通过路由规则,离开该组件时被调用 beforeRouteLeave (to, from, next) { next() }
13. history模式与hash模式
hash:
http: ... /#/...
hash值不会作为地址的一部分传给服务器。
默认开启hash模式。
history:
没有 /#/
//引入VueRouter
import VueRouter from 'vue-router'
//引入Luyou 组件
import About from '../components/About'
import Home from '../components/Home'
//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
mode: 'history', //默认hash
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
//暴露router
export default router
14. Element-UI
三、Vue3.0
1. 创建实例对象
main.js
//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
//创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
const app = createApp(App)
//挂载;应用实例必须在调用了 .mount() 方法后才会渲染出来。该方法接收一个“容器”参数
app.mount('#app')
- vue3组件中的模板结构可以没有根标签;当根组件没有设置 template 选项时,Vue 将自动使用容器的 innerHTML 作为模板。
- .mount() 方法应该始终在整个应用配置和资源注册完成后被调用。它的返回值是根组件实例而非应用实例;
- 确保在挂载应用实例之前完成所有应用配置!
//应用实例会暴露一个 .config 对象允许我们配置一些应用级的选项,例如定义一个应用级的错误处理器,它将捕获所有由子组件上抛而未被处理的错误:
app.config.errorHandler = (err) => {
/* 处理错误 */
}