Vue--CLI是Vue官方提供的标准化开发工具,全名是command line interface,国内称作脚手架,它是基于webpack配置的。
脚手架结构分析
main.js
文件,该文件是整个项目的入口文件。
javascript
// 引入Vue
import Vue from 'vue'
// 引入组件的管理者App父组件
import App from './App.vue'
// 关闭Vue的生产提示
Vue.config.productionTip = false
// 创建Vue实例对象
new Vue({
render: h => h(App),
}).$mount('#app')
webpack配置
Vue脚手架隐藏了所有webpack相关的配置,如果想查看具体配置,需要执行以下命令:
lua
vue inspect > output.js
vue.config.js
是一个可选的配置文件,开发者可以创建该文件用于自定义配置,比如配置语法检查关闭:
java
module.exports = {
// 关闭语法检查
lintOnSave: false
}
render函数
在脚手架生成的main.js
文件中引用的是运行版本的vue文件即vue.runtime.esm.js
,它没有模板解析器,所以无法使用template
配置项编写模板,采用render()
函数进行配置模板。
render()
函数 它有一个参数createElement
用于创建具体的模板内容,h1表示创建的元素,xxx表示元素的文本内容。
javascript
render(createElement){
return createElement('h1', 'xxx');
}
具体使用过程中因为没有使用this
,所以可以使用箭头函数简写,并且模板内容在App组件中已写好,所以直接传入组件参数App。
javascript
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#root')
组件之所以可以使用template模板,是因为有依赖(vue-template-compiler
)支持.vue
格式的文件的template模板解析。
mixins配置项
mixins
配置项 用于配置混入,混入是指多个组件共用的配置。
新建一个定义mixin.js文件,编写公用的配置并将其暴露。
javascript
export const mixin = {
methods: {
showName(){
console.log(this.name);
}
}
}
在组件中引入模块并使用mixins
配置项注册模块,这叫局部混入。如果混入的数据与组件本身数据重复或者冲突,则优先以组件本身数据为准,如果是生命周期钩子,则触发全部。
xml
<template>
<div>
<h2>{{ name }}</h2>
<h2>{{ age }}</h2>
<button @click="showName">点我显示名称</button>
</div>
</template>
<script>
import {mixin} from '../mixin.js'
export default {
name: 'HighStudent',
data(){
return{
name: '松风',
age: '18'
}
},
mixins: [mixin]
}
</script>
<style>
</style>
除了局部混入以外还可以采用全局混入,在main.js
文件中引入混入并调用。
dart
import {mixin2} from '../mixin'
Vue.mixin(mixin)
插件
Vue中的插件本质上是一个包含install(Vue, xxx)
方法的对象,该方法的第一个参数是Vue
,后面的参数是插件使用者传递的数据。
新建一个plugins.js文件用于定义插件,其中第一个参数代表Vue构造函数,还可以传入别的参数。所以可以实现很多功能,比如添加全局过滤器,全局指令,全局混入,添加实例方法等等。
javascript
export default{
install(Vue, x, y, z){
console.log('@@@install调用了');
Vue.mixin({
methods: {
showName(){
console.log(this.name);
}
},
mounted() {
console.log('混合')
},
})
}
}
在main.js
文件中引入插件并使用。
javascript
import plugins from './plugins';
Vue.use(plugins, 1, 2, 3);
本地存储
浏览器通过Window.localStorage和Window.seessionStorage属性实现本地存储机制
相关API
localStorage.setItem('key', 'value')
接收一个键和值作为参数。
localStorage.getItem('key')
接收一个键名作为参数,返回键值。
localStorage.removeItem('key')
接收一个键名作为参数,删除该键名键值。
localStorage.clear()
清空所有数据
- SeesionStorage存储的内容会随着浏览器窗口关闭而消失。
- LocalStorage存储的内容,需要手动清除才会消失。
- 如果value值不存在,会返回null。
- JSON.parse(null)的结果依然null。
xml
<h2>localStorage</h2>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我读取一个数据</button>
<script>
function saveData(){
localStorage.setItem('msg', 'hello');
}
function readData(){
console.log(localStorage.getItem('msg'));
}
</script>
父子组件访问
有时候我们需要父子组件互相直接访问,或者子组件访问跟组件。
父组件访问子组件
children
属性 不常用。
ref
标签属性 是Vue用于给元素或子组件注册引用信息(id的替代者)。
xml
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button @click="showDom">点我</button>
<MySchool ref="school"></MySchool>
</div>
</template>
通过$refs
属性获取对应的引用信息,应用在html标签上获取的就是真实DOM元素,应用在组件上获取的就是组件实例对象。
javascript
export default {
name: 'App',
components: {MySchool, MyStudent},
data(){
return {
msg: '欢迎学习Vue'
}
},
methods: {
showDom(){
console.log(this.$refs.title); // <h1>欢迎学习Vue</h1>
console.log(this.$refs.school); // VueComponent {_uid: 8, _isVue: true,...}
}
},
}
子组件访问父组件
$parent
和$root
父子组件通信
子组件不能够直接引用父组件或者Vue实例的数据,但是在开发中经常涉及到数据在上下级组件中传递的问题,父子组件通信的有两种,通过props向子组件传递数据,通过自定义事件向父组件发送数据。
父传子(props)
props
配置项用于父子组件之间通信,子组件接收的数据是只读
的并且和子组件本身的属性不能重名。 所以v-model
绑定的数据不能是props
传递的,因为props的数据不可修改。
但是如果传递的数据是对象类型
的,修改对象内部的属性不会报错,但不推荐这样做。
父组件在模板标签中传递数据name和age,注意不要使用驼峰命名,多个单词采用短横-
连接。
ruby
<MyStudent :name="name" :age="age"></MyStudent>
然后子组件中使用props配置项接收数据name和age,接收的方式有两种。
- 第一种仅接收数据
arduino
props: ['name', 'age']
- 第二种限制接收的数据类型
yaml
props: {
name: String,
age: Number
}
除了限制数据类型,还可以设置数据的必要性以及指定默认值,或者自定义验证函数。
type
required
default
javascript
props: {
name: {
type: String, // name的类型是字符串
required: true // name是必写的
},
age: {
type: Number,
default: 99 // age默认值是99
},
message: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default(){
return {message: 'HelloWorld'}
}
},
log: {
// 自定义验证函数
validator(value){
// 这里的value值就是log值
console.log(value);
// 传入的日志信息必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1;
}
}
}
props接收的数据优先级是高于子组件本身的属性的,所以如果需要修改接收的数据,可以在data中定义一个属性存储接收的数据。
xml
<script type='text/javascript' src='../js/vue.js'></script>
<div id='root'>
<cpn :num1="num1" :num2="num2"></cpn>
</div>
<template id="cpn">
<div>
<h2>{{number1}}</h2>
<input type="text" v-model="number1">
<h2>{{number2}}</h2>
<input type="text" v-model="number2">
</div>
</template>
<script>
Vue.config.productionTip = false;
const cpn = {
template: '#cpn',
props: {
num1: Number,
num2: Number
},
data(){
return {
// 定义两个属性用于存储props中的的属性
number1: this.num1,
number2: this.num2
}
}
}
const vm = new Vue({
el:'#root',
data: {
num1: 1,
num2: 2
},
components: {
cpn
}
})
</script>
子传父(自定义事件)
自定义事件是一种组件间通信的方式,适用于子组件传递数据给父组件,自定义事件流程主要是三步。
第一步在父组件中配置自定义事件逻辑。
javascript
// 在父组件中配置自定义事件逻辑。
methods: {
getSchoolName(name){
console.log('App组件收到了学校名', name);
},
getStudentName(name){
console.log('App组件收到了学生名', name);
}
},
第二步在父组件中给子组件实例对象绑定自定义事件,有两种方式。
- 在子组件实例对象中使用
v-on
或者@
绑定自定义事件(注意区分自定义事件名
和函数名
)。
xml
<template>
<div id="app">
<HighSchool v-on:getSchoolName="getSchoolName"></HighSchool>
<hr>
<HighStudent @getStudentName="getStudentName"></HighStudent>
</div>
</template>
- 在子组件实例对象上标记
ref
属性,在父组件中对自定义事件进行挂载或者条件绑定,这个方式灵活性更强一些,可以延迟绑定或者满足某种条件时再绑定。
xml
<template>
<div id="app">
......
<HighSchool ref="school"></HighSchool>
</div>
</template>
<script>
......
mounted(){
this.$refs.school.$on('getSchoolName', this.getSchoolName);
}
}
</script>
- 注意回调函数最好配置在methods中,如果直接传入函数则需要使用
箭头函数
,否则this指向会出现问题。若只想触发一次事件,可以使用once修饰符
,或$once方法
。
javascript
mounted() {
this.$refs.MyStudent.$on('getStudentName', (name)=>{
console.log('App收到了学生姓名:', name)
this.studentName = name;
});
// this.$refs.student.$once('getStudentMsg', this.getStudentMsg);
}
第三步子组件通过$emit()
方法触发自定义事件。
javascript
methods: {
sendSchoolName(){
this.$emit('getSchoolName', this.name)
}
}
this.$off(Event)
方法 解绑所有自定义事件,Event参数表示需要解绑的事件名,多个事件需要以数组形式传入。
父组件中也可以给子组件绑定原生DOM事件,但是需要使用native
修饰符,否则子组件会把事件当作自定义事件。
ini
<MyStudent ref="MyStudent" @click.native="show"></MyStudent>
全局事件总线(GlobalEventBus)
全局事件总线:一种组件通信方式,适用于任意组件间通信
。它的原则是谁接收数据,谁绑定事件。谁传递数据,谁触发事件。
首先安装全局事件总线,$bus
就是当前应用的vm。
javascript
new Vue({
......
beforeCreate() {
// 安装全局事件总线
Vue.prototype.$bus = this;
},
......
})
接收数据:A组件想接收数据,则在A组件中给$bus
绑定自定义事件,事件的回调留在A组件自身
。
javascript
mounted() {
this.$bus.$on('hello', (name) => {
console.log('我是School组件,收到了数据',name);
}
提供数据:B组件提供数据,则在B组件中调用自定义事件,本质上就是起到一个传参的作用。
javascript
methods: {
sendStudentName(){
this.$bus.$emit('hello', this.name);
}
},
最好在beforeDestroy
钩子中用$off
解绑当前组件所用到的
事件。
javascript
beforeDestroy() {
this.$bus.$off('hello');
},
消息订阅与发布(pubsub)
一种组件间通信方式,适用于任意组件间通信。
安装对应的模块
css
npm i pubsub-js
在需要发布和订阅消息的组件中引入pubsub
javascript
import pubsub from 'pubsub-js'
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
,每次订阅都会生成一个新的id。 回调中可以传递两个参数,第一个参数是事件名,第二个参数是数据。所以第一个参数可以用下划线_
占位。
javascript
mounted(){
this.pubId = pubsub.subscribe('hello', (msgName, data)=>{
console.log('有人发布了hello消息,hello消息的回调执行了!');
console.log(msgName, data);
})
},
提供数据:B组件发布消息。
javascript
methods: {
sendStudentMsg(){
pubsub.publish('hello', 666);
}
},
最好在beforeDestroy
钩子中,用pubsub.unsubscribe(this.pubId)
取消订阅。
插槽slot
组件的插槽是为了让我们封装的组件更加具有扩展性,就像我们的U盘硬盘一样。让使用者可以决定组件内部的一些内容到底展示什么。
默认插槽
在子组件中使用特殊的元素<slot>
就可以为子组件开启一个默认插槽,该插槽插入什么内容取决于父组件如何使用,除此之外为了方便复用插槽还可以设置默认内容。
xml
<script type='text/javascript' src='../js/vue.js'></script>
<div id='root'>
<cpn></cpn>
<cpn><span>HelloWorld</span></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
// 插槽的默认内容是button按钮
<slot><button>按钮</button></slot>
</div>
</template>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data: {
},
components: {
cpn: {
template: '#cpn'
}
}
})
</script>
具名插槽
当子组件的功能复杂时,子组件的插槽可能不止一个,父组件插入内容时,需要区分不同的插槽。这时,我们需要给插槽起一个名字,称之为具名插槽。
xml
<script type='text/javascript' src='../js/vue.js'></script>
<div id='root'>
<cpn></cpn>
// 让名为center的插槽内容替换如下
<cpn><span slot="center">HelloWorld</span></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是组件</h2>
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
</div>
</template>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data: {
},
components: {
cpn: {
template: '#cpn'
}
}
})
</script>
作用域插槽
数据在组件自身,但是根据数据生成的结构需要由组件的使用者决定。(games数据在category组件中,但是使用数据所遍历出来的结构由vm组件决定)。
slot-scope可以接收到插槽传递的所有数据,接收的数据是键值对的对象形式, :
{ "games": [ "魔兽世界", "艾尔登法环", "只狼", "DOTA2" ], "message": "hello" }
所以可以使用解构赋值的方式直接获取。
xml
<script type='text/javascript' src='../js/vue.js'></script>
<div id='root'>
<!-- 生成的是默认列表 -->
<category></category>
<!-- 生成的是有序列表 -->
<category>
<template slot-scope='gamesdata'>
<ol>
<li v-for="item in gamesdata.games">{{item}}</li>
</ol>
</template>
</category>
<!-- 使用解构赋值生成有序列表 -->
<category>
<template slot-scope='{games}'>
<ol>
<li v-for="item in games">{{item}}</li>
</ol>
</template>
</category>
</div>
<template id="category">
<div>
<h3>{{title}}分类</h3>
<!-- 给slot插槽传递games数据 message数据-->
<slot :games="games" message="hello">
<ul>
<li v-for="(item, index) in games" :key="index">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script>
Vue.config.productionTip = false;
const category = {
template: '#category',
// 数据在子组件自身
data(){
return {
title: '游戏',
games: ['魔兽世界', '艾尔登法环','只狼','DOTA2']
}
},
}
const vm = new Vue({
el:'#root',
components: {
category
}
})
</script>
nextTick
this.nextTick(callback)
函数 在下一次DOM更新结束后执行其指定的回调,当改变数据后,要基于更新后的新DOM进行某些操作时,可以在nextTick所指定的回调函数中执行,比如input框的自动聚焦。
javascript
......
// 处理待办事项的编辑
handleEdit(id){
//这里虽然完成了对虚拟DOM的操作,但是还没触发视图更新,如果直接进行文本框的聚焦,会导致聚焦失败。
this.$bus.$emit('editTodo', id);
// 所以通过nextTick()函数对更新后的DOM的进行聚焦操作。
this.$nextTick(()=>{
this.$refs.editInput.focus();
})
}
......
过渡和动画
在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名。
vue中的动画本质上还是对css的操控,比如transition和animation属性的设置。
Vue中的ajax
解决跨域问题有两种配置代理服务器的方法,一种是简单配置:
- 优点:配置简单,请求资源时直接发给8080即可。
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
- 工作方式:若按照下述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(优先匹配前端资源)。
css
devServer: {
// 请求转发给谁
proxy: 'http://localhost:6000'
}
第二种是配置具体的代理规则:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置比较繁琐,请求资源时必须加前缀。
ruby
devServer: {
proxy: {
'/api': { // 匹配所有以 'api'开头的请求路径
target: 'http://localhost:6000', // 代理目标的基础路径
pathRewrite: {'^/api': ''}, // 正则表达式将路径中的字符串api转换为空字符串
ws: true, // 用于支持websocket
changeOrigin: false, // 用于控制请求头中的host值
},
'/demo': { // 匹配所有以 'demo'开头的请求路径
target: 'http://localhost:5000', // 代理目标的基础路径
pathRewrite: {'^/demo': ''},
// ws: true,
changeOrigin: false,
},
}
}
/*
changeOrigin为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值是true
*/
前台数据
javascript
import axios from 'axios'
export default {
name: "App",
methods: {
getStudents(){
axios.get('http://localhost:8080/api/students').then(
response => {
console.log('请求成功了', response.data);
},
error => {
console.log('请求失败了', error.message);
}
)
},
getCars(){
axios.get('http://localhost:8080/democars').then(
response => {
console.log('请求成功了', response.data);
},
error => {
console.log('请求失败了', error.message);
}
)
}
},
};
后台数据
javascript
// 引入express模块
const express = require('express');
// 创建应用对象
const app = express();
app.use((request, response, next) => {
console.log('有人发送请求给服务器了......');
console.log('请求的资源是:', request.url);
console.log('请求来自于', request.get('HOST'));
next();
})
// 创建路由规则
app.all('/cars', (request, response) => {
// 设置响应头 设置允许跨域
response.setHeader('Access-Control-Allow-Origin', '*');
// 自定义响应头
response.setHeader('Access-Control-Allow-Headers', '*');
// 响应一个数据
const data = {
id: '001',
name: '奔驰',
price: 199
}
// 对对象进行字符串转换
let str = JSON.stringify(data);
// 设置响应体
response.send(str);
});
// 监听端口启动服务
app.listen(5000, () => {
console.log("服务已启动,5000端口监听中......")
})