《web前端全栈成长计划》Vue第二章学习笔记(上)
【摘要】 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
显示效果:
一切正常。
(待续)
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
作者其他文章
张辉2020/09/23 10:48:341楼编辑删除举报
《web前端全栈成长计划》Vue第一章学习笔记(上)https://bbs.huaweicloud.com/blogs/196720
《web前端全栈成长计划》Vue第一章学习笔记(下)https://bbs.huaweicloud.com/blogs/196719
《web前端全栈成长计划》Vue第二章学习笔记(上)https://bbs.huaweicloud.com/blogs/196721
《web前端全栈成长计划》Vue第二章学习笔记(下)https://bbs.huaweicloud.com/blogs/196722
《web前端全栈成长计划》Vue第三章学习笔记(上)https://bbs.huaweicloud.com/blogs/196803
(第三章暂时没下,下应该是vue源码分析)
《web前端全栈成长计划》Vue第四章学习笔记(上)https://bbs.huaweicloud.com/blogs/197138
《web前端全栈成长计划》Vue第四章学习笔记(下)https://bbs.huaweicloud.com/blogs/197139
前端小白历险记(一)链接的斜杠怎么没有了?https://bbs.huaweicloud.com/blogs/181124
前端小白历险记(二)原来是你腾讯搞的鬼!https://bbs.huaweicloud.com/blogs/191671
前端小白历险记(三)歪歪扭扭学vue——谈点vue学习时那些不大懂的东西 https://bbs.huaweicloud.com/blogs/197137
前端小白历险记(四)拼死拼活过考核——思想与方法 https://bbs.huaweicloud.com/blogs/197790