【项目-喵内记账-meoney-05】Statistic.vue 组件 列表统计页面 -标记问题
大纲链接 §
[toc]
知识概要
- 封装 Tabs,样式使用 deep 深度作用选择器,覆盖子组件内部元素样式
- 用 JS 配置组件默认
height
- 用列表展示数据
- 添加
Statistic.vue
SCSS ISO8601
和dayjs
- 数据排序
- 数据排序后分组
- 完成统计页面
createdAt of undefined
的解决办法
封装 Tabs.vue
重构Types.vue
样式,使用 深度作用选择器 deep
语法
查看
Statistic.vue
结构, 可复用Types.vue
组件
- 注意样式加了
scoped
属性,只影响当前组件的标签,不会深入组件标签继续影响内部结构- 组件
<Type class="x"/>
d的样式只是加在表层的<div>
上 - 即使写了
.x li {...;}
的样式,不对内部<li>
有任何影响
- 组件
- 可以在不污染其他组件样式的同时,使用
/deep/
或::v-deep
(SCSS)可以深入影响内部结构的样式 - 搜索
site: vuejs.org deep
:Scoped CSS 深度作用选择器 deep
语法
|
|
Statistic.vue
中,在<Type/>
上,对Types.vue
组件传入绑定属性:type.sync="yyy"
|
|
深度作用选择器 选中的样式
&.selected {}
|
|
还有一个缺点,就是如果多层嵌套,无法通过类选择器精确/动态控制样式
最佳实践:精准控制组件内部结构的样式
使用 对象控制绑定的样式(表驱动) 和 动态前缀 来控制组件内部标签的样式
- Class 与 Style 绑定
- 从父组件传入一个 前缀字符串 到子组件中,由各不同子组件去拼接,就可实现精确/动态控制样式
- 在
Types.vue
添加外部数据:属性前缀class-prefix
- 注意写清外部数据类型:
@Prop(String) readonly classPrefix?: string;
- 注意数据由于是外部传来的,无法指定初始化类型,强行指定类型
!:
或者可选?:
- 注意写清外部数据类型:
- 用对象的形式赋值给绑定的类属性
:class="{selectorA: true, selectorB: false, selectorC: expressions, active: isActive}"
- 当对象的属性值(一般是表达式)为
true
时,属性(键)对应的类选择器生效 - 官方解释:
active
这个class
存在与否将取决于数据property
isActive
的truthiness
- 可绑定多个选择器
- 可以与普通的
class attribute
共存:<div class="static active" :class="{dynamicClss: true}"></div>
- 动态地切换
class
:当isActive
变化时,class
列表将相应地更新- 当
isActive: true
,class
列表将变为class="static active"
- 当
- 当对象的属性值(一般是表达式)为
- 动态属性前缀
<li :class="{[classPrefix + '-item']: classPrefix, selected: value==='-'}">
- 如果
key
里有变量,使用ES6对象动态属性{[xxx]: 'yyy'}
- 使用(表达式)作为对象的属性名,即把表达式放在方括号内
- 还可以拼接字符串:
{[classPrefix + '-item']: classPrefix}
- 如果
- 表驱动:将所有类名以表的形式给出
Statistic.vue
|
|
Type.vue
|
|
- 可以用前缀精确地获取类名
zzz-item
,控制内部的元素 - 可以直接用深度作用选择器作为最外部选择器,但会造成被子组件原来的样式干扰
- 传入不同的前缀实现样式互相隔绝,BEM风格
- 注意
CSS
权重计算,子组件比父组件后加载
可以声明一个变量,传对象,将该对象作为
:class=""
的绑定值
抽离Types.vue
为Tabs.vue
组件,可复用切换逻辑
可现实多项内容
Tabs
切换
- 需要外部传参:对象数组包含显示文字和类型字符
+/-
、前缀字符 - 内容为父组件传入
@Prop({required: true, type: Array}) dataSource!: { text: string; type: string }[];
- 声明类型
type DataSource = { text: string; type: string }
dataSource
改为@Prop({required: true, type: Array}) dataSource!: DataSource[];
type
类型字符串@Prop(String) readonly type!: string;
classPrefix
前缀字符串@Prop(String) readonly classPrefix?: string;
重构
Tabs.vue
|
|
重构
Statistic.vue
<Tabs class-prefix="interval" :data-source="typeList" :type.sync="type"/>
- 样式前缀:
class-prefix="interval"
,子组件默认样式 - 传
dataSource
为intervalList
- 声明
intervalList = [{text: '按天', type: 'day'}, {text: '按周', type: 'week'}, {text: '按月', type: 'month'}];
- 传初始绑定的同步数据
:type.sync="interval"
为interval = 'day';
(按天)
- 声明
- 样式前缀:
- 用
Tabs.vue
改造Types.vue
:<Tabs class-prefix="type" :data-source="typeList" :type.sync="type"/>
- 样式前缀:
class-prefix="type"
:::v-deep .type-item {...}
- 声明数据源:
typeList = [{text: '支出', type: '-'}, {text: '收入', type: '+'}]
- 传初始绑定的同步数据:
:type.sync="type"
为type = '-';
(支出)
- 样式前缀:
|
|
动态属性liClass
作为样式Tabs.vue
|
|
- 遍历数据
dataSource
,绑定:key="item.type"
,插值{{ item.text }}
<li v-for="item in dataSource" :key="item.type">{{ item.text }}</li>
- 注册事件
@click="select(item)"
<li v-for="item in dataSource" :key="item.type" @click="select(item)">{{ item.text }}</li>
class
绑定::class="{[this.classPrefix +'-tabs-item']: this.classPrefix, selected: item.type === type}"
太长- 封装样式对象返回变量的函数,接受
<template>
中的数据传参item
<li v-for="item in dataSource" :key="item.type" :class="liClass(item)" @click="select(item)">{{ item.text }}</li>
- 缩写为
:class="liClass(item)"
- 函数实现
liClass(item: DataSource) {return {[this.classPrefix +'-tabs-item']: this.classPrefix, selected: item.type === this.type};}
- 不可使用箭头函数,避免
this
指向
- 封装样式对象返回变量的函数,接受
- 发布事件
select(item: DataSource) {this.$emit('update:type', item.type);}
- 注意类型声明
DataSource
Statistic.vue
查看显示插值是否正确
|
|
模块化常量数据src/constants/intervalList.ts
- 使数据不可使用栈方法,
Object.freeze()
- 禁止改变原对象,成为真正的常量
intervalList.ts
|
|
recordTypeList.ts
|
|
重构
Statistic.vue
|
|
- 重构
typeList
为recordTypeList
重构Money.vue
使用Tabs.vue
,并删除原 Types.vue
<Tabs :data-source="recordTypeList" :type.sync="record.type"/>
Money.vue
|
|
调整高度样式,更改优先级
- 注意
CSS
权重计算- 子组件比父组件后加载
- 更多使用具体的
class
选择器,精确控制样式 - 特别是使用深度作用选择器时,尽量 不使用标签选择器,里层的标签未知
- 解决样式被覆盖的方法:减少/增加 嵌套的选择器
- 减少嵌套:去除后代/子选择器
> li
,改用class
选择器增加权重
- 减少嵌套:去除后代/子选择器
Tabs.vue
|
|
Statistic.vue
|
|
用 TS 配置组件默认 height
- 使用外部数据传入高度属性值
- 注意高度属性值类型是字符串,不是数字
- 外部数据无需初始化,加上强制类型断言
!: string
- 注意内联样式
style
属性的权重
重构
Tabs.vue
|
|
重构
Statistic.vue
|
|
- 可以直接使用SCSS样式控制,也可以使用 TS 配置组件默认
height
- 但不推荐,会造成逻辑与样式耦合
用列表展示数据
展示数据结构
结构类似于无根节点的树
trees
|
|
展示数据步骤
- 首先使用 钩子获取数据
beforeCreate() {this.$store.commit('fetchRecords');}
- 使用 计算属性操作数据,注意必须有返回值
return ...
get recordList() {return ...}
获取记录列表,注意返回值类型get result() {return ...}
- 使用 计数排序 桶排序
- 使用
hashTable
存数据 - 大致结构 :
list: []
+type
/interval: string
=recordTrees: {title: string, items: RecordItem[]}[]
- 按天分组排序,在计算属性中写排序逻辑
get result() {}
- 使用
隐藏的bug
注意
recordList
的类型,即使已经声明类型,recordList: RecordItem[]
,recordList
类型为any
Vuex
类型上的bug
,在得到this.$store
时,并不能返回正确的类型,导致得到的store
返回的类型永远是any
Vuex
并不能像之前自己使用models
里数据时声明数据类型tagListModel
, 这是ts
与vuex
结合不好的地方,即使用API会丢失类型信息(vue3
的新状态管理pinia
修复了这一缺陷)- 注意
recordList[i].createdAt
属性的类型 - 每次都 强制类型断言
as RootState
get recorList() {return (this.$store.state as RootState).recordList;}
- 此时的返回值
recordList
的类型显示正确为RecordItem[]
- 属性
recordList[i].createdAt
的类型显示为Date | undefined
,但需要的是字符串,字符串才可以分割操作
Date
类型不能被JSON.stringify
序列化
- 当使用
JSON.parse()
无法保证左右两边的类型是相同的state.recordList = JSON.parse(window.localStorage.getItem('recordList') ?? '[]') as RecordItem[];
JSON.parse()
返回的是any
类型recordList
强制定义类型为recordItem[]
,只在运行时才得到JSON
不支持Date
等内置对象,会转成string
但不是所需要的
Date
日期调用了toJSON()
将其转换为了string
字符串(同Date.toISOString()
),因此会被当做字符串处
toISOString()
特指ISO8601
- 类型声明
type RecordItem = {tags: string[]; tips: string; type: string; amount: number; createdAt?: Date;}
中的createdAt?: Date;
改为createdAt?: String;
- 查看所有引用
createdAt
,TS 编译报错 - 将所有改成
*.createdAt = new Date().toISOSting()
标记问题:这是
vue
和TS
配合不好的地方
- 在
src/store/index.ts
中声明的store
类型是RootState
- 之前将
state
的类型进行断言state: {tagList: [], recordList: [], currentTag: undefined} as RootState,
- 之前将
- 查看源码
vuex/types/vue.d.ts
declare module "vue/type/vue" {interface Vue {$store: Store<any>}}
写死类型是<any>
使用计算属性computed
操作展示数据
TS声明空对象类型
- 分别声明空对象的键和值的类型
const hashTable: { [key: string]: RecordItem[] } = {};
使用
hashTable
即键值对的数据结构来存数据
|
|
recordList[i].createdAt!.split('T');
:- 判断拿到数据后,取出的值
createdAt
一定存在,加上强制类型断言createdAt!.*
ESLint
报错就取消全局类型声明custom.d.ts
中type RecordItem
的createdAt?: string;
,改为createdAt: string;
- 判断拿到数据后,取出的值
- 解构
const [date, time] = recordList[i].createdAt!.split('T');
T
的前面是日期,后面是时间ESLint
报错time
未使用就先删除time
- 类似计数排序:初始化
hashTable[date] = hashTable[date] || [];
- 推入数据
hashTable[date].push(recordList[i]);
- 尝试打印出
console.log(hashTable)
或在视图中临时写上数据{{xxx}}
- 查看每项的内容
发现一开始的hashTable
声明类型结构错误
- 避免类型声明结构混乱,定义一个中间类型,先声明哈希表值的类型:
type HashTableValue = { title: string; items: RecordItem[] };
- 初始化声明哈希表改为
const hashTable: { [HashTableKey: string]: HashTableValue } = {};
HashTableKey
命名可为任意字符串,但从所需的结构上看,表示的是日期date
- 按日期分组
- 遍历取到的计算属性
recordList
:for (let i = 0; i < recordList.length; i++) {...}
- 取出日期
const [date,] = recordList[i].createdAt.split('T');
- 将每次取出的日期
date
作为hashTable
的属性名hashTable[date]
- 初始化
hashTable
:hashTable[date] = hashTable[date] || {title: date, items: []};
- 将
hashTable[date]
对应的属性值赋值到hashTable
的[date]
属性上 - 保底值为
{title: date, items: []}
- 将
- 在每项的
items
中推入遍历的数据(recordList[i]
数组)hashTable[date].items.push(recordList[i]);
- 取出日期
- 返回值
hashTable
Statistics.vue
|
|
循环渲染数据
Statistics.vue
|
|
result
返回的是hashTable
,在 v-for 里使用对象,遍历hashTable
的property
<li v-for="(value, name) in result" :key="index">{{ value }}</li>
- 必须写
:key
DOM diff
算法让Vue
正确识别不同的DOM
,正确的复用- 给 Vue 一个提示,以便它能跟踪每个节点的身份,从而复用和重新排序现有元素,需要为每项提供一个唯一
key attribute
- 这里可以使用唯一属性名作为
:key
- 给 Vue 一个提示,以便它能跟踪每个节点的身份,从而复用和重新排序现有元素,需要为每项提供一个唯一
- key值是必须唯一的,如果重复就会报错,
Dupliacated key detected NaN
- 取到的
value
也是一个对象(表示RecordItem
) - 第二层循环
<li v-for="item in group.items" :key="item.id">{{ item.amount }} {{ item.createdAt }}</li>
暂时未实现按类型(支出/收入)和排序的逻辑
完善并添加Statistic.vue
SCSS
- 注意样式加了
scope
属性,只影响当前组件的标签,不会深入组件标签继续影响内部结构 - 使用深度作用选择器
/deep/
(CSS)或::v-deep
(SCSS) - 不用
min-height
来撑开,改用line-height
和padding
来撑开高度
格式化显示
item.tags
custom.d.ts
中改为type RecordItem = { tags: {id: string; name: string}, ...} }[];
显示
xx年xx月xx日
,重构Statistic.vue
|
|
是否超出 存储最大值
localStorage
的最大存储为5MB
左右- 使用
IndexedDB
是否超出最大显示行数
- 引入
global.scss
使用mixin
封装的超出文字省略
|
|
ISO8601
和 dayjs
ISO8601
- 得到
ISO8601
const time = new Date().toISOString()
const myTime = new Date(Date.parse(time))
myTime.getHours()
- Why you shouldn’t use Moment.js…
Vue.js min
才30k
使用
Day.js
- 安装
yarn add dayjs@1.8.20
- 引用包
import dayjs from 'dayjs';
- 查看
API
:const api = dayjs();
正确显示
toISOString()
修正时差,得到本地时间
toISOString()
会得到零时区而非本地(东八区)的时间- How to ISO 8601 format a Date with Timezone Offset in JavaScript?
- 忽略时差
date.getTime() - (date.getTimezoneOffset() * 60000)
- 增加状态属性
localTimeStamp
src/store/index.ts
|
|
src/custom.d.ts
|
|
Statistic.vue
改显示xx年xx月xx日
为显示今天、明天、昨天、上周、上月、以前
|
|
const now = dayjs();
- 判断相符
.isSame(now, 'day')
- 得到昨天:
now.valueOf() - 86400*1000
- 使用
Dayjs
提供的API
:dayjs(someday).isSame(now.subtract(1, 'day'))
- 减一天
.subtract(1, 'day')
- 使用
- 格式化
thatDay.format('YYYY年M月D日');
模块化状态管理
- 分为
tagStore.ts
和recordStore.ts
- 在组件中执行状态变更的方法不变
this.$store.commit('typeXXX', payloadXXX);
- 访问状态需要加一层模块名的属性
this.$store.state.tagStore.currentTag;
- 原来的
this.$store.state.someState
改为this.$store.state.yourModuleName.someState
- 参考 vue状态管理之vuex(十六)
重构
custom.d.ts
分开声明类型
|
|
重构
src/store/index.ts
|
|
tagStore.ts
|
|
recoredStore.ts
|
|
重构Money.vue
、Statistic.vue
和Labels.vue
等相关组件
|
|
重构
Statistic.vue
|
|
重构
Labels.vue
|
|
重构
EditLabel.vue
|
|
重构
Tags.vue
|
|
- 参考文档
- Vue2.5+ Typescript 引入全面指南 - Vuex篇
Muation
函数 不可为 async函数, 也不能 使用箭头函数来定义, 因为代码需要运行在重新绑定执行的上下文
数据排序(计数排序的变形)
首先了解数据
result
的类型
- 目前
result
是hashTable
(对象) - 遍历对象时,遍历的顺序是否固定
- 遍历对象的
key
,遍历的顺序是否固定 hashTable
中的顺序是用户输入得到的,输入顺序不一定符合预期
- 遍历对象的
JS 中对象的
key
是有顺序的
- JS 中对象的字段遍历顺序是没有保证的,例如
Object.keys
函数产生的数组顺序没有保证 - 实际上在
ES2015
之后标准规定了key
的顺序(准确的说是规定了[[OwnPropertyKeys]]()
这个内部方法返回的key
的顺序)
结论:
keys
数组分为三个部分:
- 可以作为数组索引的
key
按照升序排列,例如1、2、3
。 - 是字符串不是 symbol 的 key,按照创建顺序排列。
- symbol 类型的 key 也按照创建顺序排列。
- 参考链接 ECMAScript 9.1.11
|
|
必须转换为一个数组,才能排序
- 按时间排序,近的排在前面
- 显示的顺序为 今天 昨天 (本年内)具体日期 (去年以及之前)具体日期
|
|
clone.ts
|
|
- TS 声明类型:
type HashTableValue = { title: string; items: RecordItem[] };
- 查询到对应的
title
,将recordList
排序,依次推入数组 recordList
是RecordItem
的数组,进行排序.sort((a, b) => {})
const n = recordList.sort((a, b) => {a.createdAt});
a.createdAt
的值为字符串,按ASCII
顺序比较大小,不是预期的顺序,'a' - 'b' // NaN
不能用字符串的减法- 使用
.sort()
必须变为数字类型,用.valueOf()
:dayjs(a.createdAt).valueOf()
const newList = recordList.sort((a: RecordItem, b: RecordItem) => ( dayjs(a.createdAt).valueOf() - dayjs(b.createdAt).valueOf() ));
- 需要逆向从近期到远期的
( dayjs(b.createdAt).valueOf() - dayjs(a.createdAt).valueOf() )
- 注意
.sort()
会 改变原数组自身,保留原数组,使用 深克隆- 导入之前的工具函数
import clone from '@/lib/clone.ts';
的clone()
function clone(data: any) { return JSON.parse(JSON.stringify(data)) as RecordItem; }
- 由于接受的参数类型是
any
,而JSON.parse()
返回值类型也是any
- 使用泛型
function clone<T>(data: T): T {...}
统一入参和返回值的类型- 在尖括号中声明类型
- 之后使用
.sort()
TS 会自动推断类型
- 导入之前的工具函数
重构
Statistic.vue
|
|
- 先将局部数据
recordList
排序,再push
到数据中
数据排序后分组
- 判断数组长度
if(recordList.length === 0) {return []};
,确保存在可操作数据 - 取出新的
newList
的第一个数据const x = [{title: dayjs(recordList[0].createdAt).format('YYYY-MM-DD'), items: [recordList[0]]}];
- 从第二个数据的
.createdAt
和第一个数据的title
和开始循环比较- 新的数据和分组的
title
是否一致- 一致放入当前组的
items
- 不一致,作为新的一组的
title
,放入新分组的items
- 一致放入当前组的
- 新的数据和分组的
|
|
统一显示时间戳比较 - 标记问题ok
const localDay = dayjs(current.createdAt.split('T')[0]);
- 由于有时区的概念
dayjs(current.createdAt)
算上时间部分的日期会延后一天 - 封装倒时差函数
clearJetLag(new Date(), '-')
src/store/modules/recordStore.ts
|
|
src/lib/clearJetLag.ts
|
|
重构命名
get groupedList() { ... return rusult;}
更改
template
循环渲染的:key
- 数据由对象变为数组 方便排序
li v-for="(group, index) in groupedList" :key="index">
重构
Statistic.vue
|
|
完成统计页面
区分 支出和收入
在排序前加
filter
- 查找对应类型相匹配的
.filter(r=> r.type === this.type)
|
|
注意
.filter()
可能会使返回的新数组长度变短为空数组,进行之后的操作的时候访问内部属性为空值undefined
- 判断经过筛选后的数组长度是否为 0 ,返回空数组,结束函数
if (newList.length === 0) {return [] as groupedType[];}
显示总额
- 给
result
添加total
属性- 初始化
{ title: ..., total: 0, items: ...}
- 或者声明类型:
type groupedType = { title: string; total?: number; items: RecordItem[] };
total?: number;
表示赋值时,total
属性可以不存在const result: groupedType[] = {...}
- 初始化
- 需要统计的是每组
group
各项items
的amount
属性 - 使用
.map
遍历每组group
(map
是有返回值的forEach
;forEach
是没有返回值的map
)- 使用
reduce
对每组group
的items
进行归纳统计- 初始值
0
- 形参为 统计结果
sum
项目item
- 将
amount
进行加减,统计到result
的total
属性 - 注意
amount
类型
- 初始值
- 将统计结赋值给
group.total
- 使用
- 将
total
显示到template
:<h3 class="title">{{ showDay(group.title) }} <span> ¥{{ group.total }}</span></h3>
标记问题 记录bug:
amount
显示为字符串拼接- 从输入得到的类型为字符串而非数字
- 在
Numpad.vue
中传入的外部数据@Prop() readonly value!: number;
没有声明类型 - 必须提前声明传入的类型
@Prop(Number) readonly value!: number;
amount
在<Numpad :value.sync="record.amount" @submit="saveRecord"/>
处更新Numpad.vue
中方法confirmNum() {this.$emit(...)}
发布的第二个参数output
类型是any
,必须转为数字类型- 查看
localStorage
中recordList
的数据amount
属性值都是字符串,应为数字
重构
Statistic.vue
|
|
Vue
和 TypeScript
第二个结合不好的地方:this.$emit(event, ...args)
- TS 没有在
this.$emit()
处警告...args
的any
类型
|
|
一些 bug
Money.vue
、Tags.vue
显示传值$event
<Tags @update:selectedTags="pickTags($event)"/>
或不传值<Tags @update:selectedTags="pickTags"/>
,使用方法隐式传值,默认传$event
pickTags(eventValue: Tag[]) {this.record.tags = eventValue;}
<Tags @update:selectedTags="record.tags = $event"/>
内联语法,需要显示传值pickTags(selectedTags: Tag[]) {this.record.tags = selectedTags;}
- 参考
- v-on
$event
- 用在 普通元素上时,只能 监听原生 DOM 事件
- 用在自定义元素组件上时,也可以监听子组件触发的自定义事件
- 监听原生 DOM 事件时,方法以事件为唯一的参数。如果使用内联语句,语句可以访问一个
$event property:v-on:click="handle('ok', $event)"
- 使用事件抛出一个值
- 子组件使用
$emit
的第二个参数payload
来提供这个值<button v-on:click="$emit('enlarge-text', 0.1)">Enlarge text</button>
- 父级组件监听这个事件时,可通过
$event
访问到被抛出的这个值<blog-post v-on:enlarge-text="postFontSize += $event">
- 如果这个事件处理函数是一个方法,这个值将会作为第一个参数传入这个方法
- 子组件使用
- 内联处理器中的方法
- 有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量
$event
把它传入方法
- 有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量
- 事件处理方法 v-on 还可以接收一个需要调用的
Methods
里方法名称- 原生 DOM 事件 的
event
作为参数传给方法- 例如获取输入事件的输入值
@input="oninputValueChanged($event.target.value)"
- 例如点击事件获取点击的目标值
@click="toggle(tag, $event.target.value)"
其中参数tag
由循环渲染列表的数据提供v-for="tag in tagList"
- 例如获取输入事件的输入值
- 原生 DOM 事件 的
- v-on
实现tooltip
加个尖角标
formatter
函数拼接样式- echarts修改tooltip默认样式(使用formatter函数拼接加工)
空白 无记录 显示提示文字 Statistic.vue
|
|
- 同样地
Money.vue
缺 “添加至少一个标签” 逻辑 - 确认保存后 缺 重置备注 和标签 逻辑
<FormItem class="form-item" field-name="备注" placeholder="在这里输入备注" @update:inputValue="onUpdateTips" :value="record.tips"/>
需要绑定:value="record.tips"
才会起效
逻辑耦合
createTag
方法中当store
提交saveTags
成功保存完标签后,window.alert('xxx')
- 收集错误提示,
alert
处理所有报错
createdAt of undefined
的解决办法
- 即使返回空值有时也需声明类型
if (newList.length === 0) {return [] as groupedType[];}
- 核心痛点:每次调用
this.$store.dispatch
/this.$store.commit
/this.$store.state
/this.$store.getters
都会伴随着类型丢失。
- 所有代码:https://github.com/FrankFang/morney-live-list
- 所有 commits:https://github.com/FrankFang/morney-live-list/commits/master
参考文档
- Vuex框架原理与源码分析-明裔
- Vue & TypeScript 初体验
http://www.quasarchs.com/
- Vuex 基本入門 Day 8
- Vuex 模板使用了 vuex-class 简化 vuex
- Vuex业务模块划分项目实例
- 手摸手教你在vue-cli里面使用vuex,以及vuex简介
- Vuex 4.0中使用typescript, Vuex4.0中modules的ts使用,Vue3 + Vuex4.0 + TypeScript 使用详情
- Vuex 进阶使用之modules模块化划分、mapState、mapActions辅助函数的使用
- Vue&TypeScript初體驗使用Vuex(vuexmoduledecorators)
- Vuex进阶篇——Module模块化学习
- 更好的使用module vuex
- Vuex(module)
- Vuex 之 module 使用方法及场景
- Vuex 模块(Module)
- Vuex 模块化使用
- VueJS中学习使用Vuex详解
- 大型项目使用Vuex modules后,模块之间怎么访问action
- uni-app 使用vuex(vuex module)