Vue2:脚手架 vue-cli
- 结构
- render
- ref
- props
- mixin
- scoped
脚手架是Vue
官方提供的Vue
开发平台,.vue
文件就需要通过脚手架来解析,所以对于单文件组件就依赖于脚手架。
安装:
npm i -g @vue/cli
如果执行vue --version
有输出,那么说明脚手架安装成功了。
基于脚手架,可以快速创建一个项目的基本结构,命令如下:
vue create 项目名
创建一个名为study
的项目:
此时会询问,要使用哪一个Vue
版本,目前使用Vue2
。
出现以下界面,说明脚手架创建成功:
进入项目目录,执行:
npm run serve
此时基于现有的.vue
文件,启动一个本地服务,随后就可以通过本地服务访问页面:
访问127.0.0.1:8080
:
此时Vue
脚手架就已经开始运行了,这个页面是创建脚手架时Vue
自动创建的一个hello
界面。
结构
脚手架初始的文件结构如下:
├── 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:包版本控制文件
public/index.html
:主页文件,引入src/main.js
作为入口src/main.js
:定义了最大的vm
对象,引入App.vue
管理App
组件src/App.vue
:最大的组件,所有的组件都在该组件之下统一管理src/component/
:该目录下是所有的组件
简单来说,所有的组件都定义在src/component/
目录下,并被App.vue
统一管理,最后经过src/main.js
引入到public/index.html
主页中,这样就构成了一个页面。
程序员的绝大部分操作,都在src/component/
目录中。
render
打开main.js
,内容如下:
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
刚才提到,main.js
负责引入App.vue
但是这一块的语法非常奇怪,使用了一个render
方法。
正常来说,引入App.vue
应该这样写:
new Vue({
el:'#app',
template: `<App></App>`,
components: {App}
})
通过components
引入App
组件,再通过template
模板解析<App>
标签,但是这样是错误的。
因为vue-cli
默认引入的是一个简化版本的vue
,这个版本没有模板解析器,在vm
中没有template
选项,所以以上代码无法运行。
render
方法可以向网页中添加一个DOM
节点:
new Vue({
el:'#app',
render(createElement){
return createElement('标签', '内容')
}
})
render
接收一个参数createElement
,该参数是一个函数,函数的第一个参数传入标签名,第二个参数传入内容。这样就可以在网页中渲染出一个DOM
节点,比如创建一个<h1> hello </h1>
:
new Vue({
el:'#app',
render(createElement){
return createElement('h1', 'hello')
}
})
由于最后要使用App
标签,由不能通过模板解析器,此时就可以借助于render
:
render(createElement){
return createElement(App)
}
App
中无需传入内容,所以createElement
就没有第二个参数。以上写法可以进行简化:
// 简化参数名称
render(h){
return h(App)
}
// 变为箭头函数
render: h => h(App)
这样就变为了main.js
中的形态,最后把el: '#app'
改为$mount
的形式,就得到:
new Vue({
render: h => h(App),
}).$mount('#app')
ref
ref
是一个标签属性,其可以快速拿到一个标签的DOM
对象,如果标签是组件,那么可以得到该组件的实例对象。
当前App.vue
结构如下:
<template>
<div id="app">
<h2>一个标题</h2>
<HelloWorld/>
</div>
</template>
其包含一个标题h2
和一个组件HelloWorld
,对这两个标签添加ref
属性:
<template>
<div id="app">
<h2 ref="title">一个标题</h2>
<HelloWorld ref="hello" />
<button @click="testRef"></button>
</div>
</template>
最后在methods
中添加一个testRef
方法,输出当前的vm
:
export default {
name: 'App',
components: {
HelloWorld
},
methods:{
testRef(){
console.log(this)
}
}
}
点击按钮输出结果:
在vm
下,多出一个$ref
属性,该属性有hello
和title
两个子属性,其中hello
是VueComponent
对象,也就是一个组件的实例,而title
是<h2>
标签的DOM
对象。
基于ref
方法,可以快速的获取到指定标签的DOM
对象或者vc
对象,这在组件间通信时有很大的用处。
console.log(this.$refs.title)
console.log(this.$refs.hello)
输出结果:
props
props
可以让组件接收外部传入的数据。
现有一个StudentInfo
组件,结构如下:
<template>
<div>
<h2>学生信息:</h2>
<p>姓名:</p>
<p>年龄:</p>
<p>性别:</p>
</div>
</template>
在App.vue
中引入该组件,那么如何才能让StudentInfo
组件输出不同的学生信息?这就需要使用props
属性了。
语法:
{
props:['属性1','属性2',...]
}
在配置项中,peops
配置项可以接收来自外部的参数,将要接受的属性名以数组形式写在props
中。
修改StudentInfo.vue
:
<template>
<div>
<h2>学生信息:</h2>
<p>姓名:{{ name }}</p>
<p>年龄:{{ age }}</p>
<p>性别:{{ sex }}</p>
</div>
</template>
<script>
export default {
name: 'StudentInfo',
props:['name', 'age', 'sex']
}
</script>
在App.vue
中,只需要使用组件标签时,把参数以属性的形式传入即可:
<template>
<div id="app">
<StudentInfo name="张三" age="18" sex="男"/>
<StudentInfo name="翠花" age="22" sex="女"/>
</div>
</template>
输出结果:
这样同一个组件就可以接收父组件的参数,承载不同的信息了。
props
还有更加详细的传参方式:
props:{
name: String,
age: Number,
sex: String
}
将props
定义为对象,组件接收的每个参数可以指定接收类型,这样可以完成对数据类型的限制。
默认情况下,属性传入的都是字符串:
<StudentInfo name="张三" age="18" sex="男"/>
以上传入的三个参数都是字符串,包括18
也是字符串。如果传参时希望解析内容,而不是字符串,就需要通过:
单向绑定,这样会把""
内部的内容作为表达式解析。其中age
要以数字的形式传入,所以加上:
修饰。
<StudentInfo name="张三" :age="18" sex="男"/>
在StudentInfo
组件中,输出age+1
:
<template>
<div>
<h2>学生信息:</h2>
<p>姓名:{{ name }}</p>
<p>年龄:{{ age + 1 }}</p>
<p>性别:{{ sex }}</p>
</div>
</template>
输出结果:
加法正常执行了,说明传入的18
成功解析为了字符串。
此处可以得到两个要点:
- 传参时如果数据类型不是字符串,要加上
:
修饰,将内容作为表达式解析 - 接收方可以在
props
内部限定传入数据的类型
props
还有更详细的写法:
props{
'属性':{
type:Number,
required:false,
default:20
}
}
type
:限定该属性的类型required
:该属性是否可以缺省default
:如果不传参,该属性的缺省值
如果外界没有传入某个属性,并且这个属性没有缺省值,那么默认为undefined
。
mixin
现有StudentInfo
和TeacherInfo
两个组件,如下:
TeacherInfo
export default {
name: 'TeacherInfo',
data(){
return {
name: '李老师',
classRoom: "三班 四班"
}
},
methods:{
getName() {
console.log(this.name)
}
}
}
StudentInfo
export default {
name: 'StudentInfo',
data(){
return {
name: '张三',
age: 18,
sex: '男'
}
},
methods:{
getName() {
console.log(this.name)
}
}
}
在这两个组件中,有一个getName
方法都是一样的,那么mixin
就可以处理这样的多个组件之间,存在相同属性或方法的复用问题。
.js
文件内部的属性和方法,可以通过mixin
添加到多个组件中,这样多个组件就可以复用相同的属性和方法。
test.js
export const test = {
methods:{
getName() {
console.log(this.name)
}
}
}
在test.js
中,把getName
方法定义到一个对象下,并把对象暴露出去。
接下来就可以在组件中引入这个test.js
,并通过mixin
使用test.js
内的方法:
import {test} from '../test.js'
export default {
name: 'TeacherInfo',
data(){
return {
name: '李老师',
classRoom: "三班 四班"
}
},
mixins:[test]
}
通过mixin
引入,需要通过mixins
配置项,值为一个数组,数组内部是要导入的对象。
随后在TeacherInfo
中就可以使用test.js
内部的方法了,StudentInfo
同理。
除此之外,mixin
引入的.js
文件中,还可以定义data
数据、hook
生命周期钩子等等内容:
export const test = {
data(){
return {
a: 1,
b: 2
}
},
methods:{
getName() {
console.log(this.name)
}
},
mounted(){
console.log("挂载完成")
}
}
任何通过mixin
引入这个test
的组件,都会获得test
内部的所有内容,这样就可以把不同组件内部相同的内容,通过mixin
进行复用。
scoped
依然沿用之前的TeacherInfo
和StudentInfo
结构,但是分别为它们添加背景色的样式:
TeacherInfo
<style>
p{
background-color: pink;
}
</style>
StudentInfo
<style>
p{
background-color: skyblue;
}
</style>
两个组件分别使用的了不同的背景色,都作用于<p>
标签,输出结果:
输出时,它们都变成了粉色,可是学生信息明明是skyblue
,为什么变成了粉色?
这是因为在.vue
进行汇总时,会把所有组件的<style>
都放到一起,导致<p>
标签的样式之间发生了覆盖。
在App.vue
中引入组件:
import StudentInfo from './components/StudentInfo.vue'
import TeacherInfo from './components/TeacherInfo.vue'
TeacherInfo
是后引入的,所以会覆盖掉StudentInfo
的<p>
样式,导致学生样式输出错误。
如果要解决这个问题,就需要scoped
属性,其可以限定<style>
内部的样式,只作用于当前的组件,不会影响其它的组件。
语法:
<style scoped>
<style>
现在给两个组件的<style>
都加上这个属性:
css
样式分别独立了,大部分情况下,为了避免组件之间样式相互影响,都会启用这个属性。