《web前端全栈成长计划》Vue第四章学习笔记(下)
Vue 第四章 Vuex
73 vuex-todolist的缓存实现
73.1 分析:
读的实现
由App.vue异步读backend保存的localStorage的todos内容
依次实现 actions.js, mutation-types.js,mutation.js
写的实现:
TodoList.vue的计算属性todos会自动转换为对象的属性
在TodoList.vue中实现深度监视。
73.2 实践:
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' /* eslint-disable no-new */ /* 对应index.html的app */ /* 引入组件 将组件的映射名变为标签 组件一般是一个局部功能界面,包含了html,css,js,img等资源 */ /* 模板插入到el匹配的页面上的div去 参见 生命周期 当时判断了 是否有template选项,如果有,则编译template,挂载到页面上去 */ new Vue({ el: '#app', components: { App }, template: '<App/>', store })
App.vue
<template> <div class="todo-container"> <div class="todo-wrap"> <!-- 以前传函数,现在不要传了,都从store读就可以了--> <TodoHeader /> <TodoList /> <TodoFooter /> </div> </div> </template> <script> // 引入 import TodoHeader from './components/TodoHeader' import TodoList from './components/TodoList' import TodoFooter from './components/TodoFooter' import storageUtils from './utils/storageUtils' export default { mounted() { // 异步获取保存的todos数据,并显示 // 发送命令给action this.$store.dispatch('reqTodos') }, // 加入标签 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>
componets/TodoHeader.vue
<template> <div class="todo-header"> <!-- 第一步 给目标元素绑定监听 --> <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="addItem"/> </div> </template> <script> export default { 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>
componets/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' import storageUtils from '../utils/storageUtils' export default { //声明 // props: { // todos: Array, // deleteTodo: Function // }, // 计算属性也会成为组件对象的属性 computed: { ...mapState(['todos']) }, watch: { //监视todos的所有变化 todos: { deep: true, // 深度监视 handler: storageUtils.saveTodos //一旦发现变了,保存todos到localStorage } }, 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>
componets/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> </template> <script> export default { props: { todo: Object, index: Number }, 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>
componets/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>
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/state.js
/* 状态对象 */ export default { todos: [] }
store/actions.js
// 接收xxx.vue的组件通知,触发mutation。js调用间接更新状态的方法的对象 import { ADD_TODO,DELETE_TODO,SELECT_ALL_TODOS,CLEAR_ALL_COMPLETED } from './mutation-types' import storageUtils from '../utils/storageUtils' 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) }, //异步获取todos reqTodos ({commit}) { //模拟 setTimeout(()=>{ const todos = storageUtils.readTodos() //提交mutation commit(RECEIVE_TODOS, todos) },1000) } }
store/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 export const RECEIVE_TODOS = 'receive_todos' // 接收todos
store/mutation.js
/* 包含多个由action触发,直接更新状态方法的对象 */ import {ADD_TODO, DELETE_TODO, SELECT_ALL_TODOS, CLEAR_ALL_COMPLETED, RECEIVE_TODOS} 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) }, [RECEIVE_TODOS] (state, todos) { state.todos = todos } } // 从组件开始,到action,到mutation,再更新状态
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 } }
73.3 实际效果:
两个问题:
List显示的是歪的。
关闭浏览器重启后并没有保存原来的效果
检查日志:
发现RECEIVE_TODOS并没有生效。。。
检查了一下action.js:
import { ADD_TODO,DELETE_TODO,SELECT_ALL_TODOS,CLEAR_ALL_COMPLETED } from './mutation-types'
这里忘记引用了RECEIVE_TODOS
修改为:
import { ADD_TODO,DELETE_TODO,SELECT_ALL_TODOS,CLEAR_ALL_COMPLETED,RECEIVE_TODOS } from './mutation-types'
就可以生效了。
第二个问题:关于button浮动的问题,在 @胡琦 大大的提示下,检查了下浮动的问题,把li标签的float去掉就好了。。
li label { /*float: left;*/ /*cursor: pointer;*/ }
最后的具体效果如下:
删除后:
关掉浏览器,再打开
这样缓存也没问题,位置也没问题了。。
74 vuex的render配置
找到src下的入口js
原始写法:
new Vue({ el: '#app', components: { App }, // 将APP映射成标签 template: '<App/>', store })
更简洁的写法:2个配置变成1个
new Vue({ el: '#app', render: h=> h(App), // 渲染函数,函数接收参数是一个匿名函数,接收App组件,箭头代表是一个函数,同时代表是一个return。 store })
其实上面render那句等同于:
new Vue({ el: '#app', render: function( createElement ) { return createElement(App) // 返回的结果会插入到el定义的div里面去。 }, store })
75 vuex的概念总结
state 对象 vuex管理的状态 ,指定了很多状态属性的初始值。唯一的。
eg: const state = { xxx: initValue }
mutations 对象 方法是直接更新state。通过state进行状态引用,并可以接收参数
eg: const mutations = { yyy(state,data1) { //更新state操作 } }
actions 对象 函数跟组件中的回调函数是对照关系。action不直接更新,而是通过commit触发mutation更新。通过state进行状态应用,并可以接收参数。需要把数据包装成对象传递。actions里面可以去执行ajax等异步调用
eg: const actions = { xxx({ commit, state},data1){ commit('yyy',{data1}) } }
vue componets通过dispatch调用action (组件更接近用户)
eg: $store.dispatch('action名称',data1)
getters 计算属性的get的对象。getters由组件使用。(封装性:定义语法时尽量让上层简单)组件通过 $store.getters.xxx调用
eg: const getters = { mmm (state) { return ...} }
modules: 模块:范例是src-shop。对于大型的项目模块适用。每个功能模块创建一个modules下的js。每个modules里面里面都有一整套(state,mutation,action等)使用频率不高。
向外暴露一个store对象。基本语法,不会变。
组件中实现简化的语法 mapState,mapGetters,mapActions
eg: import {mapGetters, mapActions} from 'vuex'
computed: { ...mapState(['xxx']), ...mapGetters(['mmm']), }
methods mapActions(['zzz'])
映射store 在main.js中。
store对象:this.$store 由state和getters属性, 有dispatch方法可以分发调用action
特点:结构清晰。模型简洁,易于上手。
(全文完,谢谢阅读)
- 点赞
- 收藏
- 关注作者
评论(0)