一、介绍:
二、new Vue之后做了什么
5. 初始化生命周期、事件(其实就是制定一些规则,如生命周期有哪些?叫什么?什么时候去调用?事件修饰符都是干啥的?)。但数据代理未开始,即new Vue时传入的data还没有出现在vm身上,vm连_data都还没有
6. 调用beforeCreate(),此时无法通过vm访问到data中的数据、methods中的方法。很正常,此时数据代理还没开始,vm连_data都还没有
7. 初始化数据监测(vue如何检测对象变化、数组变化,给对象中的属性匹配getter和setter,对操作数组的方法进行二次包装)、数据代理。此时有vm上有_data了
8. 调用created(),此时可以通过vm访问到data中的数据、methods中的方法。
注意:beforeCreate()和created()指的是数据监测和数据代理创建前、创建完成
,
,vm.$mount(el)
调用后,才去判断new Vue时是否传入template配置项。如果没传el配置项,也没调用vm.$mount(el)
,那么后面的步骤都不进行了判断new Vue时是否传入template配置项?
(a)传了template配置项,将template的内容编译为render函数
(b)没传template配置项,编译el的外部HTML就作为template,意思是root容器作为template,如果他说内部HTML作为template,就是指root容器中的内容作为template,二者的区别:
如果将root容器作为template,会解析root容器的:x得到
如果将root容器中的内容作为template,不会解析root容器的:x,上述代码不变
vm.$el
并替换el(el指root容器中的东西),在这一步,vue把刚才生成的虚拟DOM转成真实DOM,并用vm.$el
保存了一份真实DOM为什么要创建
vm.$el
并保存一份真实DOM:vue在进行新旧虚拟DOM比对时,万一有的元素/节点可以复用,那他必须有之前的元素/节点,他才能去复用,
一般在此阶段进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作
如果你在mounted()中操作DOM,就有一种感觉:你让vue先工作,生成虚拟DOM再转换成真实DOM,再把真实DOM挂在页面上了,人家工作完了,你反手一改,人家白忙活了,所以要尽可能避免对DOM的操作
以上就是挂载流程,不涉及新旧虚拟DOM比对,因为挂载时压根没有旧的虚拟DOM
以上就是更新流程
vm.$destroy()
被调用时,调用beforeDestroy(),此时vm中所有的data、methods、指令等等,都处于可用状态,马上要执行销毁过程,但是你在beforeDestroy()中对数据做的所有操作都不会触发更新。一般在此阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作
当
vm.$destroy()
被调用时,vm就会自己销毁自己,会解绑全部指令和【自定义】事件监听器,但是vm之前的工作成果还在,只是之后没人帮你管理了
以上就是销毁流程
三、关于销毁Vue实例
template的值是个字符串,把root容器中的内容全写在这个字符串里(注意:template只能有一个根节点,所以root容器中的内容要被包在一个div标签中,注意不能包在template标签中,因为template标签不能作为组件根元素,因为template标签中可能包含多个节点),配置了下面这个template,就不用在root容器中写代码了。但是这种写法,它会用template中的内容完全替换掉root容器,也就是说body标签中的内容就变成了template中的内容,没有了,那么这样,你原本配置的
:x="n"
也就没有了。
<html>
<head>
<meta charset="UTF-8" />
<title>分析生命周期title>
<script type="text/javascript" src="../js/vue.js">script>
head>
<body>
<div id="root" :x="n">div>
body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el:'#root',
template:`
当前的n值是:{{n}}
`,
data:{
n:1
},
methods: {
add(){
console.log('add')
this.n++
}
}
})
script>
html>
一、组件:实现应用中局部功能代码和资源的集合
二、组件分为:非单文件组件、单文件组件
三、Vue中使用组件的三大步骤:
Vue.extend(options)
创建,其中options
和new Vue(options)
时传入的那个options
几乎一样,但也有点区别,区别如下:备注:使用template可以配置组件结构。
new Vue
的时候传入components
选项Vue.component('组件名',组件)
<html>
<head>
<meta charset="UTF-8" />
<title>基本使用title>
<script type="text/javascript" src="../js/vue.js">script>
head>
<body>
<div id="root">
<hello>hello>
<hr>
<h1>{{msg}}h1>
<hr>
<school>school>
div>
<div id="root2">
<hello>hello>
div>
body>
<script type="text/javascript">
Vue.config.productionTip = false
//第一步:创建school组件:Vue.extend({配置对象})
const s = Vue.extend({
name:'atguigu',//在开发者工具中的名字
// 这个template和我们之前说的new Vue中的template配置项的用法一致
template:`
学校名称:{{schoolName}}
学校地址:{{address}}
`,
// el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
data(){// data一定要写成函数形式,并且是普通函数
return {
schoolName:'尚硅谷',
address:'北京昌平'
}
},
methods: {
showName(){
alert(this.schoolName)
}
},
})
//第一步:创建hello组件
const hello = Vue.extend({
template:`
你好啊!{{name}}
`,
data(){
return {
name:'Tom'
}
}
})
//第二步:全局注册组件:Vue.component('组件真正叫的名字',组件所在的变量名)
Vue.component('hello',hello)// 所有的vm都能用这个组件
//创建vm
new Vue({
el:'#root',
data:{
msg:'你好啊!'
},
//第二步:注册组件(局部注册)
components:{
// 这里面写键值对,键是组件真正叫的名字,值是你上面第一步定义的变量名(组件所在变量)
school: s,// 也就是说键名和你使用组件的标签名保持一致
}
})
new Vue({
el:'#root2',
})
script>
html>
几个注意点:
备注:
- 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
- 可以使用name配置项指定组件在开发者工具中呈现的名字。
备注:不用使用脚手架时,
会导致后续组件不能渲染。
const school = Vue.extend(options)
可简写为:const school = options
,对于简写方式,vue底层有个判断,在注册组件时,如果收到的是个对象,那么他会把这个对象作为参数传入Vue.extend()
,表面上你虽然没有调用Vue.extend()
,但实际底层vue帮你调用了。如果不是简写,那么在注册组件时,vue直接把Vue.extend(options)
拿过来用
<html>
<head>
<meta charset="UTF-8" />
<title>组件的嵌套title>
<script type="text/javascript" src="../js/vue.js">script>
head>
<body>
<div id="root">div>
body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
//定义student组件。school组件是student组件的父组件,注意要把student组件的定义放在school组件前面
//意思是:把子组件准备(定义)好了再去父组件那里注册
const student = Vue.extend({
name:'student',
template:`
学生姓名:{{name}}
学生年龄:{{age}}
`,
data(){
return {
name:'尚硅谷',
age:18
}
}
})
//定义school组件
const school = Vue.extend({
name:'school',
template:`
学校名称:{{name}}
学校地址:{{address}}
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
},
//注册组件(局部)。school组件是student组件的父组件,那么把student组件注册到school组件中。
//并且student组件只能在school组件中使用
components:{
student
}
})
//定义hello组件
const hello = Vue.extend({
template:`{{msg}}
`,
data(){
return {
msg:'欢迎来到尚硅谷学习!'
}
}
})
//定义app组件
/*
开发中会定义个app组件,用于管理应用中所有的组件,vm管理app组件,app组件管理别的组件
*/
const app = Vue.extend({
template:`
`,
components:{
school,
hello
}
})
//创建vm
new Vue({
template:' ',
el:'#root',
//注册组件(局部)
components:{app}
})
script>
html>
VueComponent
的构造函数,且不是程序员定义的,是Vue.extend
生成的。
或
,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)
。Vue.extend
,返回的都是一个全新的VueComponent!!!!new Vue(options)
配置中:data函数、methods中的函数、watch中的函数、computed中的函数,它们的this均是【Vue实例对象】。new Vue
接收相同的选项,仅有的例外是像el这样根实例特有的选项。也就是说,vc有的功能,vm都有,vm有的功能(能通过el决定为哪个容器服务)vc就没有VueComponent.prototype.__proto__ === Vue.prototype
,也就是说,vue内部将VueComponent.prototype.__proto__
指向了Vue.prototype
为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
<html>
<head>
<meta charset="UTF-8" />
<title>VueComponenttitle>
<script type="text/javascript" src="../js/vue.js">script>
head>
<body>
<div id="root">
<school>school>
<hello>hello>
div>
body>
<script type="text/javascript">
Vue.config.productionTip = false
//定义school组件。Vue.extend()的返回值是个VueComponent函数
const school = Vue.extend({
name:'school',
template:`
学校名称:{{name}}
学校地址:{{address}}
`,
data(){//data中的数据通过数据代理放在了VueComponent实例对象身上
return {
name:'尚硅谷',
address:'北京'
}
},
methods: {
showName(){
console.log('showName',this)// this指向VueComponent实例对象,
// 打印出来的VueComponent实例对象的内容和vue实例对象的一样
// 区别在于:1.vue实例对象有el,VueComponent实例对象没有,
// 2.vue实例对象的data是对象,VueComponent实例对象的data是函数,
// 3.vue实例对象使用new Vue()创建,VueComponent实例对象使用new VueComponent()创建的
}
},
})
const test = Vue.extend({
template:`atguigu`
})
//定义hello组件
const hello = Vue.extend({
template:`
{{msg}}
`,
data(){
return {
msg:'你好啊!'
}
},
components:{test}
})
// console.log('@',school)
// console.log('#',hello)
//创建vm
const vm = new Vue({
el:'#root',
components:{school,hello}
})
script>
html>
main.js作为入口文件,在此创建vue实例,管控组件leader
import App from './App.vue'
new Vue({
el:'#root',//vue接管root容器
template:` `,
components:{App},
})
vue接管root容器,所以还需要新建index.html文件,index.html文件里面存放root容器:
<html>
<head>
<meta charset="UTF-8" />
<title>练习一下单文件组件的语法title>
<script type="text/javascript" src="../js/vue.js">script>
head>
<body>
<div id="root">div>
body>
html>
单文件组件都以.vue为后缀,浏览器不能直接运行.vue文件,我们需要对其处理,让他变成最纯粹的js文件,浏览器就认识了。如何处理:1.自己使用webpack搭建一个工作流;2.vue脚手架
xx.vue命名规则和组件名命名规则一致
.vue文件中不能new Vue()
,因为.vue文件是组件,组件不是new Vue()
来的
组件:实现应用中局部功能代码和资源的集合。那么组件应该有js、HTML、css这三个结构,为此.vue文件设置了三个标签来分别存放这些内容
<template>组件的结构template>
<script>组件交互相关代码script>
<style>组件的样式style>
我们之前定义组件时,是这样写的:
const school = Vue.extend({
template:`// template中的内容放在template标签中
学校名称:{{schoolName}}
学校地址:{{address}}
`,
// 下面的都属于交互内容,放在script标签中
data(){
return {
schoolName:'尚硅谷',
address:'北京昌平'
}
},
methods: {
showName(){
alert(this.schoolName)
}
},
})
把这部分内容分别写在对应的标签中即可
<template>
<div class="demo">
<h2>学校名称:{{name}}h2>
<h2>学校地址:{{address}}h2>
<button @click="showName">点我提示学校名button>
div>
template>
<script>
export default {
name:'School',
data(){
return {
name:'尚硅谷',
address:'北京昌平'
}
},
methods: {
showName(){
alert(this.name)
}
},
}
script>
<style>
.demo{
background-color: orange;
}
style>
app组件用于汇总所有组件,相当于组件的leader,app组件由vm管控,因为.vue文件是组件,且不能被浏览器识别,所以我们需要新建main.js文件,在main.js中创建vue实例vm,让vm管控app组件
<template>
<div>
<School>School>
<Student>Student>
div>
template>
<script>
//1.引入组件
import School from './School.vue'
export default {
name:'App',
//2.注册组件
components:{
School
}
}
script>
vue-cli
npm install -g @vue/cli
,安装好了之后,就多了个vue
命令如果下载缓慢请配置npm淘宝镜像:
npm config set registry https://registry.npm.taobao.org
vue create xxx
本项目使用vue2
npm run serve
,执行该命令后,vue会直接运行main.js
.gitignore:git的忽略文件,哪些文件不想被git管理,在这里配好
babel.config.js:Babel的控制文件
package-lock.json和package.json:只要你打开的工程符合npm规范,那么一定有这两个文件,他们是包的说明书,前者是包版本控制/锁定文件。指令介绍:
"scripts": {
"serve": "vue-cli-service serve",/*开发时使用这个命令,让别人帮你配置服务器,帮你把东西都弄好*/
"build": "vue-cli-service build",/*代码写完了,功能开发完了,想把整个项目变成浏览器认识的文件*/
"lint": "vue-cli-service lint"/*对.js和.vue文件进行语法检查*/
},
main.js作为入口文件,我们在里面创建vue实例时,写的以下代码:
new Vue({
el:'#app',
render: h => h(App)
})
我们以前创建vue实例时,写的代码如下,如果我们还是写下面这个代码会报错,这是为什么呢?
new Vue({
el:'#app',
template:`你好啊
`,
components:{App},
})
因为我们引入vue(import Vue from 'vue'
)使用的是ES6模块化引入,此时引入的是..\node_modules\vue\dist\vue.runtime.esm.js
,该vue残缺了模板解析器,意味着没人给你解析new Vue()中的template配置项,你必须使用render函数。
完整版的vue是
..\node_modules\vue\dist\vue.js
。
如果你引入完整版的vue(import Vue from 'vue/dist/vue.js'
),在new Vue()时可以配置template,不用配置render。
引入残缺版vue,但是我还想配置内容,就要使用render函数,render函数是vue帮你调用的,render函数将App组件放入容器中,render函数必须有返回值,返回值就是你想渲染的具体内容,render函数接收一个参数createElement,该参数createElement是个函数,借助该函数createElement渲染具体的内容。createElement('标签名','标签体内容')
或createElement(组件名)
render(createElement){
return createElement('h1','你好啊')
}
没有用到this,可以写成箭头函数,箭头函数只有一个参数,可以省略(),只有一条语句并且是返回值,可以省略return和{},createElement是形参,可以用其他变量代替,如h,那么上面代码可以精简为:render: h => h('h1','你好啊')
。等同于:template':
你好啊
'
综上:
import Vue from 'vue'
import App from './App.vue'//引入App组件,它是所有组件的父组件
new Vue({
el:'#app',
render: h => h(App),// 这个App是个变量,他会去上面找,就找到了你上面引入的那个组件
})
import Vue from 'vue/dist/vue.js'
import App from './App.vue'//引入App组件,它是所有组件的父组件
new Vue({
el:'#app',
template:`你好啊
`,
components:{App},
})
完整版vue.js与残缺版vue.runtime.xxx.js的区别:
为什么要设计精简版vue:如果只有完整版vue的话,在vue源码中,模板解析器代码的体积占了vue的1/3,有天你代码写完了,需要交给webpack打包,webpack打包完了,会生成一个非常大的文件,该文件中肯定包含vue,也就包含模板解析器的代码,但此时乃至以后,都用不上模板解析器了,因为在开发时,模板解析器帮我们翻译模板,但现在我们的代码写完了,借助webpack已经可以把vue翻译成.js、.css、.html了,而且那些该解析的模板都解析完了,变成了浏览器认识的文件,此时模板解析器就没什么作用了,但是他还在打包后的文件中。所以就出现了精简版vue,打包后的文件能节约一些空间。
举一个生活中的例子:假如你家要装修,要铺瓷砖,你有以下两种方案:
Vue脚手架隐藏了所有webpack相关的配置,若想查看具体的webpack配置,请执行:vue inspect > output.js
如果你采用脚手架的默认配置,以下文件/文件夹名你不能改:public、index.html、src、main.js。但是如果你非要改,在package.json保存的地方新建vue.config.js(他们两个文件要同级),vue.config.js代码如下:
//vue最终会把vue.config.js输送给webpack,webpack是基于Node的,所以vue.config.js中使用commonJS。
module.exports = {
pages: {
index: {
entry: 'src/main.js',//入口
},
},
lintOnSave:false, //关闭语法检查
}
修改package.json后一定要重新npm run serve
。
其余配置参考官方配置参考。
其实,脚手架会把vue.config.js中的配置与webpack中已经写好的配置进行合并,某个配置如果vue.config.js中有,则使用vue.config.js的,否则使用webpack中已经写好的,这样程序员是碰不到核心文件的。
需求:点击按钮获取某个DOM元素。
vue给我们提供了ref属性,用于给元素或子组件注册引用信息(id的替代者),即给DOM节点打标识。用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
用法:
.....
或
this.$refs.xxx
VueComponent会把所有包含ref的节点收集到自身的$refs
属性中,如下图。$refs
的值是一个对象,键是ref的值,值是该ref的节点。
app.vue中代码如下:
<template>
<div>
<h1 v-text="msg" ref="title">h1>
<button ref="btn" @click="showDOM">点我输出上方的DOM元素button>
<School ref="sch"/>
<School id="sch"/>
div>
template>
<script>
//引入School组件
import School from './components/School'
export default {
name:'App',
components:{School},
data() {
return {
msg:'欢迎学习Vue!'
}
},
methods: {
showDOM(){
console.log(this.$refs.title) //真实DOM元素
console.log(this.$refs.btn) //真实DOM元素
console.log(this.$refs.sch) //School组件的实例对象VueComponent(vc)
console.log(document.getElementById('sch'))//School组件对应的完整的dom结构
}
},
}
script>
school.vue中代码如下:
<template>
<div class="school">
<h2>学校名称:{{name}}h2>
<h2>学校地址:{{address}}h2>
div>
template>
<script>
export default {
name:'School',
data() {
return {
name:'尚硅谷',
address:'北京·昌平'
}
},
}
script>
<style>
.school{
background-color: gray;
}
style>
使用ref和使用id的区别:对于传统的HTML标签来说,二者只有写法和用法上的区别,但是对于组件标签来说,
得到的是School组件的实例对象VueComponent。如下图
得到的是School组件对应的完整的dom结构,如下图
props:让组件接收外部传过来的数据。
父组件给子组件传数据:通过给子组件的标签写属性的方式传入数据:数据名="值"
,一定要加""
app.vue代码:
<template>
<div>
<Student name="李四" sex="女" :age="18"/>
div>
template>
<script>
import Student from './components/Student'
export default {
name:'App',
components:{Student}
}
script>
子组件使用props接收父组件传入的数据,使用props接收到的数据保存在了本组件实例对象身上,如果你在子组件实例对象身上的props中声明了一个变量,但是父组件没给你传该变量,那么多余变量的值为undefined
⚠️props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
Student.vue中代码:
<template>
<div>
<h1>{{msg}}h1>
<h2>学生姓名:{{name}}h2>
<h2>学生性别:{{sex}}h2>
<h2>学生年龄:{{myAge+1}}h2>
<button @click="updateAge">尝试修改收到的年龄button>
div>
template>
<script>
export default {
name:'Student',
data() {
console.log(this)
return {
msg:'我是一个尚硅谷的学生',
// 接收到的props不允许改,控制台会报错,但是还是能修改成功.那么如果我们想修改接收到的数据,
// 可以在data中定义个变量接收接收到的数据,后续改data中的变量即可
myAge:this.age
// name:'cara'//在data中定义个name变量,在props中定义个name变量用于接收父组件传过来的数据
// 两个name都会被放到组件实例对象上,会发生冲突,控制台会报错,props的name变量优先级更高,
// 以外部传进来的数据为主,也就是说props的变量优先被组件实例接收,优先被放在组件实例身上
}
},
methods: {
updateAge(){
this.myAge++
}
},
//如果你这里声明了一个变量,但是父组件没给你传,那么多余变量的值为undefined
// props:['name','age','sex']
//接收的同时对数据进行类型限制,props写成对象,里面是一对对的键值对,键是数据名,值是JS内置对象
/* props:{
name:String,
age:Number,
sex:String
} */
//接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
props:{
//required和default不能同时存在
name:{
type:String, //name的类型是字符串
required:true, //name是必要的
},
age:{
type:Number,
default:99 //默认值
},
sex:{
type:String,
required:true
}
}
}
script>
混入:可以把多个组件共用的配置提取成一个混入对象
需求:点击“姓名”,弹窗弹出姓名。
现在我们有两个组件School和Student,他们中间有些代码一样
那我们可以把这些一样的代码提取到一个文件中,在School和Student使用混入直接使用这些相同的代码。新建mixin.js,将相同代码提取并暴露。注意:此处为了后续演示,我们还添加了生命周期函数和data。
export const hunhe = {
methods: {
showName(){
alert(this.name)
}
},
mounted() {
console.log('你好啊!')
},
}
export const hunhe2 = {
data() {
return {
x:100,
y:200
}
},
}
混入原则:
有两种使用混入的方式:
<template>
<div>
<h2 @click="showName">学生姓名:{{name}}h2>
<h2>学生性别:{{sex}}h2>
div>
template>
<script>
import {hunhe,hunhe2} from '../mixin'//局部引入一个hunhe
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男'
}
},
mixins:[hunhe,hunhe2]
}
script>
import Vue from 'vue'//引入Vue
import App from './App.vue'//引入App
import {hunhe,hunhe2} from './mixin'//全局引入混合
//这样写,在你整个应用中,所有VC和VM都会得到这两个混合
Vue.mixin(hunhe)
Vue.mixin(hunhe2)
new Vue({//创建vm
el:'#app',
render: h => h(App)
})
在plugins.js中定义一个插件plugins,用于增强vue。插件的本质是对象,这个对象里面必须有install函数,install函数收到的第一个参数是vue构造函数Vue,之后的参数就是插件使用者传递进来的数据。
export default {
install(Vue,x,y,z){
console.log(x,y,z)
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})
//定义全局指令
Vue.directive('fbind',{
//指令与元素成功绑定时(一上来)
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
//指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
}
})
//定义混入
Vue.mixin({
data() {
return {
x:100,
y:200
}
},
})
//给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.hello = ()=>{alert('你好啊')}
}
}
使用插件时,在main.js中先引入再使用Vue.use()
应用插件,vue会帮你调用插件的install方法,并把vue构造函数作为第一个参数传入install方法。
import plugins from './plugins'//引入插件
//应用(使用)插件,一定要先应用插件,再创建vm.
Vue.use(plugins,1,2,3)
然后就可以使用插件了,如在组件中使用:
<template>
<div>
<h2>学校名称:{{name | mySlice}}h2>
<input type="text" v-fbind:value="name">
<button @click="test">点我测试一个hello方法button>
div>
template>
<script>
export default {
name:'School',
data() {
return {
name:'尚硅谷atguigu'
}
},
methods: {
test(){
this.hello()
}
},
}
script>
以前我们写style样式时,没有加scoped,此时在每个组件中写的样式都汇总在一起了,可能出现类名冲突,在这种情况下,后【import】引入的组件的样式会覆盖掉先引入的组件的样式。为了解决这个问题,style标签添加scoped,使得样式只在【本组件】起作用,不会在子组件起作用。
写法:
原理:给style标签添加scoped,vue会给最外层的div添加一定标签属性,属性的值时随机生成的,再配合标签属性选择器,就完成了控制指定的div的样式
⚠️脚手架解析vue文件时,先引入,再读取配置项,最后才解析模板。
style标签中还有lang属性,用于指定写样式的语言(不写这个属性默认指定css),如css、scss、less。但是vue脚手架无法处理css之外的样式语言,所以需要提前安装插件,如less-loader,但是可能出现兼容性问题,建议安装:npm i less-loader@7
在终端输入:
npm view less-loader versions
可以查看less-loader的【所有】版本
一、组件化编码流程:
二、props适用于:
三、使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
四、props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
五、数据在哪里,操作数据的方法就在哪里。
一、存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
二、浏览器端通过Window.sessionStorage
和Window.localStorage
属性来实现本地存储机制。
三、相关API:
xxxxxStorage.setItem('key', 'value')
:该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。xxxxxStorage.getItem('person')
:该方法接受一个键名作为参数,返回键名对应的值。xxxxxStorage.removeItem('key')
:该方法接受一个键名作为参数,并把该键名从存储中删除。xxxxxStorage.clear()
:该方法会清空存储中的所有数据。四、备注:
xxxxxStorage.getItem(xxx)
如果xxx对应的value获取不到,那么getItem的返回值是null。JSON.parse(null)
的结果依然是null。五、localstorage的应用:保存搜索记录
六、浏览器中【Session会话】指的是浏览器关闭
七、SessionStorage和LocalStorage的API一样,他们最大的区别就是前者会话结束内容消失,后者需要手动删除
八、LocalStorage存储的内容什么时候消失:
removeItem()
或clear()
<html>
<head>
<meta charset="UTF-8" />
<title>localStoragetitle>
head>
<body>
<h2>localStorageh2>
<button onclick="saveData()">点我保存一个数据button>
<button onclick="readData()">点我读取一个数据button>
<button onclick="deleteData()">点我删除一个数据button>
<button onclick="deleteAllData()">点我清空一个数据button>
<script type="text/javascript" >
let p = {name:'张三',age:18}
function saveData(){
/*
localStorage.setItem('键','值')。键值都必须是字符串,如果值不是字符串,他会帮你调用值的toString()
如果值是个对象,调完toString()就变成了[object Object]。
使用JSON.stringify(字符串)将对象变成字符串
*/
localStorage.setItem('msg','hello!!!')
localStorage.setItem('msg2',666)// 666.toString()
localStorage.setItem('person',JSON.stringify(p))
}
function readData(){
// localStorage.getItem('键')获取键对应的值,获取到的值是字符串,获取不到则返回null
console.log(localStorage.getItem('msg'))
console.log(localStorage.getItem('msg2'))
const result = localStorage.getItem('person')
console.log(JSON.parse(result))// JSON.parse(result)将字符串转成对象。JSON.parse(null)为null
// console.log(localStorage.getItem('msg3'))// null
}
function deleteData(){
// localStorage.removeItem('键')删除键对应的键值对
localStorage.removeItem('msg2')
}
function deleteAllData(){
localStorage.clear()// 清空
}
script>
body>
html>
一、内置事件是给HTML元素用的,自定义事件是给组件用的。
二、组件的自定义事件是一种组件间通信的方式,适用于:子组件 => 父组件
三、使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
四、绑定自定义事件:
或
<Demo ref="demo"/>
......
mounted(){
this.$refs.xxx.$on('atguigu',this.test)
}
五、若想让自定义事件只能触发一次,可以使用.once
修饰符,或$once
方法。
六、触发自定义事件:this.$emit('atguigu',数据)
七、解绑自定义事件this.$off('atguigu')
八、组件上也可以绑定原生DOM事件,需要使用.native
修饰符。
九、通过this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
需求:点击School组件的按钮,School组件将自己的数据name传给父组件App组件
思路:以前的做法(本组件代码):父组件给子组件传递一个函数,子组件调用该函数,并将数据作为参数传进去,父组件就能收到数据,在函数中进行相应的处理
App.vue中代码:
<template>
<div class="app">
<h1>{{msg}},学生姓名是:{{studentName}}h1>
<School :getSchoolName="getSchoolName"/>
div>
template>
<script>
import School from './components/School'
export default {
name:'App',
components:{School},
data() {
return {
msg:'你好啊!',
studentName:''
}
},
methods: {
getSchoolName(name){
console.log('App收到了学校名:',name)
}
}
script>
School.vue中代码:
<template>
<div>
<h2>学校名称:{{name}}h2>
<h2>学校地址:{{address}}h2>
<button @click="sendSchoolName">把学校名给Appbutton>
div>
template>
<script>
export default {
name:'School',
props:['getSchoolName'],
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
methods: {
sendSchoolName(){
this.getSchoolName(this.name)
}
},
}
script>
需求:点击Student组件的按钮,Student组件将自己的数据name传给父组件App组件
$emit
发送一个事件,数据放在事件的参数中,父组件使用@或v-on监听事件,监听到后调用某函数,数据会直接作为参数传入该函数
这行代码:@或v-on在Student这个组件标签上,所以是给Student这个组件的实例对象VueComponent身上绑定了一个事件,事件名叫atguigu,如果以后有人触发了该事件,那么getStudentName函数就会被调用。<template>
<div class="app">
<h1>{{msg}},学生姓名是:{{studentName}}h1>
<Student @atguigu="getStudentName"/>
div>
template>
<script>
import Student from './components/Student'
export default {
name:'App',
components:{School,Student},
data() {
return {
msg:'你好啊!',
studentName:''
}
},
methods: {
// 在Student组件中触发atguigu事件时传入了参数,在getStudentName函数中会收到那些参数
// name,...params:第一个参数正常接收,剩余参数全部接收到数组params中
getStudentName(name,...params){
console.log('App收到了学生名:',name,params)
this.studentName = name
}
}
}
script>
如何触发atguigu事件:你给谁绑定的事件,你就找谁去触发事件。Student.vue代码:
<template>
<div class="student">
<h2>学生姓名:{{name}}h2>
<h2>学生性别:{{sex}}h2>
<h2>当前求和为:{{number}}h2>
<button @click="sendStudentlName">把学生名给Appbutton>
div>
template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男',
number:0
}
},
methods: {
sendStudentlName(){
//触发Student组件实例身上的atguigu事件:this.$emit('想触发的事件名',后面的参数可选且都是数据)
this.$emit('atguigu',this.name,666,888,900)
}
}
}
script>
$emit
发送一个事件,数据放在事件的参数中。<template>
<div class="app">
<h1>{{msg}},学生姓名是:{{studentName}}h1>
<Student ref="student"/>
div>
template>
<script>
import Student from './components/Student'
export default {
name:'App',
components:{School,Student},
data() {
return {
msg:'你好啊!',
studentName:''
}
},
methods: {
getStudentName(name,...params){
console.log('App收到了学生名:',name,params)
this.studentName = name
}
},
mounted() {
//下面这行代码:App组件一挂载,就给组件中ref为student的标签绑定atguigu事件,
//当atguigu事件被触发时,调用this.getStudentName方法
this.$refs.student.$on('atguigu',this.getStudentName) //绑定自定义事件
// this.$refs.student.$once('atguigu',this.getStudentName) //绑定自定义事件(一次性)
/*
如果把第一行代码改为:直接把回调函数作为第二个参数传进来,回调函数是普通函数
this.$refs.student.$on('atguigu',function(name,...params){
console.log('App收到了学生名:',name,params)
console.log(this)//this指向触发事件的组件,即绑定事件的实例对象VueComponent,这里为Student实例对象
//vue底层的设计:谁触发的事件,事件回调中的this就指向谁
this.studentName = name
})
但是如果代码写为:this.$refs.student.$on('atguigu',this.getStudentName)
Student组件实例对象触发的atguigu事件,所以this.getStudentName中的this本应该指向Student组件实例对象,
但是getStudentName写在了methods中,且getStudentName是普通函数,那么getStudentName中的this指向本组件实例对象,
这样一看貌似this冲突了,其实vue以后者为主,getStudentName中的this指向本组件实例对象,即App组件实例对象
如果代码写为:直接把回调函数作为第二个参数传进来,回调函数是箭头函数
this.$refs.student.$on('atguigu',(name,...params)=>{
console.log('App收到了学生名:',name,params)
console.log(this)//this指向本组件实例对象,即App组件实例对象
this.studentName = name
})
箭头函数没有this,箭头函数的this是定义箭头函数时,所在的作用域指向的对象,这里是mounted函数,
vue中规定,生命周期函数是普通函数,那么生命周期函数中的this指向本组件实例对象,即App组件实例对象
*/
},
}
script>
如果把mounted中的第一行代码(this.$refs.student.$on('atguigu',this.getStudentName)
)改为如下,即直接把回调函数作为第二个参数传进来,回调函数是普通函数
this.$refs.student.$on('atguigu',function(name,...params){
console.log('App收到了学生名:',name,params)
console.log(this)//this指向触发事件的组件,即绑定事件的实例对象VueComponent,这里为Student实例对象
//vue底层的设计:谁触发的事件,事件回调中的this就指向谁
this.studentName = name
})
但是如果代码写为:this.$refs.student.$on('atguigu',this.getStudentName)
Student组件实例对象触发的atguigu事件,所以this.getStudentName中的this本应该指向Student组件实例对象,但是getStudentName写在了methods中,且getStudentName是普通函数,那么getStudentName中的this指向本组件实例对象,这样一看貌似this冲突了,其实vue以后者为主,getStudentName中的this指向本组件实例对象,即App组件实例对象
如果代码写为如下,即直接把回调函数作为第二个参数传进来,回调函数是箭头函数
this.$refs.student.$on('atguigu',(name,...params)=>{
console.log('App收到了学生名:',name,params)
console.log(this)//this指向本组件实例对象,即App组件实例对象
this.studentName = name
})
箭头函数没有this,箭头函数的this是定义箭头函数时,所在的作用域指向的对象,这里是mounted函数,vue中规定,生命周期函数是普通函数,那么生命周期函数中的this指向本组件实例对象,即App组件实例对象
方法二适用场景:你可以在组件挂载后5s或者等ajax请求的数据回来了,再给标签绑定事件,此时mounted中的方法改为如下:
setTimeout(() => {
this.$refs.student.$on('atguigu',this.getStudentName)
},5000)
方法一一上来就给标签绑定事件了,方法二比较灵活。
要想让自定义组件执行原生DOM点击事件,需要加事件修饰符
.native
,如:。vue在解析时发现
@click.native="show"
,知道是原生dom事件,于是他把这个click事件交给了Student组件最外层的元素,如果不加事件修饰符.native
的话,他会认为@click
是自定义事件
如何解绑:你给谁绑定的事件,你就找谁去解绑事件。
解绑一个自定义事件:this.$off('事件名称')
解绑多个自定义事件:this.$off(['事件名称1','事件名称2',...])
解绑所有的自定义事件:this.$off()
Student.vue代码:
<template>
<div class="student">
<h2>学生姓名:{{name}}h2>
<h2>学生性别:{{sex}}h2>
<h2>当前求和为:{{number}}h2>
<button @click="add">点我number++button>
<button @click="sendStudentlName">把学生名给Appbutton>
<button @click="unbind">解绑atguigu事件button>
<button @click="death">销毁当前Student组件的实例(vc)button>
div>
template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男',
number:0
}
},
methods: {
add(){
console.log('add回调被调用了')
this.number++
},
sendStudentlName(){
//触发Student组件实例身上的atguigu事件:this.$emit('想触发的事件名',后面的参数可选且都是数据)
this.$emit('atguigu',this.name,666,888,900)
},
unbind(){
this.$off('atguigu') //解绑一个自定义事件:this.$off('事件名称')
// this.$off(['atguigu','demo']) //解绑多个自定义事件:this.$off(['事件名称1','事件名称2',...])
// this.$off() //解绑所有的自定义事件:this.$off()
},
death(){
this.$destroy() //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效,
// 但是原生的DOM事件(点击事件click)还能用。
}
},
}
script>
注意:如果销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件(unbind、sendStudentlName)全都不奏效,但是原生的DOM事件(点击事件click)还能用,即点击【点我number++】按钮,还能调用add()触发number++
同理,如果VM被销毁了,他所有子组件都没了,所有子组件中的自定义事件也都被销毁了,但是原生的DOM事件(点击事件click)还能用。
一、全局事件总线是一种组件间通信的方式,适用于任意组件间通信。
二、安装全局事件总线:在main.js中创建vue实例时,将当前vue实例绑定到Vue.prototype.$bus
上
new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
......
})
三、使用事件总线:
$bus
绑定自定义事件,事件的回调留在A组件自身。methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}
this.$bus.$emit('xxxx',数据)
四、最好在beforeDestroy钩子中,用$off
去解绑当前组件所用到的事件。
需求:点击Student组件的按钮,把Student组件的name传给School组件。
import Vue from 'vue'//引入Vue
import App from './App.vue'//引入App
Vue.config.productionTip = false//关闭Vue的生产提示
new Vue({//创建vm
el:'#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线
},
})
<template>
<div class="student">
<h2>学生姓名:{{name}}h2>
<h2>学生性别:{{sex}}h2>
<button @click="sendStudentName">把学生名给School组件button>
div>
template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男',
}
},
methods: {
sendStudentName(){
this.$bus.$emit('hello',this.name)
}
},
}
script>
<template>
<div class="school">
<h2>学校名称:{{name}}h2>
<h2>学校地址:{{address}}h2>
div>
template>
<script>
export default {
name:'School',
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
mounted() {
this.$bus.$on('hello',(data)=>{
console.log('我是School组件,收到了数据',data)
})
},
beforeDestroy() {//组件销毁前解绑事件
this.$bus.$off('hello')
},
}
script>
一、消息订阅与发布是一种组件间通信的方式,适用于任意组件间通信。
二、思想:A组件想用C组件的消息,A组件是消息使用者,所以他订阅消息,C组件是消息提供者,所以他发布消息。A组件订阅名为demo的消息,并指定一个回调test。C组件会发布demo消息,发布时带上数据,此时A组件就会收到demo消息并调用test,数据作为参数传进了test函数中。
三、使用步骤:原生JS无法轻松实现消息订阅与发布,所以我们需要借助第三方库pubsub-js(你也可以用别的库,消息订阅与发布只是一个理念,有很多库把这个理念落到了实处),pubsub-js可以在任何框架中,轻松实现消息订阅与发布。
npm i pubsub-js
import pubsub from 'pubsub-js'
四、接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods(){
demo(data){......}
}
......
mounted() {
this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
}
五、提供数据:pubsub.publish('xxx',数据)
六、最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)
去取消订阅。
七、需求:点击Student组件的按钮,把Student组件的name传给School组件。
八、思路:School组件接收数据,Student组件发送数据,双方都要用到pubsub-js,在两个组件中引入pubsub-js,引入的pubsub-js是个对象,身上有很多有用的方法。
<template>
<div class="student">
<h2>学生姓名:{{name}}h2>
<h2>学生性别:{{sex}}h2>
<button @click="sendStudentName">把学生名给School组件button>
div>
template>
<script>
import pubsub from 'pubsub-js'
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男',
}
},
methods: {
sendStudentName(){
//发布消息:pubsub.publish('消息名',数据)
pubsub.publish('hello',666)
}
},
}
script>
<template>
<div class="school">
<h2>学校名称:{{name}}h2>
<h2>学校地址:{{address}}h2>
div>
template>
<script>
import pubsub from 'pubsub-js'// 引入pubsub-js,pubsub是个对象,身上有很多有用的方法
export default {
name:'School',
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
// methods: {
// pub(msgName, data) {
// console.log(this)// this指向本组件实例,即School组件
// }
// },
mounted() {
/*
订阅消息:pubsub.subscribe('消息名',回调函数)
回调函数接收两个参数,分别是消息名和数据(规定的)
每次订阅消息都会获得一个订阅ID,有点像定时器
this.pubId = pubsub.subscribe('hello',function(msgName,data){
console.log(this)// this指向undefined,因为你这里使用的是第三方库
})
也可以将回调函数抽离成方法放在methods中,订阅时传递方法名即可,此时回调函数的this依旧指向本组件实例,即School组件
this.pubId = pubsub.subscribe('hello',this.pub)
*/
this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
console.log(this)// this指向本组件实例,即School组件
console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
})
},
beforeDestroy() {
//通过ID取消订阅:pubsub.unsubscribe(ID)
pubsub.unsubscribe(this.pubId)
},
}
script>