《web前端全栈成长计划》Vue第二章学习笔记(上)
本文摘自 论坛 https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=71831&authorid=70062&page=1 笔者对vue学习的笔记。由于vue笔者也是刚刚接触,基本上都是以观看视频和具体动手实践的方式进行。为防止迷路,特收集整理成本博文。
文中的图片、代码很多来自于教程本身,但都来自于本人实践后截图和拷贝的结果,特此说明。
Vue 第二章 Vue项目与案例
18 使用vue-cli创建项目
18.1 创建vue项目
npm install -g vue-cli
vue的工作空间 C:\Users\zhang\WebstormProjects\
创建demo项目
webpack是模板项目之一,项目名称必须是小写
vue init webpack vue_demo
系统帮你用npm自动做npm install
cd vueDemo
18.2 启动vue项目
npm run rev
访问成功。后续需要基于此写代码。
19 基于脚手架编写项目
19.1 vue项目结构
build 配置文件夹
webpack.xxx.conf.js (xxx: base,dev,prod)
config 配置文件夹
index.js
port: 8080(如果有两个需要运行,则需要手工改一下)
autoOpenBrowser 是否自动打开浏览器
node_modules 依赖
src 源码目录
static 静态
放全局资源
.gitkeep GIT使用
.babelrc 作用:ES6转ES5
rc:rumtime control 运行时控制
.eslintrc.js ESLINT的配置
.eslintignore 忽略ESLINT的检查
.gitignore GIT使用
index.html 单页应用的主页
package.json 页面描述文件
README.md
创建vue模板文件:
入口:index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>vue_demo</title> </head> <body> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>
App.vue
<template> <!-- 模板必须有个根标签 --> <div id="app"> <!-- <img src="./assets/logo.png">--> <HelloWorld/> <!-- 3 使用组件标签 --> </div> </template> <script> /* 引入组件 */ import HelloWorld from './components/HelloWorld' /* 向外默认暴露一个对象 */ /* 需要在webstorm配置中disable eslint,(jslint)和jshint检查 */ /* 2 映射组件标签 */ export default { name: 'App', components: { HelloWorld } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
main.js
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. /* 入口js */ /* 创建vue实例 */ import Vue from 'vue' /* 注意大小写 */ import App from './App' Vue.config.productionTip = false /* eslint-disable no-new */ /* 对应index.html的app */ /* 引入组件 将组件的映射名变为标签 组件一般是一个局部功能界面,包含了html,css,js,img等资源 */ /* 模板插入到el匹配的页面上的div去 参见 生命周期 当时判断了 是否有template选项,如果有,则编译template,挂载到页面上去 */ new Vue({ el: '#app', components: { App }, template: '<App/>' })
componets目录下的HelloWorld.vue
<template> <div class="hello"> <img src="../assets/logo.png" alt="logo"> <h1>{{ msg }} <h2>Essential Links <ul> <a href="https://vuejs.org" target="_blank" > Core Docs <a href="https://forum.vuejs.org" target="_blank" > Forum <a href="https://chat.vuejs.org" target="_blank" > Community Chat <a href="https://twitter.com/vuejs" target="_blank" > Twitter <br> <a href="http://vuejs-templates.github.io/webpack/" target="_blank" > Docs for This Template </ul> <h2>Ecosystem <ul> <a href="http://router.vuejs.org/" target="_blank" > vue-router <a href="http://vuex.vuejs.org/" target="_blank" > vuex <a href="http://vue-loader.vuejs.org/" target="_blank" > vue-loader <a href="https://github.com/vuejs/awesome-vue" target="_blank" > awesome-vue </ul> </div> </template> <!-- 配置对象与vue一致 data可以写对象,也可以写函数,但是组件中必须写函数 --> <script> export default { name: 'HelloWorld', data () { return { msg: 'Hello Zhang Hui' } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
显示效果:
20 打包发布项目
20.1 生成打包文件
npm run build
生成dist文件夹
20.2 准备服务器
20.2.1 使用静态服务器
serve dist
20.2.2 使用动态服务器
发布到tomcat
修改配置 webpack.prod.conf.js
output:{
publicPath: '/vue_demo/'
}
直接将dist目录改名为vue_demo拷贝到tomcat的webapps目录下
21 vue eslint代码规范检查工具
eslint检查的基本原理:
内部定义了n个规则
对代码进行规则检查,如果发现有违反规则,则打印错误信息
eslint.org介绍了所有的规则对应的处理方式
eg 期待没有空格,发现有2个空格
如何让一些操0的规则失效。
修改.eslintignore
rules: { // allow async-await 'generator-star-spacing': 'off', // allow debugger during development 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'indent': 'off' }
也可以在.eslintignore文件中忽略检查(不建议)
22 初始化显示
23 交互添加
24 交互删除
拆分页面:
app-->add--->List-->Item
入口:main.js
最外层: App.vue
<template> <!-- 模板必须有个根标签 --> <div id="app"> <header class="site-header jumbotron"> <div class="container"> <div class="row"> <div class="col-xs-12"> <h1>请发表对vue的评论 </div> </div> </div> </header> <div class="container"> <!-- 将组件中的函数传出去 --> <Add :addComment="addComment"/> <br> <List :comments="comments" :deleteComment="deleteComment"/> </div> </div> </template> <script> /* 引入组件 */ import Add from './components/Add.vue' import List from './components/List.vue' /* 向外默认暴露一个对象 */ /* 需要在webstorm配置中disable eslint,(jslint)和jshint检查 */ /* 2 映射组件标签 */ /* 组件间的通讯:通过标签 */ /* 采用了HMR技术,并不是刷新整个模块 */ export default { name: 'App', /* return对象用于放实际的评论数据 */ data() { return { comments: [ { name: '张辉', content: 'vue还行' }, { name: '李四', content: 'vue太慢' }, { name: '王五', content: 'vue很难' } ] } }, methods: { // 增加评论 addComment(comment) { this.comments.unshift(comment) }, // 删除指定下标的评论,该函数应该逐级传递给Item deleteComment(index) { this.comments.splice(index, 1) } }, components: { Add, List } } </script> <style> /*#app {*/ /* font-family: 'Avenir', Helvetica, Arial, sans-serif;*/ /* -webkit-font-smoothing: antialiased;*/ /* -moz-osx-font-smoothing: grayscale;*/ /* text-align: center;*/ /* color: #2c3e50;*/ /* margin-top: 60px;*/ /*}*/ </style>
components目录
三个组件
1.Add.vue
<template> <div> <!-- Add组件--> <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 type="text" 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 bth-default pull-right" @click="add">提交</button> </div> </div> </form> </div> </div> </template> <script> export default { props: { addComment: { // 指定了属性名,属性值的类型以及必要性 type: Function, required: true } }, data() { return { name: '', content: '' } }, 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) // 4清除输入 this.name = '' this.content = '' } } } </script> <style> </style>
2.List.vue
<template> <div> <!-- List组件--> <div class="col-md-8"> <h3 class="reply">评论回复:</h3> <h2 v-show="comments.length===0">暂无评论,点击左侧添加评论!!! <ul class="list-group"> <Item v-for="(comment,index) in comments" :key="index" :comment="comment" :deleteComment="deleteComment" :index="index"/> </ul> </div> </div> </template> <script> import Item from './Item.vue' export default { /* 声明接收属性:这个属性会成为组件对象的属性,可以被模板直接读取,此时this是组件对象 */ props: ['comments', 'deleteComment'], components: { Item } } </script> <style> .reply{ margin-top: 0px; } 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 .sentence { padding: 0px 50px; } .user{ font-size: 22px; } </style>
3.Item.vue
<template> <div> <!-- Item组件--> <li class="list-group-item"> <div class="handle"> <a href="javascript:;" @click="deleteItem">删除</a> </div> <p class="user"><span>{{comment.name }}<span>说: <p class="sentence">{{ comment.content }}! </li> </div> </template> <script> export default { // props:['comment'] // 指定属性名 props: { comment: Object, deleteComment: Function, index: Number }, methods: { deleteItem() { const { comment, index, deleteComment } = this if (window.confirm(`确定删除${comment.name}的评论吗?`)) { deleteComment(index) } } } } </script> <style> </style>
显示效果:
原始页面:
新增:
删除确认:
删掉一个:
删完:
第二个练习 todolist
添加
列表显示
删除
统计
25 静态组件
先拆分组件
todoheader+ todolist+todofooter
list再拆分:todoitem
将静态组件html和css分别拆到各个模块中去。
26 动态初始化显示
动态组件:
实现数据的动态显示
设计todos数组 ,数组每个元素都是一个对象 标题title,勾选状态completed。
数据放在哪个组件保存?
分析后发现都需要,则todos在App.vue实现。
注意以下问题:
1.App.vue要向下传输todos,需要在 模板中
<TodoList :todos="todos"/>
2.TodoList.vue要从App.vue获取todos,需要在props定义todos
3.TodoList.vue需要引用TodoItem.vue的循环体,需要在components引用TodoItem,并且在模板中写TodoItem
4.TodoItem.vue需要从TodoList.vue 获取todo和index,需要在props中定义todo和index
5.遇到input ,记得写v-model
6.任何地方记得引号别忘了。
显示效果:
27 header交互增加
方法类似,需要在App.vue增加addTodo函数(因为todos 在App,所以方法也需要在App)。然后传递(暴露)给TodoHeader。
在TodoHeader的input使用v-model绑定数据,且使用@keyup.enter来调用新增.(在此时调用从App传来的Function:addTodo
显示效果如下:
28 交互删除
目标:当鼠标移入时,显示删除,并将颜色修改。 点击删除后,删除当时的Todo
App需要新增deleteTodo,然后先后传递给TodoList和TodoItem;最后在btn增加deleteItem.
TodoItem需要处理onmouseenter和onmouseleave事件。在over时背景色修改为灰色,并显示删除字样,如leave时,则修改背景色为白色,并啥也不显示。
并且在li中需要进一步处理background和btn的是否显示按钮。
显示效果如下:
删除时:
删除后:
29 footer处理
分析:
显示:已完成和全部
点击全选后,用户操作清除已完成。
要点:
在App和TodoFooter中修改
App传递 selectAllTodos和deleteCompleteTodos函数给footer
selectAllTodos的实现:将每个todo改为check的值,check为true则都为true,check为false则都为false
deleteCompleteTodos的实现:使用filter将非complte的找到。(删除已完成等于留下未完成)
TodoFooter:
使用computeSize计算属性记录已选中的 this.todos.reduce((preTotal, todo) => preTotal + (todo.complete?1:0),0)
使用isAllCheck计算属性的get和set方法跟todos发生联动
显示效果:
全选:
全不选:(注意清除按钮也消失)
清除若干——清除前:
清除后:
清除全部:
30 数据保存
浏览器关闭后,如果还能保存,该怎么做?
将状态保存在文件中。利用localStorage
什么时候存:只要todos发生改变都要存(删除,添加,勾选都要存)
什么时候读:打开就应该读
存啥?todos
要点:
todos初始化时从localStorage读取 todos: JSON.parse(window.localStorage.getItem('todos_key') || '[]')
watch深度监视,使用handler保存最新的todos数据到localStorage中:
效果:
可以到 Application->LocalStorage查看到具体的值。
31 自定义事件(组件间通讯的第二种方式)
前面使用了:组件间通信的第一种方式:props
子组件跟父组件通信。
31.1事件作用:
1。绑定监听,
2。触发事件
31.2 用法1(绑定事件监听的第一种方式)
1.v-on: 绑定自定义事件
eg1: v-on:侦听名称:回调函数
v-on:increment="incrementTotal"
eg2:@on(事件名eventName,回调函数function)
2.使用 $emit(eventName,optionalPayload)触发事件
3.修改内容:
App.vue
原来:(传递函数)
<TodoHeader :addTodo="addTodo"/>
改为:
删除header props中的addTodo函数
props: { // 声明接收 // addTodo: Function, },
给TodoHeader标签绑定addTodo的事件侦听
<TodoHeader @addTodo="addTodo"/>
TodoHeader.vue
原来:
// 3.将todo对象添加到todos this.addTodo(todo)
改为:
// 3.将todo对象添加到todos // this.addTodo(todo) // 触发自定义事件 this.$emit('addTodo',todo)
App父组件绑定了事件监听,当子组件发送变化时,触发了事件.
4.效果如下:
原始:
增加后:
31.3 用法2(绑定事件监听的第二种方式)
1.$on:
2.修改内容:
App.vue
原来:
<TodoHeader :addTodo="addTodo"/>
改为:(定义ref)
<TodoHeader ref="header"/>
删除header props中的addTodo函数
props: { // 声明接收 // addTodo: Function, },
增加生命周期mounted的处理:
mounted () { // 在生命周期回调函数中执行绑定 // 给TodoHeader绑定addTodo事件监听 // this.$on('addTodo',this.addTodo) //这是给app绑定的监听,不对 this.$refs.header.$on('addTodo',this.addTodo) },
TodoHeader.vue
改法跟方法一一致。
3.效果如下:
第一种方式简单,适用于父子之间。但不适合经过多层(传递)的场景。
32 消息订阅和发布(组件间通讯的第三种方式)
1下载库
npm info pubsub-js
npm install --save pubsub-js
2.库提供了2个方法:
Publish
Subscribe
3.修改方法:
App.vue
<TodoList :todos="todos" :deleteTodo="deleteTodo"/>
改为:
<TodoList :todos="todos" />
并删除TodoList的deleteTodo函数引用
props: { todos: Array, // deleteTodo: Function },
以及TodoList的deleteTodo操作
<TodoItem v-for="(todo, index) in todos" :key="index" :todo="todo" :index="index" />
进一步删除TodoItem的相关操作:
props: { todo: Object, index: Number, // deleteTodo: Function },
和
deleteItem() { const {todo, index, deleteTodo} = this if(window.confirm(`确认删除${todo.title}`)){ // deleteTodo(index) } }
下面需要进行发布消息和订阅消息操作。
订阅消息就是绑定事件监听
发布消息就是触发事件
import增加
import PubSub from 'pubsub-js'
在mounted里面订阅消息:
mounted () { // 在生命周期回调函数中执行绑定 // 给TodoHeader绑定addTodo事件监听 // this.$on('addTodo',this.addTodo) //这是给app绑定的监听,不对 this.$refs.header.$on('addTodo',this.addTodo) //订阅消息 PubSub.subscribe('deleteTodo',(msg,index) => { // msg无用,使用箭头函数。 this.deleteTodo(index) }) },
在TodoItem增加发布消息的处理
引入PubSub
import PubSub from 'pubsub-js'
在deleteItem发布消息:
deleteItem() { const {todo, index, deleteTodo} = this if(window.confirm(`确认删除${todo.title}`)){ // deleteTodo(index) //发布消息 PubSub.publish('deleteTodo',index) } }
4.显示效果:
删除时:
删除后:
好处:两个组件通信, 没有任何位置要求。父子,兄弟都可以做。
33 Slot(组件间通讯的第四种方式)
组件间通过标签通信
原理:
以饿了么app为例
同一个组件写了2个标签
设计组件时:设计占位
使用时:向占位传递内容
组件不仅仅是数据变化,而且标签(结构)也有变化。
传什么,显示什么。
传递的内容是:标签
具体做法:
反复用到时适用slot
修改方式:
TodoFooter的修改:
<template> <div class="todo-footer"> <label> <!-- 改为slot方式 编写占位 --> <!-- <input type="checkbox" v-model="isAllCheck"/>--> <slot name="checkAll"></slot> </label> <span> <!-- <span>已完成{{computeSize}} / 全部{{todos.length}}--> <slot name="count"></slot> <!-- <button class="btn btn-danger" v-show="computeSize" @click="deleteCompleteTodos">清除已完成任务</button>--> <slot name="delete"></slot> </div> </template> <script> export default { props: { // todos: Array, // deleteCompleteTodos: Function, // selectAllTodos: Function // 选择所有或者不选择所有 }, // computed: { // computeSize() { // return this.todos.reduce((preTotal, todo) => preTotal + (todo.complete?1:0),0) // }, // // // 难点在于可能想不到使用计算属性 // isAllCheck: { // get() { // return this.computeSize === this.todos.length && this.computeSize>0 // }, // // set(value) { // value是checkbox最新值 // this.selectAllTodos(value) // } // } // } } </script>
App的修改:
<template> <div class="todo-container"> <div class="todo-wrap"> <!-- - 自定义事件可以代替 传递函数 给TodoHeader绑定 addTodo的事件监听 --> <TodoHeader ref="header"/> <TodoList :todos="todos" /> <!-- <TodoFooter :todos="todos" :deleteCompleteTodos="deleteCompleteTodos" :selectAllTodos="selectAllTodos"/>--> <todo-footer> <!-- 需要注意的是:原来在子组件编写的相关代码,都需要迁移到父组件去 --> <input type="checkbox" v-model="isAllCheck" slot="checkAll"/> <span slot="count">已完成{{computeSize}} / 全部{{todos.length}} <button class="btn btn-danger" v-show="computeSize" @click="deleteCompleteTodos" slot="delete">清除已完成任务</button> </todo-footer> <!-- 另一种写法: todo-footer --> </div> </div> </template>
此时需要将原来在TodoFooter实现的computed移植到App中:
computed: { computeSize() { return this.todos.reduce((preTotal, todo) => preTotal + (todo.complete?1:0),0) }, // 难点在于可能想不到使用计算属性 isAllCheck: { get() { return this.computeSize === this.todos.length && this.computeSize>0 }, set(value) { // value是checkbox最新值 this.selectAllTodos(value) } } },
演示效果:
基本跟原来一致。
另外,组件间通讯的第5种方式:vuex放在第四章讲解(尚未开放)
34 存储优化
将读写localStorage的功能单独用一个模块实现
创建util目录,创建storageUtil.js文件
/* 使用LocalStorage存储数据的工具模块 向外暴露:函数或者对象 如何选择? 一个函数是一个功能 一个对象是多个功能 选择依据是:需要暴露一个功能还是多个功能 */ const TODOS_KEY = 'todos_key' export default { saveTodos (todos) { window.localStorage.setItem(TODOS_KEY,JSON.stringify(value)) }, readTodos(){ return JSON.parse(window.localStorage.getItem(TODOS_KEY) || '[]') } }
修改app.vue
1.先import
// 引入工具 import storageUtil from './util/storageUtil'
2.修改load部分
// todos: JSON.parse(window.localStorage.getItem('todos_key') || '[]') todos: storageUtil.readTodos()
3.修改read部分
// window.localStorage.setItem('todos_key',JSON.stringify(value)) storageUtil.saveTodos(value)
或改写handler
deep: true, // 深度监视 // handler: function( value){ // //将todos最新值的JSON数据,保存到localStorage // // window.localStorage.setItem('todos_key',JSON.stringify(value)) // storageUtil.saveTodos(value) // } handler: storageUtil.saveTodos
显示效果:
一切正常。
(待续)
- 点赞
- 收藏
- 关注作者
评论(0)