组件(Component)是 Vue.js 最强大的功能之一。
组件可以扩展 HTML 元素,封装可重用的代码。
组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树:
1. 使用vue-cli创建模板项目
1.1 简介
1) vue-cli 是 vue 官方提供的脚手架工具
2) github: https://github.com/vuejs/vue-cli
3) 作用: 从 https://github.com/vuejs-templates 下载模板项目
1.2 创建vue项目
npm install -g vue-cli
vue init webpack vue_demo
cd vue_demo
npm install
npm run dev
访问: http://localhost:8080/
1.3 模板项目的结构
|-- build : webpack 相关的配置文件夹(基本不需要修改)
|-- dev-server.js : 通过 express 启动后台服务器
|-- config: webpack 相关的配置文件夹(基本不需要修改)
|-- index.js: 指定的后台服务的端口号和静态资源文件夹
|-- node_modules
|-- src : 源码文件夹
|-- components: vue 组件及其相关资源文件夹
|-- App.vue: 应用根主组件
|-- main.js: 应用入口 js
|-- static: 静态资源文件夹
|-- .babelrc: babel 的配置文件
|-- .eslintignore: eslint 检查忽略的配置
|-- .eslintrc.js: eslint 检查的配置
|-- .gitignore: git 版本管制忽略的配置
|-- index.html: 主页面文件
|-- package.json: 应用包配置文件
|-- README.md: 应用描述说明的 readme 文件
1.4 效果
2. 项目的打包和发布
2.1 打包
npm run build
会生成如下目录:
2.2 使用静态服务器发布
npm install -g serve
serve dist
访问: http://localhost:5000
2.3 使用tomcat发布
1.修改配置: webpack.prod.conf.js
output: { publicPath: '/xxx/' //打包文件夹的名称 }
2.重新打包: npm run build
3.修改 dist 文件夹为项目名称: xxx
4.将 xxx 拷贝到运行的 tomcat 的 webapps 目录下
访问: http://localhost:8080/xxx
3. 组件定义与使用
3.1 vue文件的组成
1)模板页面
<template>
<!--页面模板 -->
</template>
2)js模块对象
<script>
export default {
data() {
return {}
},
methods: {},
computed: {},
components: {}
}
</script>
3)样式
<style scoped>
/* 样式定义 */
</style>
3.2 基本使用
1) 引入组件
2) 映射成标签
3) 使用组件标签
<template>
<!-- 写法一: 一模一样 -->
<HelloWorld></HelloWorld>
<!-- 写法二: 大写变小写, 并用-连接 -->
<hello-world></hello-world>
</template>
<script>
import HelloWorld from './components/HelloWorld'
export default {
components: {
HelloWorld
}
}
</script>
4. 组件间通信
4.1 基本原则
1) 不要在子组件中直接修改父组件的状态数据
2) 数据在哪, 更新数据的行为(函数)就应该定义在哪
4.2 组件间通信 1:props
使用组件标签时:
<my-component name='tom' :age='3' :set-name='setName'></my-component>
定义MyComponent时,在组件内声明所有的 props:
// 方式一: 只指定名称
props: ['name', 'age', 'setName']
// 方式二: 指定名称和类型
props: { name: String, age: Number, setName: Function }
// 方式三: 指定名称/类型/必要性/默认值
props: { name: {type: String, required: true, default:xxx}, ...}
注意:
-
此方式用于父组件向子组件传递数据
-
所有标签属性都会成为组件对象的属性, 模板页面可以直接引用
-
问题:
-
如果需要向非子后代传递数据必须多层逐层传递
-
兄弟组件间也不能直接 props 通信, 必须借助父组件才可以
-
4.3 组件间通信2: vue自定义事件
绑定事件监听
// 方式一: 通过 v-on 绑定
@click="deleteTodo"
// 方式二: 通过$on() 绑定自定义事件(delete_todo)监听
<TodoHeader ref="xxx"/>
this.$refs.xxx.$on('delete_todo', function (todo) {
this.deleteTodo(todo)
})
触发事件
// 触发事件(只能在父组件中接收)
this.$emit(eventName, data)
注意:
-
此方式只用于子组件向父组件发送消息(数据)
-
问题: 隔代组件或兄弟组件间通信此种方式不合适
4.4 组件间通信3:消息订阅与发布
订阅消息
PubSub.subscribe('msg', function(msg, data){})
发布消息
PubSub.publish('msg', data)
注意:
-
此方式可实现任意关系组件间通信(数据)
比如我们订阅一个消息,实现数组的删除操作
mounted () {
// 订阅消息(deleteTodo)
PubSub.subscribe('deleteTodo', (msg, index) => {
this.deleteTodo(index)
})
}
methods: {
deleteTodo (index) {
this.todos.splice(index, 1)
},
}
<button class="btn btn-danger" v-show="isShow" @click="deleteItem">删除</button>
deleteItem () {
// 发布消息(deleteTodo)
PubSub.publish('deleteTodo', this.index)
}
4.5 组件间通信4:slot
此方式用于父组件向子组件传递标签数据
子组件: Child.vue
<template>
<div>
<slot name="xxx">不确定的标签结构 1</slot>
<div>组件确定的标签结构</div>
<slot name="yyy">不确定的标签结构 2</slot>
</div>
</template>
父组件: Parent.vue
<child>
<div slot="xxx">xxx 对应的标签结构</div>
<div slot="yyy">yyyy 对应的标签结构</div>
</child>
5.vue-ajax
Vue 要实现异步加载需要使用到 vue-resource 库。
Vue.js 2.0 版本推荐使用 axios 来完成 ajax 请求。
下面我们介绍一下 axios
使用 npm:
npm install axios
使用 cdn:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
5.1 GET
下面是axios发送get请求的一个示例
//引入axios模块
import axios from 'axios'
// 发ajax请求进行搜索
// 直接在 URL 上添加参数 q=xxx
const url = `https://api.github.com/search/users?q=${searchName}`
axios.get(url)
.then(response => {
// 成功了, 更新数据(成功)
this.users = response.data.items.map(item => ({
url: item.html_url,
avatarUrl: item.avatar_url,
username: item.login
}))
})
.catch(error => {
this.errorMsg = '请求失败!'
})
如果需要传递数据,可以使用 this.$http.get('url',{params : jsonData})
格式,第二个参数 jsonData
就是传到后端的数据。
// 通过params设置参数
axios.get('https://api.github.com/search/users',{params : {q:searchName}}).then(function(res){
// ...
},function(res){
// ...
});
5.2 POST
axios.post('/user', {
firstName: 'Fred', // 参数 firstName
lastName: 'Flintstone' // 参数 lastName
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
5.3 API
可以通过向 axios 传递相关配置来创建请求。
axios(config)
// 发送 POST 请求
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
// GET 请求远程图片
axios({
method:'get',
url:'http://bit.ly/2mTM3nY',
responseType:'stream'
})
.then(function(response) {
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
});
axios(url[, config])
// 发送 GET 请求(默认的方法)
axios('/user/12345');
为方便使用,官方为所有支持的请求方法提供了别名,可以直接使用别名来发起请求:
axios.request(config) axios.get(url[, config]) axios.delete(url[, config]) axios.head(url[, config]) axios.post(url[, data[, config]]) axios.put(url[, data[, config]]) axios.patch(url[, data[, config]])
在使用别名方法时, url、method、data 这些属性都不必在配置中指定。
5.4 响应结构
axios请求的响应包含以下信息:
{
// `data` 由服务器提供的响应
data: {},
// `status` HTTP 状态码
status: 200,
// `statusText` 来自服务器响应的 HTTP 状态信息
statusText: "OK",
// `headers` 服务器响应的头
headers: {},
// `config` 是为请求提供的配置信息
config: {}
}
6. 组件入门案例演示
4.1 初始化显示
我们有这样一个页面,我们把这个页面按照组件化的形式开发。
我们把页面划分成了3个组件,文件目录结构为:
1)首先,创建我们的入口文件
index.js
import Vue from 'vue';
import App from './App.vue';
new Vue({
el: '#app',
components: { App },
template: '<App/>',
});
2)我们的页面使用了bootstrap的样式。我们在static下面先导入样式文件
然后在index.html中引入
<link rel="stylesheet" href="./static/css/bootstrap.css">
3)App.vue中引入组件,并将基本结构写出来
<template>
<div>
<header class="site-header jumbotron">
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>请发表对Vue的评论</h1>
</div>
</div>
</div>
</header>
<div class="container">
<Add/>
<List/>
</div>
</div>
</template>
<script>
//1.引入组件
import Add from './components/Add.vue';
import List from './components/List.vue';
export default {
name: 'App',
//2.映射组件标签
components: {
Add, List
},
};
</script>
<style>
</style>
4)Add.vue页面
<template>
<div class="col-md-4">
<form class="form-horizontal">
<div class="form-group">
<label>用户名</label>
<input type="text" class="form-control" placeholder="用户名">
</div>
<div class="form-group">
<label>评论内容</label>
<textarea class="form-control" rows="6" placeholder="评论内容"></textarea>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button" class="btn btn-default pull-right">提交</button>
</div>
</div>
</form>
</div>
</template>
<script>
export default {
name: "add"
}
</script>
<style scoped>
</style>
5)List.vue页面
<template>
<div class="col-md-8">
<h3 class="reply">评论回复:</h3>
<h2 style='display: none'>暂无评论,点击左侧添加评论!!!</h2>
<ul class="list-group">
<li class="list-group-item">
<div class="handle">
<a href="javascript:;">删除</a>
</div>
<p class="user"><span >xxx</span><span>说:</span></p>
<p class="centence">vue不错!</p>
</li>
<li class="list-group-item">
<div class="handle">
<a href="javascript:;">删除</a>
</div>
<p class="user"><span >yyy</span><span>说:</span></p>
<p class="centence">vue有点难!</p>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "list"
}
</script>
<style scoped>
</style>
6)引入样式
List.vue中引入样式
.reply {
margin-top: 0px;
}
7)定义初始化data数据
因为我们的评论数据在add和list里面都有用到,所以我们定义到app.vue中
data () {
return {
// 数据在哪个组件,更新数据的行为(方法)就应该定义在哪个组件
comments:[
{
name: 'Tom',
content: 'vue真好用'
},
{
name: 'Jack',
content: 'vue真简单'
}
]
}
}
然后将数据传给list
<List :comments="comments"/>
List.vue中声明接收属性
export default {
name: "list",
// 声明接收属性,这个属性就会成为组件对象的属性
props: ['comments']
}
我们这里可以把item单独定义成一个组件,所以可以把comment传给Item.vue
8)数据传递
数据是一个数组,所以我们使用v-for遍历。将刚才的写死的数据修改成如下:
<ul class="list-group">
<Item v-for="(comment, index) in comments" :key="index" :comment="comment"/>
</ul>
<script>
import Item from './Item.vue'
export default {
// ...
components: {Item}
}
</script>
9)Item.vue
List.vue将comment对象传过来了,我们使用props接收。然后编写样式。
<template>
<li class="list-group-item">
<div class="handle">
<a href="javascript:;">删除</a>
</div>
<p class="user"><span >{{comment.name}}</span><span>说:</span></p>
<p class="centence">{{comment.content}}</p>
</li>
</template>
<script>
export default {
name: "Item",
props:{
// 指定了属性名和属性值的类型
comment: Object
}
}
</script>
<style scoped>
li {
transition: .5s;
overflow: hidden;
}
.handle {
width: 40px;
border: 1px solid #ccc;
background: #fff;
position: absolute;
right: 10px;
top: 1px;
text-align: center;
}
.handle a {
display: block;
text-decoration: none;
}
.list-group-item .centence {
padding: 0px 50px;
}
.user {
font-size: 22px;
}
</style>
最终效果如下:
4.2 交互添加
因为我们的comments数组定义在App.vue中,所以我们在该文件中定义添加的方法。
methods:{
addComment(comment){
this.comments.unshift(comment);
}
},
我们想要在Add.vue中使用它,所以需要将该方法传递给该组件
<Add :addComment="addComment"/>
我们需要在Add.vue中做添加操作
<template>
<div class="col-md-4">
<form class="form-horizontal">
<div class="form-group">
<label>用户名</label>
<input type="text" class="form-control" placeholder="用户名" v-model="name">
</div>
<div class="form-group">
<label>评论内容</label>
<textarea class="form-control" rows="6" placeholder="评论内容" v-model="content"></textarea>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button" class="btn btn-default pull-right" @click="add">提交</button>
</div>
</div>
</form>
</div>
</template>
<script>
export default {
props: {
addComment: {
// 指定了属性名/属性值的类型
type: Function,
// 指定了必要性
required: true
}
},
data() {
return {
name: '',
content: ''
}
},
name: "add",
methods: {
add() {
// 1.检查输入的合法性
const name = this.name.trim();
const content = this.content.trim();
if (!name || !content) {
alert("姓名或内容不能为空");
return;
}
// 2.根据输入的数据封装成一个comment对象
const comment = {
name,
content
};
// 3.添加到comments中
this.addComment(comment);
// 5.清除输入
this.name = '';
this.content = '';
}
}
}
</script>
<style scoped>
</style>
4.3 交互删除
在App.vue中,定义删除的方法。
// 删除指定下标的评论
deleteComment(index){
this.comments.splice(index,1);
}
将该方法传给List.vue组件
<List :comments="comments" :deleteComment="deleteComment"/>
List.vue接收该方法,并将该方法和数组的index传给Item.vue
<Item v-for="(comment, index) in comments" :key="index" :comment="comment" :deleteComment="deleteComment" :index="index"/>
<script>
// ...
props: ['comments','deleteComment']
</script>
没有评论时,展示对应的说明
<h2 v-show="comments.length===0">暂无评论,点击左侧添加评论!!!</h2>
Item.vue中做删除操作
<a href="javascript:;" @click="deleteItem()">删除</a>
<script>
props:{
// ...
deleteComment: Function,
index: Number
},
methods:{
deleteItem(){
const {comment} =this;
if(confirm(`确定删除${comment.name}的评论吗?`)){
this.deleteComment(this.index);
}
}
}
</script>