Vue学习笔记(三)
非单文件组件
非单文件组件顾名思义,它是在一个文件里有多组件,直接在html文件中写即可,不过实际开发中,很少使用,了解即可
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
组件一般来说分为两步,第一步是创建一个组件,也就是创建好组件内的各项配置,第二步就是注册组件,非单文件的话,可以简单点,直接一次完成两步。
Vue.component 的作用就是注册一个组件,第一个参数是组件名,第二个参数就是组件,因为这里是一步到位了,所以第二个参数就是组件的配置项了
组件本质上是可复用的 Vue 实例,因此配置项其实基本上就是直接把原来 Vue 实例中的配置项给拿过来用,不过还是有一些区别的
- 首先是没有了 el ,因为是组件,我们可以随便拿过来用,所以不能限制死在哪个容器,所以不用挂载到容器上,
- 第二点,data 必须写成函数的形式,不能写成对象,因为我们的组件是可复用的,如果写成对象的话,那么每一个用到该组件的地方,调用的 data 就是同一个了,改了一个就把所有的都改了,这显然不是我们想要的,因此,写成函数可以保证 data 可以复用,且每一个都是独立的
- 我们写 Vue 实例的时候,是把模板写在了容器里,现在组件里没有了容器,我们就把模板写在
template
里,也就是把原来容器里的代码,复制到 template 中,如果要换行的话,记得用模板字符串的引号template: ``
,还有个必须注意的是,template 中,只能有一个根元素,也就是里面的元素画出来应该是一颗树
那么我们该如果使用组件呢,很简单,刚刚的 Vue.component 是全局注册了该组件,因此直接在容器里将组件当作一个普通的 DOM 标签即可
<div id="components-demo">
<button-counter></button-counter>
</div>
new Vue({ el: '#components-demo' })
有全局注册自然也有局部注册,这个就放到后面单文件组件一起
Vue 脚手架
安装
在使用单文件组件之前,我们需要安装 Vue 的脚手架——Vue CLI
非单文件组件我们可以直接写在 html 里,而单文件组件我们是一个文件一个组件,那么是什么文件呢,答案是 .vue 文件,正常的浏览器显然不可能识别 Vue 文件,因此我们自然需要另外配置一个开发环境,怎么创建一个 Vue 的开发环境呢,答案就是使用 Vue CLI,也就是 Vue 的脚手架
首先 Vue cli 是一个 npm 包,因此安装之前还需要安装好 Node.js,这个直接去官网下载最新版安装即可
然后就是安装 Vue 脚手架,可以先看看官网安装教程
npm install -g @vue/cli
如果按照成功的话,可以试试输入 vue
看看能不能执行,可以的话就是安装成功了
创建项目
创建项目很简单, 第一步,先切换到你要创建项目的目录,然后执行
vue create hello-world
create 后面跟你的项目名即可
然后它会让你选择安装 Vue2 还是 Vue3 还是自定义的配置,本文都是基于 Vue2 的,因此安装的是 Vue2
运行项目
其实创建项目成功后就会告诉你运行项目的命令
进入你的项目目录,执行下面的命令
npm run serve
它会开启一个小型的内置服务器,直接通过浏览器访问告诉你的网址即可
项目结构
创建好的项目应该是差不多是这个样子的
- node_modules: 你安装的包括 Vue 在内的各种包
- public: 你的网站根目录,index.html 就是你的首页(作为单页面应用也就这么一个页面)
- src
- assets:素材文件,比如网址要用的图片、视频、字体、css等等
- components:各种各种要用到的组件都放这里面
- App.vue: 所有组件的父组件,挂在根组件下面,一般里面只注册组件,把组件放在模板里,顶多再写点全局的配置
- main.js:整个项目其实最先运行的就是这个文件,是项目的入口
- .gitgnore: git 的忽略文件,声明哪些文件不受 git 管理,如果不创建 git 仓库可以忽略这个文件
- babel.config.js:很显然,是 babel 的配置文件, babel 的作用就是帮你将 es6 的代码转换成 es5 的(虽然现在绝大部分浏览器都已经支持 es6 了,不支持 es6 的浏览器估计也不会支持的了 Vue 。。。)
- package-lock.json:包版本控制文件,记录你用的每个包是什么版本的,有什么依赖
- package.json:是个 npm 项目都有这个相当于一个说明书,和上面那个配套使用
- README.md:但凡用过 github 都知道吧,默认写的是怎么安装和运行该项目
- vue.config.js:很显然是 Vue 的配置文件,不过默认是没有的,需要额外配置才加上的,比如 vue 的代理
单文件组件
一个 vue 文件分为三个部分
<template>
<!-- 写你之前写在容器里的模板代码,同时也就是非单文件组件里的 template 配置项
要求也和 template 里的一样只能有一个根元素 -->
</template>
<script>
// data、methods等等
</script>
<style>
/* 组件的样式 */
</style>
template 其实没什么好说的,除了只能有一个根元素外,该怎么写就怎么写
script 里和之前写法有点不同,我们可以分为三步,第一步导入用到的组件或者其他什么东西,第二步配置组件,第三步把组件导出去,后面两步可以简化成一步,大致可以写成下面这样
import HelloWorld from './components/HelloWorld.vue' //这是 es6 的语法,.vue可省略
export default { //同样是 es6 的语法,默认导出
name:'ButtonCounter', //当前组件的名字,可以省略,建议写着,不然名字就会根据导入时的名字来
components: { //局部注册组件
HelloWorld,
// ...
},
data() { //原来的 data,只不过写成函数形式
return {
count: 0
}
},
// ...
}
style 里就是正常的 css,不过默认情况下,这些 css是全局生效的,如果想要只在该组件上生效,就要加一个 scoped,否则如果出现样式的冲突,后导入的样式就会覆盖原来的样式
<style scoped>
</style>
组件之间传参
父组件传给子组件
父组件如何使用的子组件?很简单,像用普通标签一样使用,那么这么把参数和值传给这个标签?自然是通过标签的属性来传递
<template>
<HelloWorld test="hello"></HelloWorld>
</template>
而且在子组件的这个标签上,我们也可以使用之前所有的 Vue 指令语法
你传给了子组件,但子组件要用的话,肯定得接收这些参数(如果不接收,默认会把这些属性加到子组件的根元素上)
因此在子组件的 script 中要增加一项配置 props
export default {
name:'HelloWorld',
props:['test']
}
最简化的方法是写成数组的形式,再复杂一点就是写成对象的形式,声明每个变量的类型
export default {
name:'HelloWorld',
props:{
test:String,
}
}
还可以定义的再再复杂一点
export default {
name:'HelloWorld',
props:{
test:{
type: String, //定义类型
default: 'test', //默认值
required:true, //是否必须
// ...
},
}
}
复杂的定义只是为了更加的安全,即便不满足,也不会报错,只是会在控制台警告你
子组件传给父组件
这个就比较麻烦了,一般来说我们是通过事件的形式来传递,也就是父组件在子组件上绑定事件,当子组件的这个事件被触发的时候,携带着需要传递的参数,那么父组件上跟这个事件绑定的那个 methods 中的函数被调用的时候就能收到哪个传递的参数
<template>
<HelloWorld @testEvent="helloTest"></HelloWorld>
</template>
<script>
import HelloWorld from './components/HelloWorld'
export default {
name:'ButtonCounter',
components: {
HelloWorld,
},
methods: {
helloTest(value){
console.log(value)
}
}
}
}
</script>
写到这里和以前 v-on 绑定事件是一样的,只不过这里的事件是我们自定义的事件
那么剩下的自然就是事件的触发了,既然是子组件触发事件那么就自然是在子组件里写
触发自定义事件需要一个函数$emit,只要子组件调用这个函数,这个对应的事件就会被触发,并带上后面想要传递的参数(当然你不想传也可以,传好几个也可以)
this.$emit('testEvent',value)
双向绑定
上面两种都是单向传递,那么自然也有双向的办法,在之前我们提到过,v-model 本质上是v-bind 绑定属性,v-on 绑定事件来修改 data 里的数据,你是不是想到了什么,没错就是上面两种以前使用,就可以实现双向绑定,和以前的区别,也就是需要在子组件上接收参数以及主动触发事件并携带参数
需要注意的是一点,我们不应该在子组件中修改父组件传递过来的 prop ,而是应该将想要修改的值,通过事件传递给父组件,由父组件来修改
在组件中也可以使用 v-model,只不过默认绑定的是 value 属性和 input 事件,如果像修改可以在子组件中增加配置项 model
model: {
prop: 'checked',
event: 'change'
},
任意组件之间传参
父子组件之间传递其实已经有点小麻烦了,如果都这么传递的话,一条父子链传上去就要死了
如果想要任意组件之间传参怎么办?答案是没办法直接传,毕竟都看不到彼此,那怎么办呢?很简单,弄个全局都看得到到的
在此之前有个概念就是,我们所有的组件最后都是会成为一个 VueComponent 的实例对象,这个 VueComponent 是从 Vue 这个对象继承过来的,因此如果我们想要有一个所有组件都能看到的东西,这个东西应该在 VueComponent 身上,准确的说是 VueComponent 的原型上,但一般来说我们拿不到VueComponent,因此也没法往上放东西,但我们可以拿到 Vue,因为 VC 是从 Vue 继承过来的,所以 VueComponent.prototype.__proto__ === Vue.prototype
因此我们可以接触到的,能让所有组件都看得到的,就是 Vue 的原型,因此我们要在这上面放东西
我们既然要实现任意传参,肯定不能只是弄个全局的参数,不然谁改了,什么时候改的都不知道,所以我们应该通过事件来传参,因此那个 Vue 原型上放的东西就被称作全局事件总线。既然是通过事件来传递,那么就需要在这个上面绑定事件,并且能够主动触发这个事件,绑定事件我们除了 v-on 外,还有个方法就是 $on函数 ,看到 $,就说明这个是 Vue 本身自带的提供给我们的东西,和 $emit 一样,因此全局事件总线除了需要放在 Vue 的原型上以外,本身还要能够调用 $on 和 $emit ,因此总线对象就必须是 Vue 的实例对象或者组件的实例对象
全局事件总线我们一般命名为 $bus,因为需要调用 $on 和 $emit ,所以最简单的办法就是在 main.js 创建 Vue 实例对象前,把实例对象本身也就是 this 放到 Vue 的原型上面
new Vue({
beforeCreate(){
Vue.prototype.$bus = this
},
render: h => h(App),
}).$mount('#app')
这样我们只需要在接收数据的组件上,给 $bus 绑定事件,然后在发送数据的组件上触发事件即可
<!-- A.vue -->
<template>
<button @click="send()">-</button>
</template>
<script>
export default {
methods: {
sendMessage() {
this.$bus.$emit("sendToB", 'Hello,我是A');
}
}
};
</script>
<!-- B.vue -->
<template>
<p>{{message}}</p>
</template>
<script>
export default {
data(){
return {
message: ''
}
},
mounted() {
this.$bus.$on("sendToB", (message) => {
// A发送来的消息
this.message = message
});
}
};
</script>
不过实际上我们很少用,因为过多的全局事件监听不仅是个负担,也很难管理,每一个监听的事件都应该在合适的时候创建,以及合适的时候被移除,一不小心就会造成一个大灾难
如果有一些数据需要由多个组件来一同管理,最好的办法不是用全局事件总线,而是官方的状态管理插件——Vuex