Vue脚手架

1.初始化脚手架

1.1 说明

  1. Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)。

  2. 最新的版本是 5.x。

  3. 文档: https://cli.vuejs.org/zh/。

1.2 具体说明

第一步(仅第一次执行):全局安装@vue/cli。

npm install -g @vue/cli

第二步:切换到你要创建项目的目录,然后使用命令创建项目

vue create xxxx

第三步:启动项目

npm run serve

1.出现下载缓慢请配置 npm 淘宝镜像:npm config set registry https://registry.npm.taobao.org

2.Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,

请执行:vue inspect > output.js

1.3 模板项目结构

├── 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.ref 属性

2.1 ref介绍

  1. 被用来给元素或子组件注册引用信息(id的替代者)
  2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
  3. 使用方式:
    1. 打标识:<h1 ref="xxx">.....</h1><School ref="xxx"></School>
    2. 获取:this.$refs.xxx

代码示例

<template>
	<div>
		<h1 v-text="msg" ref="title"></h1>
		<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
		<School ref="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组件的实例对象(vc)
			}
		},
	}
</script>

3.props 属性

3.1 props介绍

1.作用:用于父组件给子组件传递数据

2.读取方式一: 只指定名称

props: ['name', 'age', 'setName'] 

3.读取方式二: 指定名称和类型

props: {

name: String, 

age: Number, 

setNmae: Function 

} 

4.读取方式三: 指定名称/类型/必要性/默认值

props: {
	name: {
    	type: String, 
   	 	required: true, 
    	default:xxx
	}, 
} 

type 可以是下列原生构造函数中的一个:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

额外的,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。例如,给定下列现成的构造函数:

function Person (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

你可以使用:

Vue.component('blog-post', {
  props: {
    author: Person
  }
})

来验证 author prop 的值是否是通过 new Person 创建的。

备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

3.2 props大小写

HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:

Vue.component('blog-post', {
  // 在 JavaScript 中是 camelCase 的
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>

重申一次,如果你使用字符串模板,那么这个限制就不存在了。

3.3 props类型

通常你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}

3.4 单向数据流

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

注意:在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。

4.Mixins 属性

4.1 Mixin定义

官方解释:

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

定义mixin也非常简单,它就是一个对象而已,只不过这个对象里面可以包含Vue组件中的一些常见配置,如data、methods、created等等。

我们在mixin中简单的写点东西,代码如下:

export const mixins = {
  data() {
    return {
      msg: "我是小猪课堂",
    };
  },
  computed: {},
  created() {
    console.log("我是mixin中的created生命周期函数");
  },
  mounted() {
    console.log("我是mixin中的mounted生命周期函数");
  },
  methods: {
    clickMe() {
      console.log("我是mixin中的点击事件");
    },
  },
};

全局混入:Vue.mixin(xxx) 局部混入:mixins:['xxx']

4.2 局部混入

顾名思义,局部混入和组件的按需加载有点类似,就是需要用到mixin中的代码时,我们再在组件章引入它。全局混入的话,则代表我在项目的任何组件中都可以使用mixin。

组件中引入mixin也非常简单,我们稍微改造下App.vue组件。

代码如下:

// src/App.vue
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png" />
    <button @click="clickMe">点击我</button>
  </div>
</template>

<script>
import { mixins } from "./mixin/index";
export default {
  name: "App",
  mixins: [mixins],
  components: {},
  created(){
    console.log("组件调用minxi数据",this.msg);
  },
  mounted(){
    console.log("我是组件的mounted生命周期函数")
  }
};
</script>

上段代码中引入mixin的方法也非常简单,直接使用vue提供给我们的mixins属性:mixins:[mixins]

  • mixin中的生命周期函数会和组件的生命周期函数一起合并执行。
  • mixin中的data数据在组件中也可以使用。
  • mixin中的方法在组件内部可以直接调用。
  • 生命周期函数合并后执行顺序:先执行mixin中的,后执行组件的。

4.3 全局混入

我们也可以在全局先把它注册好,这样我们就可以在任何组件中直接使用了。

修改main.js,代码如下:

import Vue from "vue";
import App from "./App.vue";
import { mixins } from "./mixin/index";
Vue.mixin(mixins);

Vue.config.productionTip = false;

new Vue({
  render: (h) => h(App),
}).$mount("#app");

虽然这样做很方便,但是我们不推荐,来看看官方的一段话:

请谨慎使用全局混入,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件)。大多数情况下,只应当应用于自定义选项,就像上面示例一样。推荐将其作为插件open in new window发布,以避免重复应用混入。

5.插件

5.1 开发插件

Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或 property
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }
    ...
  })

  // 3. 注入组件选项
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
    ...
  })

  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}

5.2 使用插件

通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成:

// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)

new Vue({
  // ...组件选项
})

也可以传入一个可选的选项对象:

Vue.use(MyPlugin, { someOption: true })

Vue.use 会自动阻止多次注册相同插件,届时即使多次调用也只会注册一次该插件。

Vue.js 官方提供的一些插件 (例如 vue-router) 在检测到 Vue 是可访问的全局变量时会自动调用 Vue.use()。然而在像 CommonJS 这样的模块环境中,你应该始终显式地调用 Vue.use()

// 用 Browserify 或 webpack 提供的 CommonJS 模块环境时
var Vue = require('vue')
var VueRouter = require('vue-router')

// 不要忘了调用此方法
Vue.use(VueRouter)

awesome-vueopen in new window 集合了大量由社区贡献的插件和库。

6.自定义事件

6.1 自定义事件名

不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。举个例子,如果触发一个 camelCase 名字的事件:

this.$emit('myEvent')

则监听这个名字的 kebab-case 版本是不会有任何效果的:

<!-- 没有效果 -->
<my-component v-on:my-event="doSomething"></my-component>

不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或 property 名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。

因此,我们推荐你始终使用 kebab-case 的事件名

6.2 自定义事件

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:<Demo @mooc="test"/><Demo v-on:mooc="test"/>

    2. 第二种方式,在父组件中:

      <Demo ref="demo"/>
      ......
      mounted(){
         this.$refs.xxx.$on('mooc',this.test)
      }
      
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

  4. 触发自定义事件:this.$emit('mooc',数据)

  5. 解绑自定义事件this.$off('mooc')

  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

  7. 注意:通过this.$refs.xxx.$on('mooc',回调)绑定自定义事件时,回调要么配置在methods中要么用箭头函数,否则this指向会出问题!

普通事件使用vue的默认事件处理逻辑,需要先添加监听再出发事件,事件触发后添加的监听无法执行

  • 1)监听事件
this.$on('myEvent', () => {}) // 添加事件监听
this.$once('myEvent', () => {}) // 添加事件监听,并且该监听只执行一次
  • 2)触发监听
this.$emit('myEvent', myArgument) // 触发事件,并且可以给监听器传递参数
  • 3)移除监听
this.$eventBus.$off('myEvent') // 移除事件的所有监听

7.全局事件总线

7.1 简介

全局事件总线是一种组件间通信的方式,适用于任意组件间通信

全局事件总线并不是插件,配置文件等等,全局事件总线是程序员在做Vue开发中总结积累的一套方法,是一套规则,只要满足这套规则,就可以实现组件间的通信。

7.2 原理

2022-09-04_191516

结合上图,假若C组件想要传递数据给A组件,那么,就通过全局事件总线在A组件中绑定一个自定义事件,并界定一个参数来接收传递的数据,同样在C组件中,就需要通过全局事件总线对自定义事件进行触发,并传递参数。

简单理解,全局事件总线其实就是一个中间介质,组件间的相互通信借助于这个中间介质,通过这个中间转换介质,从而完成数据的传递与接收,实现组件间的相互通信

全局事件总线是一个独立存在的部分,要想实现组件间的相互通信,又是自定义事件,那就要满足两个条件

一、满足所有组件都能访问得到全局事件总线

既然要能让所有的组件都能访问得到全局事件总线,那么创建的思路就是,全局事件总线一定要让VC或者VM访问得到。

结合组件的内置关系:

VueComponent.prototype._proto_ === Vue.prototype

这个关系的作用就在于可以让 组件实例对象(vc) 可以访问到Vue原型上的属性和方法

二、可以调用 $on ,和 $off$emit

因为 $on ,和 $off$emit 这三个方法在Vue原型对象上,所以,我们的全局事件总线就要放在Vue的原型对象(vue.prototype)上,以确保每个组件都能访问得到。

7.3 实现全局总线

首先,想要实现全局事件总线,就要安装全局事件总线,在main.js中完成全局事件总线的安装配置

main.js:
//创建vm
new Vue({
	el:'#app',
	render: h => h(App),
	// beforeCreate中模板未解析,且this是vm
	beforeCreate(){
		Vue.prototype.$bus = this	//安装全局事件总线
	}
})

接下来,我们就要对想要接收到数据的组件进行自定义事件的绑定,简单来说就是,谁要接收数据,自定义事件就绑定在谁身上

绑定全局事件总线 TestB.vue:

mounted(){
  // 绑定自定义事件
  this.$bus.$on('自定义事件名', (接收参数)=>{
    console.log('我是TestB组件,收到了数据', 接收参数);
  })
}

最后一步,全局事件总线的触发,事件的触发是在发送数据的组件中完成的,简单来说,谁是数据的发送者,谁就来触发事件

触发全局事件总线 TestA.vue:

 methods:{
  // 触发事件,事件名不能重复
  触发事件方法名(){
    this.$bus.$emit('自定义事件名', 传递参数);
  }
},

全局事件总线之所以叫全局,是因为任何组件都可以访问,这就导致如果大量组件都绑定了全局事件总线,难免会造成代码混乱,且自定义事件名可能发生重复的问题,所以在开发中,使用全局事件总线时要根据实际业务情况进行选择。

8.发布订阅模式

8.1 简介

1.这种方式的思想与全局事件总线很相似

2.它包含以下操作:

(1) 订阅消息 --对应绑定事件监听

(2) 发布消息 --分发事件

(3) 取消消息订阅 --解绑事件监听

3.需要引入一个消息订阅与发布的第三方实现库: PubSubJS

Pubsubjs 是一个用 JavaScript 编写的基于主题的发布/订阅库。

Pubsubjs 具有同步解耦功能,因此主题是异步发布的。 这有助于保持程序的可预测性,因为在使用者处理主题时,主题的发起者不会被阻塞。

Pubsubjs 被设计为在单个进程中使用,并不适合多进程应用程序(比如具有许多子进程的 Node.js-Cluster)。 如果你的 Node.js 应用程序是一个单进程应用程序,你很好。 如果它是(或将是)一个多进程应用程序,你最好使用 redis pub / sub 或类似的应用程序

8.2 使用步骤

安装pubsub: npm i pubsub-js 引入: import pubsub from 'pubsub-js' 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身

 // 订阅消息
 // 使用箭头函数或者函数名 使this指向指向vc原型实例 
 pubsub.subscribe('hello',(funname, data) => {
     // funname为消息名称  data为传递的参数
     console.log(funname, data)
 })

发布消息,提供数据:

// 发布消息
// 参数1为消息名称  消息2是传递的参数
pubsub.publish('hello', 'Tom')

最好在beforeDestory钩子中,使用pubsub.unsubScribe('hello')取消订阅

9.Vue.$nextTick

9.1 Vue中DOM更新机制

Vue的响应式并不是只数据发生变化之后,DOM就立刻发生变化,而是按照一定的策略进行DOM的更新。这样的好处是可以避免一些对DOM不必要的操作,提高渲染性能。

在Vue官方文档中是这样说明的:

可能你还没有注意到,Vue异步执行DOM更新。只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作上非常重要。然后,在下一个的事件循环“tick”中,Vue刷新队列并执行实际 (已去重的) 工作。

Vue不可能对每一个数据变化都做一次渲染,它会把这些变化先放在一个异步的队列当中,同时它还会对这个队列里面的操作进行去重,比如你修改了这个数据三次,它只会保留最后一次。这些变化是都可以通过队列的形式保存起来,那现在的问题就来到了,那vue是在事件循环的哪个时机来对DOM进行修改呢?

Vue有两种选择,一个是在本次事件循环的最后进行一次DOM更新,另一种是把DOM更新放在下一轮的事件循环当中。因为本轮事件循环最后执行会比放在下一轮事件循环要快很多,所以Vue优先选择第一种,只有当环境不支持的时候才触发第二种机制。

虽然性能上提高了很多,但这个时候问题就出现了,我们都知道在一轮事件循环中,同步执行栈中代码执行完成之后,才会执行异步队列当中的内容,那我们获取DOM的操作是一个同步的呀!!那岂不是虽然我已经把数据改掉了,但是它的更新异步的,而我在获取的时候,它还没有来得及改。

9.2 Vue.$nextTick()

放在$nextTick 当中的操作不会立即执行,而是等数据更新、DOM更新完成之后再执行,这样我们拿到的肯定就是最新的了。$nextTick方法将回调延迟到下次DOM更新循环之后执行。

9.2.1 使用

mounted: function () {
  this.$nextTick(function () {
    // Code that will run only after the
    // entire view has been rendered
  })
}

10.过渡与动画

官方文档写得很详细:过渡与动画 — Vue.js (vuejs.org)open in new window

11.配置代理服务器

11.1 跨域问题

11.1.1 什么是跨域问题?

浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都会导致跨域问题。即前端接口去调用不在同一个域内的后端服务器而产生的问题。

11.1.2 如何解决跨域问题? --- 代理服务器

代理服务器的主要思想是通过建立一个端口号和前端相同的代理服务器进行中转,从而解决跨域问题。因为代理服务器与前端处于同一个域中,不会产生跨域问题;而且代理服务器与服务器之间的通信是后端之间的通信,不会产生跨域问题。

具体流程如下图所示,红色框代表浏览器,粉色框代表代理服务器,蓝色框代表后端的服务器。

2022-09-04_202818

11.1.3 如何实现代理服务器?-- 用vue-cli来实现

vue-cli配置官方文档open in new window

11.2 vue-cli配置代理的两种方法

方法一:在Vue.config.js中添加如下配置:

devServer:{
    proxy:"http://localhost:5000"
}

说明:

1、优点:配置简单,请求资源时直接发给前端(8080)即可

2、缺点:不能配置多个代理,不能灵活的控制请求是否走代理

3、工作方式:若按照上述配置代理,当请求了不存在的资源时,那么该请求就会转发给服务器(有限匹配前端资源)

方法二:编写vue.config.js配置具体代理规则

module.exports = {
    devServer: {
        proxy: {
            '/api1': { // 匹配所有以'/api1' 开头的请求路径
                target: 'http://localhost:5000', // 代理目标的基础路径
                changeOrigin: true,
                pathRewrite: {'^/api1':''}
            },
            '/api2': { // 匹配所有以'/api2' 开头的请求路径
                target: 'http://localhost:5001',// 代理目标的基础路径
                changeOrigin: true,
                pathRewrite: {'^/api2':''}
            },
        }
    }
}

/*
    changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
    changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
    changeOrigin默认值为true
*/

说明:

1、优点:可以配置多个代理,并且可以灵活的控制请求是否走代理

2、缺点:配置略微繁琐,请求资源时必须加前缀。

12.Vue 中的 ajax

12.1 axios

通用的 Ajax 请求库, 官方推荐,使用广泛

12.2 vue-resource

vue 插件库, vue1.x 使用广泛,官方已不维护

13.插槽

官方文档写的很详细:插槽 — Vue.js (vuejs.org)open in new window

13.1 理解

父组件向子组件传递带数据的标签,当一个组件有不确定的结构时, 就需要使用

slot 技术,注意:插槽内容是在父组件中编译后, 再传递给子组件的。

13.2 分类

13.2.1 默认插槽

父组件中:
<Category> 
	<div>html结构1</div>
</Category> 
子组件中:
<template> 
	<div>
	<!-- 定义插槽 --> 
	<slot>插槽默认内容...</slot> 
	</div> 
</template>

13.2.2 命名插槽

父组件中:
<Category> 
    <template slot="center"> 
        <div>html结构1</div> 
	</template> 
	<template v-slot:footer> 
    <div>html结构2</div> 
	</template> 
</Category>
子组件中:
<template> 
    <div>
   	 	<!-- 定义插槽 --> 
    	<slot name="center">插槽默认内容...</slot> 
		<slot name="footer">插槽默认内容...</slot> 
	</div> 
</template>

13.2.3 作用域插槽

  1. 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

  2. 具体编码:

父组件中:
<Category> 
	<template scope="scopeData">
		<!-- 生成的是ul列表 --> 
		<ul>
    		<li v-for="g in scopeData.games" :key="g"> {{g}}</li> 
       </ul> 
   </template> 
</Category> 
<Category> 
    <template slot-scope="scopeData"> 
		<!-- 生成的是h4标题 --> 
		<h4 v-for="g in scopeData.games" :key="g">{{g}} 
		</h4> 
    </template> 
</Category> 
子组件中:
<template> 
<div>
    <slot :games="games"></slot> 
</div> 
</template> 
<script> 
    export default { 
        name:'Category', 
        props:['title'], 
        //数据在子组件自身 
        data() { 
            return { games:['红色警戒','穿越火线','劲舞团','超级 玛丽'] } 
        }, 
    } 
</script>

14.webStorage

  1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

  2. 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。

  3. 相关API:

    1. xxxxxStorage.setItem('key', 'value'); 该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。

    2. xxxxxStorage.getItem('person');

      ​ 该方法接受一个键名作为参数,返回键名对应的值。

    3. xxxxxStorage.removeItem('key');

      ​ 该方法接受一个键名作为参数,并把该键名从存储中删除。

    4. xxxxxStorage.clear()

      ​ 该方法会清空存储中的所有数据。

  4. 备注:

    1. SessionStorage存储的内容会随着浏览器窗口关闭而消失。
    2. LocalStorage存储的内容,需要手动清除才会消失。
    3. xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。
    4. JSON.parse(null)的结果依然是null。