1、分析脚手架
├── 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:包版本控制文件
2、render函数
//main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
el:"#app",
render(createElement){
return createElement('h1',"你好啊")
}
})
render是函数,能创建元素,再精简点
render: createElement=>createElement('h1',"你好啊")
new Vue({
el:"#app",
render:h=>h(App)
})
关于不同版本的Vue:
1.vue.js与vue.runtime.xxx.js的区别:
(1).vue.js是完整版的Vue,包含:核心功能+模板解析器。
(2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement
函数去指定具体内容。
3、默认配置
Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,
终端输入vue inspect > output.js
使用vue.config.js可以对脚手架进行个性化定制,详情见官网
module.exports = {
pages: {
index: {
//入口
entry: 'src/main.js',
},
},
lintOnSave:false//关闭语法检查
}
4、ref属性
1.获取原生dom元素原生里用id
来操作,vue里面用ref
,被用来给元素或子组件注册引用信息(id的替代者)
2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3. 使用方式:
- 打标识:
<h1 ref="xxx">.....</h1>
或<School ref="xxx"></School>
- 获取:
this.$refs.xxx
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="showDOM">点我</button>
<School></School>
<Student></Student>
</div>
</template>
<script>
//引入组件
import School from './components/School'
import Student from './components/Student'
export default {
name:'App',
data(){
return{
msg:"2021/9/14"
}
},
components:{
School,
Student
},
methods:{
showDOM:function(){
console.log(this.$refs.title);
console.log(this.$refs.btn);
}
}
}
</script>
5、props配置
- 功能:让组件接收外部传过来的数据
- 传递数据:
<Demo name="xxx"/>
- 接收数据:
- 第一种方式(简单声明接收):
props:['name','age','sex']
- 第二种方式(限制数据类型):
- 第一种方式(简单声明接收):
props:{
name:String,
age:Number,
sex:String
}
- 第三种方式(限制类型、限制必要性、指定默认值):
props:{
name:{
type:String, //类型
required:true, //必要性
default:'老王' //默认值
}
}
示例
//app.vue
<template>
<div>
<Student name="李四" sex="女" :age="18+1"></Student>
</div>
</template>
<script>
//引入组件
import Student from './components/Student'
export default {
name:'App',
components:{
Student
}
}
</script>
//student.vue
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>
<script>
export default {
name:'Student',
props:['name','age','sex']
}
</script>
动态绑定,计算双引号里面的值
<Student name="李四" sex="女" :age="18+1"></Student>
<Student name="李四" sex="女" age="18+1"></Student>
如果组件中也定义了相同名称的数据
<script>
export default {
name:'Student',
data(){
return{
age:12
}
},
props:{
name:String,
age:Number,
sex:String
}
}
</script>
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告。
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
<button @click="showAge">点我</button>
</div>
</template>
<script>
export default {
name:'Student',
methods:{
showAge:function(){
this.age++
}
},
//props:['name','age','sex'],//简单接收
props:{
name:String,
age:Number,
sex:String
}
}
</script>
若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{thisAge}}</h2>
<button @click="showAge">点我</button>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return{
thisAge:this.age
}
},
methods:{
showAge:function(){
this.thisAge++
}
},
//props:['name','age','sex'],//简单接收
props:{
name:String,
age:Number,
sex:String
}
}
</script>
Cate.vue
<template>
<div class="cate">
<h3>title</h3>
<ul>
<li v-for="item in listData">{{item}}</li>
</ul>
</div>
</template>
<script>
export default {
name:"Cate",
props:["listData","title"]
}
</script>
<style>
.cate{
background-color:skyblue;
width: 200px;
height: 300px;
}
h3{
text-align: center;
}
</style>
//App.vue
<template>
<div class="container">
<Cate :listData="foods" title="foods"/>
<Cate :listData="games" title="games"/>
<Cate :listData="films" title="films"/>
</div>
</template>
<script>
import Cate from './components/Cate'
export default {
name:'App',
components:{
Cate
},
data(){
return{
foods:["火锅","烧烤","炸鸡","泡面"],
games:["超级玛丽","奥比岛","洛克王国","摩尔庄园"],
films:["教父","峰暴","一天","巧克力工厂"],
}
}
}
</script>
<style>
.container{
display: flex;
justify-content: space-around;
}
</style>
6、mixin混入
多个组件共享一个配置
- 功能:可以把
多个组件共用的配置
提取成一个混入对象 - 使用方式:
第一步定义混合:
{
data(){....},
methods:{....}
....
}
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{sex}}</h2>
<button @click="showName">点我</button>
</div>
</template>
<script>
import {mixin}from'../mixin.js'
export default {
name:'Student',
data(){
return{
name:"张三",
sex:"男"
}
},
mixins:[mixin]
}
</script>
//minin.js
export const mixin={methods:{
showName:function(){
console.log(this.name)
}
}}
第二步使用混入:
全局混入:Vue.mixin(xxx)
在main.js中写入
局部混入:mixins:['xxx']
在组件中写
7、插件
- 功能:用于增强Vue
- 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
- 定义插件:
//plugins.js
对象.install = function (Vue, options) {
// 1. 添加全局过滤器
Vue.filter(....)
// 2. 添加全局指令
Vue.directive(....)
// 3. 配置全局混入(合)
Vue.mixin(....)
// 4. 添加实例方法
Vue.prototype.$myMethod = function () {...}
Vue.prototype.$myProperty = xxxx
}
- 使用插件:
Vue.use()
//main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
import plugins from './plugins'
//关闭Vue的生产提示
Vue.config.productionTip = false
//应用(使用)插件
Vue.use(plugins,1,2,3)
//创建vm
new Vue({
el:'#app',
render: h => h(App)
})
8、scoped样式
- 作用:让样式在局部生效,防止冲突。
- 写法:
<style scoped>
- 写在App.vue不太适用
安装less-loader
npm i less-loader@7.2.3
查看插件版本
npm view less-loader versions
<style lang="less" scoped>
.demo{
background-color: pink;
.atguigu{
font-size: 40px;
}
}
</style>
9、浏览器缓存
- 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
- 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
- 相关API:
xxxxxStorage.setItem('key', 'value');
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
JSON.stringify
将 JavaScript 值转换为 JSON 字符串
JSON.parse
将数据转换为 JavaScript 对象
xxxxxStorage.getItem('person');
该方法接受一个键名作为参数,返回键名对应的值。
xxxxxStorage.removeItem('key');
该方法接受一个键名作为参数,并把该键名从存储中删除。
xxxxxStorage.clear()
该方法会清空存储中的所有数据。 - 备注:
- SessionStorage存储的内容会随着浏览器窗口关闭而消失。
- LocalStorage存储的内容,需要手动清除才会消失。
xxxxxStorage.getItem(xxx)
如果xxx对应的value获取不到,那么getItem的返回值是null。JSON.parse(null)
的结果依然是null。
10、组件自定义事件
通过父组件给子组件传递函数类型的props实现:子给父传递数据
//School.vue
<template>
<div>
<h2>学校:{{name}}</h2>
<h2>地址:{{address}}</h2>
<button @click="sendSchoolName">点我</button>
</div>
</template>
<script>
export default {
name:'School',
data(){
return{
name:"一中",
address:"广州"
}
},
props:['getSchoolName'],
methods: {
sendSchoolName(){//传递过来了以后要调用
this.getSchoolName(this.name)
}
},
}
</script>
//App.vue
<template>
<div>
<Student></Student>
<hr>//通过父组件给子组件传递函数类型的props实现:子给父传递数据
<School :getSchoolName="getSchoolName"></School>
</div>
</template>
<script>
//引入组件
import Student from './components/Student.vue'
import School from './components/School.vue'
export default {
name:'App',
components:{
Student,
School
},
methods:{
getSchoolName:function(name){
console.log("App收到了name",name);
},
}
}
</script>
通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on)
//App.vue
<template>
<div>
<Student @dianji="getStudentName"/>//父组件给子组件绑定一个自定义事件实现
</div>
</template>
<script>
//引入组件
import Student from './components/Student.vue'
export default {
name:'App',
components:{
Student,
},
methods:{
getStudentName(name,...params){
console.log('App收到了学生名:',name,params)
this.studentName = name
},
}
}
</script>
//Student.vue
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{sex}}</h2>
<button @click="sendStudentlName">点我</button>
</div>
</template>
<script>
import {mixin}from'../mixin.js'
export default {
name:'Student',
data(){
return{
name:"张三",
sex:"男"
}
},
methods:{
sendStudentlName(){
//需要绑定点击触发
//触发Student组件实例身上的dianji事件
//子给父传递数据
this.$emit('dianji',this.name,666,888,900)
},
}
}
</script>
通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref
//App.vue
<template>
<div>
<Student ref="student"/>
</div>
</template>
<script>
//引入组件
import Student from './components/Student.vue'
import School from './components/School.vue'
export default {
name:'App',
components:{
Student,
School
},
methods:{
getStudentName(name,...params){
console.log('App收到了学生名:',name,params)
this.studentName = name
},
},
mounted() {
//给student标签绑定一个点击事件,然后这个事件会调用方法
this.$refs.student.$on('dianji',this.getStudentName) //绑定自定义事件
// this.$refs.student.$once('atguigu',this.getStudentName) //绑定自定义事(一次性)
},
}
</script>
//Student.vue
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{sex}}</h2>
<button @click="sendStudentlName">点我</button>
</div>
</template>
<script>
import {mixin}from'../mixin.js'
export default {
name:'Student',
data(){
return{
name:"张三",
sex:"男"
}
},
methods:{
sendStudentlName(){
//触发Student组件实例身上的dianji事件
this.$emit('dianji',this.name,666,888,900)
},
}
}
</script>
一个是通过vue的@绑定自定义事件,一个是通过ref属性获取到dom元素然后绑定自定义事件
- 若想让自定义事件只能触发一次,可以使用
once
修饰符,或$once
方法。 - 触发自定义事件:
this.$emit('dianji',数据)
- 解绑自定义事件
this.$off('dianji')
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{sex}}</h2>
<button @click="sendStudentlName">点我输出</button>
<button @click="unbind">点我解绑</button>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return{
name:"张三",
sex:"男"
}
},
methods:{
sendStudentlName(){
//触发Student组件实例身上的dianji事件
this.$emit('dianji',this.name,666,888,900)
},
unbind()
{
this.$off("dianji")//解绑一个事件
this.$off(["dianji","demo"])//解绑多个事件
this.$off()//解绑所有事件
}
},
}
</script>
如果调用this.destory,那么所有自定义属性都会被解绑,因为绑定的实例对象已经被销毁了
4. 组件上也可以绑定原生DOM事件,需要使用native
修饰符,不然会默认认为绑定的是自定义事件。
<Student ref="student" @click.native="方法名"></Student>
- 注意:通过
this.$refs.xxx.$on('dianji',回调函数)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
11、全局事件总线
- 一种组件间通信的方式,适用于任意组件间通信。
- 安装全局事件总线:
//App.vue
new Vue({
......
beforeCreate() {//this指向这个vue实例对象
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
......
})
- 使用事件总线:
- 接收数据:A组件想接收数据,则在A组件中给
$bus
绑定自定义事件,事件的回调留在A组件自身。
- 接收数据:A组件想接收数据,则在A组件中给
methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}
- 提供数据:
this.$bus.$emit('xxxx',数据)
- 最好在
beforeDestroy
钩子中,用$off
去解绑当前组件所用到的事件。
实现Student、School组件间的相互通信,Student组件传递信息给School组件,School组件绑定自定义方法,然后Student组件emit触发,并且传递参数进行通信,
//main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
//创建vm
new Vue({
el:'#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线
},
})
//Student.vue
<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:'男',
}
},
mounted() {
// console.log('Student',this.x)
},
methods: {
sendStudentName(){
this.$bus.$emit('hello',this.name)
}
},
}
</script>
//School.vue
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>
<script>
export default {
name:'School',
data() {
return {
name:'二中',
address:'北京',
}
},
mounted() {
// console.log('School',this)
this.$bus.$on('hello',(data)=>{
console.log('我是School组件,收到了数据',data)
})
},
beforeDestroy() {
this.$bus.$off('hello')
},
}
</script>
12、消息的订阅和发布
- 一种组件间通信的方式,适用于任意组件间通信。
- 使用步骤:
- 安装pubsub:
npm i pubsub-js
- 引入:
import pubsub from 'pubsub-js'
- 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
- 提供数据:
pubsub.publish('xxx',数据)
- 最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)
去取消订阅。
- 安装pubsub:
School组件订阅然后Student组件发布信息实现组件间的通信
语法:
(1) import PubSub from ‘pubsub-js’ // 引入
(2) PubSub.subscribe(‘msgName’, functon(msgName, data){ })
(3) PubSub.publish(‘msgName’, data): 发布消息, 触发订阅的回调函数调用
(4) PubSub.unsubscribe(token): 取消消息的订阅
//School.vue
<template>
<div>
<h2>学校:{{name}}</h2>
<h2>地址:{{address}}</h2>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name:'School',
data(){
return{
name:"一中",
address:"广州"
}
},
mounted(){
this.pubId =pubsub.subscribe('hello',(a,b)=>{
console.log("11111111111",a,b)
})
},
beforeDestroy() {
pubsub.unsubscribe(this.pubId)//取消订阅
},
}
</script>
//Student.vue
<template>
<div>
<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:"男"
}
},
mounted(){
},
methods:{
sendStudentName(){
pubsub.publish('hello',this.name)
}
}
}
</script>
13、this.$nextTick
- 语法:
this.$nextTick
(回调函数) - 作用:在下一次 DOM 更新结束后执行其指定的回调。
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
<template>
<button ref="tar"
type="button"
name="button"
@click="testClick">{{content}}</button>
</template>
<script>
export default {
data () {
return {
content: '初始值'
}
}
methods: {
testClick(){
this.content = '改变了的值'
// 这时候直接打印的话,由于dom元素还没更新
// 因此打印出来的还是未改变之前的值
console.log(this.$refs.tar.innerText) // 初始值
}
}
}
</script>
this.$nextTick
这个方法作用是当数据被修改后使用这个方法会回调获取更新后的dom再渲染出来
methods:{
testClick() {
this.content = '改变了的值'
let that = this
this.$nextTick(() => {
// dom元素更新后执行,因此这里能正确打印更改之后的值
console.log(that.$refs.tar.innerText) // 改变了的值
})
}
}
14、插槽
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于父组件 ===> 子组件
14.1、默认插槽
如果组件标签里没有插入内容的时候,不会显示
如果有插入内容,会显示
父组件中:
//App.vue
<Category>
<div>html结构1</div>
</Category>
当使用的组件标签中插入内容时,插入的内容会被放到组件的slot标签中
子组件中:
//Category.vue
<template>
<div>
<!-- 定义插槽 -->
<slot>我是默认内容</slot>
</div>
</template>
像下面组件标签中加入了图片标签
//App.vue
<template>
<div class="container">
<Category :listData="foods" title="foods">
<img src="https://z3.ax1x.com/2021/09/25/4rtPMV.png" alt="">
</Category>
</div>
</template>
如果组件标签内没有没有放标签
//App.vue
<template>
<div class="container">
<Category :listData="foods" title="foods">
</Category>
</div>
</template>
组件标签中没有插入其他标签
14.2、具名插槽
具名插槽则是当子组件需要显示不同的效果时使用具名插槽,通过name属性给插槽命名。
父组件中:
写法一:
//App.vue
<Category>
<template slot="center">
<div>html结构1</div>
</template>
</Category>
写法二:
//App.vue
<Category>
<template v-slot:footer>
<div>html结构2</div>
</template>
</Category>
子组件中:
//Category.vue
<template>
<div>
<!-- 定义插槽 -->
<slot name="center">插槽默认内容...</slot>
<slot name="footer">插槽默认内容...</slot>
</div>
</template>
具体代码
//Cate.vue
<template>
<div class="cate">
<h3>title</h3>
<slot name="center">我是默认值1111</slot>
<slot name="footer">我是默认值2222</slot>
</div>
</template>
<script>
export default {
name:"Cate",
props:["listData","title"]
}
</script>
<style>
.cate{
background-color:skyblue;
width: 200px;
height: 300px;
}
h3{
text-align: center;
}
</style>
//App.vue
<template>
<div class="container">
<Cate title="foods">
<img slot="center" src="https://z3.ax1x.com/2021/09/25/4rtPMV.png" alt="">
<template v-slot:footer>
<h4>更多图片</h4>
</template>
</Cate>
</div>
</template>
<script>
import Cate from './components/Cate'
export default {
name:'App',
components:{
Cate
},
data(){
return{
foods:["火锅","烧烤","炸鸡","泡面"],
games:["超级玛丽","奥比岛","洛克王国","摩尔庄园"],
films:["教父","峰暴","一天","巧克力工厂"],
}
}
}
</script>
<style>
.container{
display: flex;
justify-content: space-around;
}
img{
width: 100%;
}
</style>>
14.3、作用域插槽
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。
需要组件的使用者收到了数据,怎么使用取决于组件的使用者
(games数据在Cate组件中,但使用数据所遍历出来的结构由App组件决定)
作用域插槽语法,必须要使用template
<template scope="scopeData">
{{scopeData}}
</template>
//App.vue
<template>
<div class="container">
<Cate>
<template scope="scopeData">
{{scopeData}}
</template>
</Cate>
</div>
</template>
<script>
import Cate from './components/Cate'
export default {
name:'App',
components:{
Cate
}
}
</script>
//Cate.vue
<template>
<div class="cate">
<h3>{{title}}</h3>
<slot :games="games">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot>
</div>
</template>
<script>
export default {
name:"Cate",
props:["title"],
data(){
return{
games:["超级玛丽","奥比岛","洛克王国","摩尔庄园"],
}
}
}
</script>
<template scope="scopeData">
{{scopeData}}
</template>
这个收到是一个对象,就是下面图所示,我们需要的是.games
<template scope="scopeData">
{{scopeData.games}}
</template>