《web前端全栈成长计划》Vue第四章学习笔记(上)
【摘要】 web前端全站成长计划第三阶段vue课程第四章的学习笔记(上)
本文摘自 论坛 https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=71831&authorid=70062&page=1 笔者对vue学习的笔记。由于vue笔者也是刚刚接触,基本上都是以观看视频和具体动手实践的方式进行。为防止迷路,特收集整理成本博文。
文中的图片、代码很多来自于教程本身,但都来自于本人实践后截图和拷贝的结果,特此说明。
Vue 第四章 Vuex
66 vuex:counter应用的vuex实战1
66.1 安装vuex
66.2 实战
创建vuex的核心管理对象模块vuex
编写store.js ,main.js及App.vue
store.js
/* vuex的核心管理对象模块 */
import Vue from 'vue'
import Vuex from 'vuex'
// 声明使用
Vue.use(Vuex)
// 状态
const state = {
count: 0 // 从App的data移过来,初始化状态
}
// 包含多个更新state函数的对象
const mutations = {
// 本质上只有+1和-1两个操作
// 增加的mutation
INCREMENT(state){
state.count ++
},
// 减少的mutation
DECREMENT(state){
state.count --
}
}
// 包含多个对应事件回调函数的对象
const actions = {
// 一个函数就是一个action
// 增加
increment({commit}){
// 提交一个mutation commit最终导致一个mutation调用
commit('INCREMENT')
},
decrement({commit}){
commit('DECREMENT')
},
// 带条件的action
incrementIfOdd({commit,state}){
// 提交一个mutation commit最终导致一个mutation调用
if(state.count %2 === 1){
commit('INCREMENT')
}
},
// 异步action
incrementAsync({commit}){
// 在action中可以直接执行异步代码
setTimeout(() => {
commit('INCREMENT')
},1000)
}
}
// 包含多个getter计算属性函数的对象
const getters = {
evenOrOdd (state) {
return state.count % 2 === 0 ? '偶数': '奇数'
}
}
// 以下都是固定的
export default new Vuex.Store({
state, // 状态
mutations, // 包含多个更新state函数的对象
actions, // 包含多个对应事件回调函数的对象
getters // 包含多个getter计算属性函数的对象
})
main.js. 引入store对象
/*
入口JS
*/
import Vue from 'vue'
import App from './App.vue'
import store from './store'
// 创建vm
/* eslint-disable no-new */
new Vue({
el: '#app',
components: {App}, // 映射组件标签
template: '<App/>', // 指定需要渲染到页面的模板
store // 会产生 $store对象,有state,getter属性。
})
App.vue
<template>
<div>
click {{ $store.state.count }} times, count {{ evenOrOdd }} <button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>
</div>
</template>
<script>
export default {
// data () {
// return {
// }
// },
computed: {
evenOrOdd () {
// return this.count % 2 === 0 ? '偶数': '奇数'
return this.$store.getters.evenOrOdd
// 不需要调用,只需要读取属性值,即可自动调用getter
// this代表组件对象
}
},
methods: {
increment () {
// 需通知vuex去增加
this.$store.dispatch('increment')
// 触发store中对应的action调用。
},
decrement () {
this.$store.dispatch('decrement')
},
incrementIfOdd () {
this.$store.dispatch('incrementIfOdd')
},
incrementAsync () {
this.$store.dispatch('incrementAsync')
}
}
}
</script>
<style>
</style>
从上面可以看出,App.cue变得异常简单。很多逻辑都迁移到了store.js
今后,如果store.js比较多,可能也会将
state, // 状态
mutations, // 包含多个更新state函数的对象
actions, // 包含多个对应事件回调函数的对象
getters
这四个定义放到多个文件中。分别去定义。
66.3 演示效果:
67 vuex:counter应用的vuex实战2
使用mapState, mapGetters, mapActions简化映射:
App.vue:
<template>
<div>
click {{ $store.state.count }} times, count {{ evenOrOdd }} <button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions} from 'vuex'
export default {
computed: {
...mapState(['count']), //相当于上面的内容 mapState返回值也是对象
...mapGetters(['evenOrOdd']), // mapGetters 返回值是对象
},
methods: {
// 回调函数名要跟action一致
...mapActions(['increment','decrement','incrementIfOdd','incrementAsync'])
}
}
</script>
<style>
</style>
当回调函数跟action的函数名不一致时,可以用以下方法做对照:
比如:
store.js改为
const getters = {
evenOrOdd2 (state) {
return state.count % 2 === 0 ? '偶数': '奇数'
}
}
App.vue的mapGetters改为:
...mapGetters({evenOrOdd: 'evenOrOdd2'}) //后面是getters的名字,前面是app中回调函数的名字
执行效果如下:
跟前面的一致。
68 vuex结构图
backend api:后台api
在action可以发送后台请求,跟后台交互
devtools: chrome开发工具
监视者mutation的调用。
我们将具体的vuex结构图完善一下:
、
69 vuex版本的todolist——创建结构
70 vuex版本的todolist——header组件
71 vuex版本的todolist——list和item组件
72 vuex版本的todolist——footer组件
实战配置:
vuex用来管理多组件共享的状态。
分析:
todos状态数据应该放入到vuex的store中。
真实项目:拆分成多个模块
App.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<!-- 以前传函数,现在不要传了,都从store读就可以了-->
<!-- <TodoHeader :addTodo="addTodo"/>-->
<TodoHeader />
<!-- <TodoList :todos="todos" :deleteTodo="deleteTodo"/>-->
<TodoList />
<!-- <TodoFooter :todos="todos" :deleteCompleteTodos="deleteCompleteTodos" :selectAllTodos="selectAllTodos"/>-->
<TodoFooter />
<!-- 另一种写法: todo-footer -->
</div>
</div>
</template>
<script>
// 引入
import TodoHeader from './components/TodoHeader'
import TodoList from './components/TodoList'
import TodoFooter from './components/TodoFooter'
export default {
data() {
return {
// 换成从localStorage读取到todos
// todos: [ // 交给list显示
// {title: '前端开发工作', complete: false},
// {title: '后端开发工作', complete: true},
// {title: '锻炼身体', complete: false}
// ]
// 存的是文本,字符串转为数组
// 如果没有数据,则存储 []
todos: JSON.parse(window.localStorage.getItem('todos_key') || '[]')
// 深度监视
// 一般的监视不能监视属性。
}
},
// methods: {
// addTodo(todo) {
// this.todos.unshift(todo)
// },
// deleteTodo(index) {
// this.todos.splice(index,1)
// },
//
// //全选或者全不选,取自于左下角的check
// selectAllTodos(check) {
// this.todos.forEach(todo => todo.complete = check)
// },
// // 找到所有的成功的,删掉
// deleteCompleteTodos() {
// this.todos = this.todos.filter(todo => !todo.complete)
// }
//
// },
// watch: {
// todos: {
// deep: true, // 深度监视
// handler: function( value){
// //将todos最新值的JSON数据,保存到localStorage
// window.localStorage.setItem('todos_key',JSON.stringify(value))
// }
// }
// },
// 加入标签
components: {
TodoHeader,
TodoList,
TodoFooter
}
}
</script>
<style>
/*app*/
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</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'
import store from './store'
import './base.css'
// 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/>',
store
})
store/state.js
/* 状态对象 */
export default {
todos: []
}
store/index.js
/* vuex 最核心的管理对象 */
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
store/actions.js
// 接收xxx.vue的组件通知,触发mutation。js调用间接更新状态的方法的对象
import { ADD_TODO,DELETE_TODO,SELECT_ALL_TODOS,CLEAR_ALL_COMPLETED } from './mutation-types'
export default {
addTodo ({commit},todo) {
// 提交一个mutation commit最终导致一个mutation调用
// 无论是什么类型,都用对象包起来
commit(ADD_TODO, {todo})
},
deleteTodo ({commit},index) {
// 无论是什么类型,都用对象包起来
commit(DELETE_TODO, {index})
},
selectAllTodos ({commit},check) {
commit(SELECT_ALL_TODOS, {check})
},
clearAllCompleted ({commit}) {
commit(CLEAR_ALL_COMPLETED)
}
}
store/getters.js
// 包含所有state的所有计算属性
export default {
//总数
totalCount(state) {
return state.todos.length
},
//完成的数量
completeCount(state) {
return state.todos.reduce((preTotal, todo) => {
return preTotal + (todo.complete ? 1 : 0)
},0)
},
//判断是否全部选中
isAllSelected (state, getters) {
// return getters.totalCount === getters.completeCount && getters.totalCount > 0
return getters.totalCount === getters.completeCount && state.todos.length > 0
}
}
stores/mutation-types.js
/*
所有mutation 的名称常量
*/
export const ADD_TODO = 'add_todo' // 添加todo
export const DELETE_TODO = 'delete_todo' // 删除todo
export const SELECT_ALL_TODOS = 'select_all_todos' // 选择所有
export const CLEAR_ALL_COMPLETED = 'clear_all_completed' // 清除已完成的todo
stores/mutation.js
/*
包含多个由action触发,直接更新状态方法的对象
*/
import {ADD_TODO,DELETE_TODO,SELECT_ALL_TODOS,CLEAR_ALL_COMPLETED} from './mutation-types'
export default {
// 这里变成中括号比较难以理解
[ADD_TODO] (state, {todo}) {
state.todos.unshift(todo)
},
[DELETE_TODO] (state, {index}) {
state.todos.splice(index,1)
},
[SELECT_ALL_TODOS] (state, {check}) {
state.todos.forEach(todo => todo.complete = check)
},
[CLEAR_ALL_COMPLETED] (state) {
state.todos = state.todos.filter( todo => !todo.complete)
}
}
// 从组件开始,到action,到mutation,再更新状态
components/TodoHeader.vue
<template>
<div class="todo-header">
<!-- 第一步 给目标元素绑定监听 -->
<input type="text" placeholder="请输入你的任务名称,按回车键确认"
v-model="title" @keyup.enter="addItem"/>
</div>
</template>
<script>
export default {
// props: {
// // 声明接收
// addTodo: Function,
// },
data() {
return {
title: '' //这个只是当前组件内部使用,仍然放在这里即可,不需要被vuex管理
}
},
methods: {
addItem() {
// 1检查输入的合法性
const title = this.title.trim()
if(!title){
alert('不能为空')
return
}
// 2.根据输入生成一个对象
const todo = {
title,
complete: false
}
// 3.将todo对象添加到todos
// this.addTodo(todo)
// 这时需要通知vuex
this.$store.dispatch('addTodo',todo)
// 4.清除输入
this.title = ''
}
}
}
</script>
<style>
/*header*/
.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>
components/TodoList.vue
<template>
<ul class="todo-main">
<TodoItem v-for="(todo, index) in todos" :key="index" :todo="todo" :index="index" />
</ul>
</template>
<script>
import { mapState} from 'vuex'
import TodoItem from './TodoItem'
export default {
//声明
// props: {
// todos: Array,
// deleteTodo: Function
// },
computed: {
...mapState(['todos'])
},
components: {
TodoItem
}
}
</script>
<style>
/*main*/
.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;
}
/*item*/
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>
components/TodoItem.vue
<template>
<!--进入内部元素 会促发mouseout;只有离开才会使用mouseleave
所以应该选择enter和leave
-->
<li @mouseenter="handleEnter(true)" @mouseleave="handleEnter(false)"
:style="{background: bgColor}">
<label>
<input type="checkbox" v-model="todo.complete"/>
<span>{{ todo.title }} </label>
<button class="btn btn-danger" v-show="isShow" @click="deleteItem">删除</button>
</li>
</template>
<script>
export default {
props: {
todo: Object,
index: Number
// deleteTodo: Function
},
data() {
return {
bgColor: 'white', // 背景颜色
isShow: false // 按钮默认是否显示
}
},
methods: {
handleEnter(isEnter) {
if(isEnter) {
this.bgColor = 'gray'
this.isShow = true
} else {
this.bgColor = 'white'
this.isShow = false
}
},
deleteItem() {
// const {todo, index, deleteTodo} = this
// if(window.confirm(`确认删除${todo.title}`)){
// // deleteTodo(index)
// this.$store.dispatch("deleteTodo",index)
// }
this.$store.dispatch('deleteTodo', this.index)
}
}
}
</script>
<style>
/*item*/
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>
components/TodoFooter.vue
<template>
<div class="todo-footer">
<label>
<input type="checkbox" v-model="isAllComplete"/>
</label>
<span>
<span>已完成{{completeCount}} / 全部{{totalCount}}
<button class="btn btn-danger" v-show="completeCount" @click="clearAllCompleted">清除已完成任务</button>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapGetters(['totalCount','completeCount']),
// isAllComplete有点特殊
isAllComplete: {
get() {
return this.$store.getters.isAllComplete
},
set(value) { // value是checkbox最新值
this.$store.dispatch('selectAllTodos', value)
}
}
},
methods: {
...mapActions(['clearAllCompleted'])
}
}
</script>
<style>
/*footer*/
.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>
实战效果:
新增后:
删除:
清除已完成:
(待续)
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
作者其他文章
张辉2020/09/23 10:49:081楼编辑删除举报
《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