vue组件化编程

组件(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 效果

访问 http://localhost:8080/

 

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>

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值