02.Vue组件化编码

使用 vue-cli 创建模板项目

创建 vue 项目
npm install -g vue-cli
vue init webpack vue_demo
cd vue_demo
npm install
npm run dev
访问: http://localhost:8080/

模板项目的结构

|-- 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 文件

示例代码

/*main.js*/
<template>
  <div>
    <h2 class="title">{{msg}}</h2>
  </div>
</template>

<script>
export default {
  data () {
    return {
      msg: '第一个Vue组件'
    }
  }
}
</script>

<style scoped>
  .title {
    color: red;
  }
</style>

/*App.vue*/
<template>
  <div>
    <img src="./assets/logo.png" alt="logo" class="logo">
    <HelloWorld/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  components: {
    HelloWorld
  }
}
</script>

<style>
  .logo {
    width: 100px;
    height: 100px;
  }
</style>


/*HelloWorld.vue*/

<template>
  <div>
    <h2 class="title">{{msg}}</h2>
  </div>
</template>

<script>
export default {
  data () {
    return {
      msg: '第一个Vue组件'
    }
  }
}
</script>

<style scoped>
  .title {
    color: red;
  }
</style>

项目的打包与发布

打包:
npm run build

发布 1: 使用静态服务器工具包
npm install -g serve
serve dist
访问: http://localhost:5000

发布 2: 使用动态 web 服务器(tomcat)

修改配置: webpack.prod.conf.js

 output: {
      publicPath: '/xxx/' //打包文件夹的名称
 }

重新打包:
npm run build
修改 dist 文件夹为项目名称: xxx
将 xxx 拷贝到运行的 tomcat 的 webapps 目录下 访问: http://localhost:8080/xxx

eslint

  • ESLint 是一个代码规范检查工具
  • 它定义了很多特定的规则, 一旦你的代码违背了某一规则, eslint 会作出非常有用的提示
  • 官网: http://eslint.org/
  • 基本已替代以前的 JSLint

ESLint 提供以下支持

  • ES
  • JSX
  • style 检查
  • 自定义错误和提示

ESLint 提供以下几种校验

  • 语法错误校验
  • 不重要或丢失的标点符号,如分号
  • 没法运行到的代码块(使用过 WebStorm 的童鞋应该了解)
  • 未被使用的参数提醒
  • 确保样式的统一规则,如 sass 或者 less
  • 检查变量的命名

规则的错误等级有三种

  • 0:关闭规则。
  • 1:打开规则,并且作为一个警告(信息打印黄色字体)
  • 2:打开规则,并且作为一个错误(信息打印红色字体)

相关配置文件

  • .eslintrc.js : 全局规则配置文件
'rules': {
   'no-new': 1
}
  • 在 js/vue 文件中修改局部规则
/* eslint-disable no-new */ 
new Vue({
  el: 'body',
  components: { App }
 })

-.eslintignore: 指令检查忽略的文件

*.js
*.vue

组件定义与使用

vue 文件的组成

-模板页面

<template>
页面模板
</template>
  • JS 模块对象
<script>
export default {
    data() {
      return {}
    }, 
    methods: {}, 
    computed: {}, 
    components: {}
} 
</script>
  • 样式
<style>
样式定义
</style>

基本使用

  • 引入组件
  • 映射成标签
  • 使用组件标签
<template> 
    <HelloWorld></HelloWorld> 
    <hello-world></hello-world>
</template> 
<script>
import HelloWorld from './components/HelloWorld' 
export default {
  components: {
     HelloWorld
  } 
}
</script>

关于标签名与标签属性名书写问题

  • 写法一: 一模一样
  • 写法二: 大写变小写, 并用-连接

组件间通信

组件间通信基本原则

  • 不要在子组件中直接修改父组件的状态数据
  • 数据在哪, 更新数据的行为(函数)就应该定义在哪

vue 组件间通信方式

  • props
  • vue 的自定义事件
  • 消息订阅与发布(如: pubsub 库)
  • slot
  • vuex

组件间通信 1: props

使用组件标签时

<my-component name='tom' :age='3' :set-name='setName'></my-component>

定义 MyComponent 时

  • 在组件内声明所有的 props
  • 方式一: 只指定名称
    props: ['name', 'age', 'setName']
  • 方式二: 指定名称和类型
props: {
    name: String,
    age: Number, 
    setNmae: Function
}
  • 方式三: 指定名称/类型/必要性/默认值
props: {
    name: {type: String, required: true, default:xxx},
}

注意

  • 此方式用于父组件向子组件传递数据
  • 所有标签属性都会成为组件对象的属性, 模板页面可以直接引用
  • 问题:
       a. 如果需要向非子后代传递数据必须多层逐层传递
       b. 兄弟组件间也不能直接 props 通信, 必须借助父组件才可以

组件间通信 2: vue 自定义事件

绑定事件监听

// 方式一: 通过 v-on 绑定 
@delete_todo="deleteTodo"
// 方式二: 通过$on() 
this.$refs.xxx.$on('delete_todo', function (todo) {
    this.deleteTodo(todo)
 })

触发事件

// 触发事件(只能在父组件中接收)
 this.$emit(eventName, data)

注意:

  • 此方式只用于子组件向父组件发送消息(数据)
  • 问题: 隔代组件或兄弟组件间通信此种方式不合适

组件间通信 3: 消息订阅与发布(PubSubJS 库)

订阅消息

PubSub.subscribe('msg', function(msg, data){})

发布消息

PubSub.publish('msg', data)

注意

  • 优点: 此方式可实现任意关系组件间通信(数据)

事件的 2 个重要操作(总结)

  • 绑定事件监听 (订阅消息)

目标: 标签元素 <button>
事件名(类型): click/focus
回调函数: function(event){}

  • 触发事件 (发布消息)

DOM 事件: 用户在浏览器上对应的界面上做对应的操作
自定义: 编码手动触发

组件间通信 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>

示例代码

/*main.js*/

/*
入口JS
 */
import Vue from 'vue'
import App from './App.vue'

import './base.css'

// 创建vm
/* eslint-disable no-new */
new Vue({
  el: '#app',
  components: {App}, // 映射组件标签
  template: '<App/>' // 指定需要渲染到页面的模板
})


/*TodoHeader.vue*/

<template>
  <div class="todo-header">
    <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="inputTodo" @keyup.enter="add"/>
  </div>
</template>

<script>
  export default {
    props: {
      addTodo: { // 指定属性名, 属性值的类型, 必要性
        type: Function,
        required: true
      }
    },
    data () {
      return {
        inputTodo: ''
      }
    },
    methods: {
      add () {
        // 得到输入的数据
        const inputTodo = this.inputTodo.trim()
        // 检查合法性
        if(!inputTodo) {
          alert('必须输入')
          return
        }
        // 封装一个todo对象
        const todo = {
          title: inputTodo,
          complete: false
        }
        // 添加到todos中显示
        this.addTodo(todo)
        // 清除输入
        this.inputTodo = ''
      }
    }
  }
</script>

<style>
  .todo-header input {
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
  }

  .todo-header input:focus {
    outline: none;
    border-color: rgba(82, 168, 236, 0.8);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
  }
</style>

/*TodoList.vue*/

<template>
  <ul class="todo-main">
    <TodoItem v-for="(todo, index) in todos" :key="index"
              :todo="todo" :deleteTodo="deleteTodo" :index="index"/>
  </ul>
</template>

<script>
  import TodoItem from './TodoItem.vue'
  export default {
    // 声明接收标签属性
    props: ['todos', 'deleteTodo'], // 会成为当前组件对象的属性, 可以在模板中直接访问, 也可以通过this来访问

    components: {
      TodoItem
    }
  }
</script>

<style>
  .todo-main {
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
  }

  .todo-empty {
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
  }
</style>

/*TodoItem.vue*/

<template>
  <li :style="{background: bgColor}" @mouseenter="handleEnter(true)" @mouseleave="handleEnter(false)">
    <label>
      <input type="checkbox" v-model="todo.complete"/>
      <span>{{todo.title}}</span>
    </label>
    <button class="btn btn-danger" v-show="isShow" @click="deleteItem">删除</button>
  </li>
</template>

<script>
  export default {
    props: {// 指定属性名和属性值的类型
      todo: Object,
      deleteTodo: Function,
      index: Number
    },

    data () {
      return {
        bgColor: 'white',
        isShow: false
      }
    },

    methods: {
      handleEnter (isEnter) {
        if(isEnter) { // 进入
          this.bgColor = '#cccccc'
          this.isShow = true
        } else { // 离开
          this.bgColor = '#ffffff'
          this.isShow = false
        }

      },

      deleteItem () {
        this.deleteTodo(this.index)
      }
    }
  }
</script>

<style>
  li {
    list-style: none;
    height: 36px;
    line-height: 36px;
    padding: 0 5px;
    border-bottom: 1px solid #ddd;
  }

  li label {
    float: left;
    cursor: pointer;
  }

  li label li input {
    vertical-align: middle;
    margin-right: 6px;
    position: relative;
    top: -1px;
  }

  li button {
    float: right;
    display: none;
    margin-top: 3px;
  }

  li:before {
    content: initial;
  }

  li:last-child {
    border-bottom: none;
  }
</style>

/*TodoFooter.vue*/

<template>
  <div class="todo-footer">
    <label>
      <input type="checkbox" v-model="checkAll"/>
    </label>
    <span>
          <span>已完成{{completeSize}}</span> / 全部{{todos.length}}
        </span>
    <button class="btn btn-danger" v-show="completeSize" @click="deleteAllCompleted">清除已完成任务</button>
  </div>
</template>

<script>
  export default {
    props: {
      todos: Array,
      deleteCompleteTodos: Function,
      selectAll: Function
    },

    computed: {
      completeSize () {
        return this.todos.reduce((preTotal, todo) => preTotal + (todo.complete?1:0) ,0)
      },

      checkAll: {
        get () { // 决定是否勾选
          return this.completeSize===this.todos.length && this.completeSize>0
        },

        set (value) {// 点击了全选checkbox  value是当前checkbox的选中状态(true/false)
          this.selectAll(value)
        }
      },
    },

    methods: {
      deleteAllCompleted () {
        if(window.confirm('确定清除已完成的吗?')) {
          this.deleteCompleteTodos()
        }
      }
    }
  }
</script>

<style>
  .todo-footer {
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
  }

  .todo-footer label {
    display: inline-block;
    margin-right: 20px;
    cursor: pointer;
  }

  .todo-footer label input {
    position: relative;
    top: -1px;
    vertical-align: middle;
    margin-right: 5px;
  }

  .todo-footer button {
    float: right;
    margin-top: 5px;
  }

</style>

/*storageUtils.js*/
/*
向local中存储数据的工具模块
1. 向外暴露一个函数(功能)
   只有一个功能需要暴露
2. 向外暴露一个对象(包含多个功能)
   有多个功能需要暴露
 */

const TODOS_KEY = 'todos_key'
export default {
  readTodos () {
    return JSON.parse(localStorage.getItem(TODOS_KEY) || '[]')
  },
  saveTodos (todos) {
    localStorage.setItem(TODOS_KEY, JSON.stringify(todos))
  }
}

/*
export function xxx() {

}

export function yyy () {

}*/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 使用 vue-cli 创建模板项目 1、说明 1)vue-cli 是 vue 官方提供的脚手架工具2)github...
    qianxun0921阅读 3,954评论 0 0
  • 一、 组件component 1. 什么是组件? 组件(Component)是 Vue.js 最强大的功能之一。组...
    饥人谷_Leonardo阅读 6,246评论 0 18
  • 本文章是我最近在公司的一场内部分享的内容。我有个习惯就是每次分享都会先将要分享的内容写成文章。所以这个文集也是用来...
    Awey阅读 13,140评论 4 67
  • 昨天晚上因为有事回家晚了,日记也没有写,儿子早就有困意了,不吼不叫已经坚持快一个礼拜了,孩子高兴我的心里也没有了往...
    凯峰妈妈阅读 1,243评论 0 0
  • 前段时间跟一个朋友聊天,谈论国内与新西兰的各种差异。聊着聊着,我向其吐槽国内生活压力之大,比如我家乡,那里收入不高...
    利兹阅读 4,125评论 0 1

友情链接更多精彩内容