project from jirengu.com
- 写ID生成器(在lib目录下新建
createId.ts):
//createdId.ts
let id: number = parseInt(window.localStorage.getItem('_idMax') || '0');
function createId() {
id++;
return id;
}
export default createId;
//tagListModel.ts
create(name: string) {
const names = this.data.map(item => item.name);
if (names.indexOf(name) >= 0) {
return 'duplicated';
}
const id = createId().toString(); //新增
this.data.push({id, name: name}); //新增
this.save();
return 'success';
},
目前项目中实现标签动态的删减,与记账页面的标签显示不同步,因为两个组件都分别做了一次
fetch、window.localStorage.getItem,解决此问题,需要先把两个存数据的Model统一一下。
const recordList = recordListModel.fetch();
const tagList = tagListModel.fetch();
- 重新封装
recordListModel.ts,重新封装create方法,同时封装clone方法(在lib目录下新建clone.ts):
//src/lib/clone.ts
function clone(data:any){
return JSON.parse(JSON.stringify(data));
}
export default clone;
//recordListModel.ts
import clone from '@/lib/clone';
const localStorageKeyName = 'recordList';
const recordListModel = {
data: [] as RecordItem[],
create(record:RecordItem) {
const record2: RecordItem = clone(record);
record2.createdAt = new Date();
this.data.push(record2);
},
fetch() {
this.data = JSON.parse(window.localStorage.getItem(localStorageKeyName) || '[]');
return this.data;
},
save() {
window.localStorage.setItem(localStorageKeyName, JSON.stringify(this.data));
}
};
export default recordListModel;
- 使用封装好的API,对数据的任何操作都可以用通过
recordListModel来搞定:
const recordList = recordListModel.fetch();
saveRecord() {
recordListModel.create(this.record)
}
onRecordListChange() {
recordListModel.save();
}
全局数据管理
解决标签显示不同步问题,需要先把两个存数据的Model统一一下,找到
Money.vue和Labels.vue的共同上一级组件:App.vue,或者说提到main.ts用于渲染app的这层,在此处获取到数据,在此之前需要去custom.d.ts中提前声明:
//custom.d.ts
type RecordItem = { //TS类型声明只写类型,JS类型声明写具体内容
tags: string[]
notes: string
type: string
amount: number
createdAt?: Date
}
type Tag = {
id: string;
name: string;
}
type TagListModel = {
data: Tag[]
fetch: () => Tag[]
create: (name: string) => 'success' | 'duplicated'
update: (id: string, name: string) => 'success' | 'not found' | 'duplicated'
remove: (id: string) => boolean;
save: () => void
}
interface Window { //声明window属性
tagList: Tag[];
}
- 声明全局变量,用window来容纳数据:
//main.ts
import tagListModel from '@/models/tagListModel';
window.tagList = tagListModel.fetch();
- 分别直接从window上拿到数据:
//Money.vue
export default class Money extends Vue {
tags = window.tagList;
//Labels.vue
export default class Labels extends Vue {
tags = window.tagList;
- 把
create也封装一下,在此之前需要去custom.d.ts中提前声明:
interface Window { //声明window属性
tagList: Tag[];
createTag:(name:string)=>void
}
- 声明全局变量,用window来容纳数据:
//main.ts
window.createTag = (name:string)=>{
const message = tagListModel.create(name);
if (message === 'duplicated') {
window.alert('标签名重复了');
} else if (message === 'success'){
window.alert('标签添加成功');
}
}
- 使用这个API:
//Labels.vue
createTag() {
const name = window.prompt('请输入标签名');
if (name) {
window.createTag(name);
}
}
- 继续封装
remove、update:
//custom.d.ts
interface Window { //声明window属性
tagList: Tag[];
createTag:(name:string)=>void
removeTag:(id:string)=>boolean
updateTag: (id: string, name: string) => 'success' | 'not found' | 'duplicated'
}
//main.ts
window.removeTag = (id: string) => {
return tagListModel.remove(id);
};
window.updateTag = (id:string, name:string)=>{
return tagListModel.update(id, name);
}
封装record
//custom.d.ts
interface Window {
recordList:RecordItem[];
createRecord:(record:RecordItem)=>void
}
//main.ts
// record store
window.recordList = recordListModel.fetch();
window.createRecord = (record: RecordItem) => recordListModel.create(record);
// tag store
window.tagList = tagListModel.fetch();
window.findTag = (id: string) => {
return window.tagList.filter(t => t.id === id)[0];
};
window.createTag = (name: string) => {
const message = tagListModel.create(name);
if (message === 'duplicated') {
window.alert('标签名重复了');
} else if (message === 'success') {
window.alert('标签添加成功');
}
};
window.removeTag = (id: string) => {
return tagListModel.remove(id);
};
window.updateTag = (id: string, name: string) => {
return tagListModel.update(id, name);
};
//Money.vue
saveRecord() {
window.createRecord(this.record)
}
-
目前存在的问题一是用了过多的全局变量,二是严重依赖window
-
修改注释的样式:
- 先把过多的全局变量挂到一个对象上,来解决用了过多的全局变量,先声明类型:
//custom.d.ts
interface Window { //声明window属性
store:{
tagList: Tag[];
createTag: (name: string) => void
findTag:(id:string)=> Tag | undefined;
removeTag: (id: string) => boolean
updateTag: (id: string, name: string) => 'success' | 'not found' | 'duplicated'
//也可以写为 updateTag: TagListModel['update']
recordList:RecordItem[];
createRecord:(record:RecordItem)=>void
}
}
- 在
main.ts中把全局变量都挂在store上:
window.store = {
// record store
recordList: recordListModel.fetch(),
createRecord: (record: RecordItem) => recordListModel.create(record),
// tag store
tagList: tagListModel.fetch(),
findTag(id: string) {
return this.tagList.filter(t => t.id === id)[0];
},
createTag: (name: string) => {
const message = tagListModel.create(name);
if (message === 'duplicated') {
window.alert('标签名重复了');
} else if (message === 'success') {
window.alert('标签添加成功');
}
},
removeTag: (id: string) => {
return tagListModel.remove(id);
},
updateTag: (id: string, name: string) => {
return tagListModel.update(id, name);
}
};
- 再解决严重依赖window:
- 新建一个
index2.ts在store文件夹下,把刚才的main.ts中的store内容都粘贴到index2.ts里,然后把window删掉:
//index2.ts
import recordListModel from '@/models/recordListModel';
import tagListModel from '@/models/tagListModel';
const store = {
// record store
recordList: recordListModel.fetch(),
createRecord: (record: RecordItem) => recordListModel.create(record),
// tag store
tagList: tagListModel.fetch(),
findTag(id: string) {
return this.tagList.filter(t => t.id === id)[0];
},
createTag: (name: string) => {
const message = tagListModel.create(name);
if (message === 'duplicated') {
window.alert('标签名重复了');
} else if (message === 'success') {
window.alert('标签添加成功');
}
},
removeTag: (id: string) => {
return tagListModel.remove(id);
},
updateTag: (id: string, name: string) => {
return tagListModel.update(id, name);
}
};
export default store;
- 此时,任何想用数据的就可以直接用,比如在
Money.vue或其他组件中使用数据:
//Money.vue
import store from '@/store/index2';
export default class Money extends Vue {
tags = store.tagList;
recordList = store.recordList;
saveRecord() {
store.createRecord(this.record)
}
//Labels.vue
tags = store.tagList;
createTag() {
const name = window.prompt('请输入标签名');
if (name) {
store.createTag(name);
}
}
//EditLabels.vue
tag = store.findTag(this.$route.params.id);
created() {
this.tag = store.findTag(this.$route.params.id);
if (!this.tag) {
this.$router.replace('404');
}
}
update(name: string) {
if (this.tag) {
store.updateTag(this.tag.id, name);
}
}
remove() {
if (this.tag) {
if (store.removeTag(this.tag.id)) {
this.$router.back();
} else {
window.alert('删除失败');
}
}
}
手写store来实现数据集中管理
- 继续优化
store,index2实际上分两个部分,一部分是recordList,另一部分是tagList,可以分为两个文件recordStore.ts和tagStore.ts:
//recordStore.ts
import recordListModel from '@/models/recordListModel';
export default {
// record store
recordList: recordListModel.fetch(),
createRecord: (record: RecordItem) => recordListModel.create(record),
}
//tagStore.ts
import tagListModel from '@/models/tagListModel';
export default {
// tag store
tagList: tagListModel.fetch(),
findTag(id: string) {
return this.tagList.filter(t => t.id === id)[0];
},
createTag: (name: string) => {
const message = tagListModel.create(name);
if (message === 'duplicated') {
window.alert('标签名重复了');
} else if (message === 'success') {
window.alert('标签添加成功');
}
},
removeTag: (id: string) => {
return tagListModel.remove(id);
},
updateTag: (id: string, name: string) => {
return tagListModel.update(id, name);
}
};
- 如何在
index2.ts中引入上面的两个对象:
import recordStore from '@/store/recordStore';
import tagStore from '@/store/tagStore';
const store = {
...recordStore, //浅拷贝(只复制地址)、模块化
...tagStore, //浅拷贝(只复制地址)、模块化
};
export default store;
- 将 model 融合进 store,把共性的功能合并,直接把model中的代码拷贝到store中,合并后,就可以删掉原model文件.
- 先合并record:
//recordStore.ts
import clone from '@/lib/clone';
const localStorageKeyName = 'recordList';
let data: RecordItem[] | undefined = undefined;
function fetch() {
data = JSON.parse(window.localStorage.getItem(localStorageKeyName) || '[]') as RecordItem[];
return data;
}
function save() {
window.localStorage.setItem(localStorageKeyName, JSON.stringify(data));
}
export default {
fetch: fetch,
save: save,
recordList: fetch(),
createRecord: (record: RecordItem) => {
const record2: RecordItem = clone(record);
record2.createdAt = new Date();
data && data.push(record2);
save();
},
};
- 进一步封装上面的代码:
//recordStore.ts
import clone from '@/lib/clone';
const localStorageKeyName = 'recordList';
let data: RecordItem[] | undefined = undefined;
const recordStore = {
fetch() {
data = JSON.parse(window.localStorage.getItem(localStorageKeyName) || '[]') as RecordItem[];
return data;
},
save() {
window.localStorage.setItem(localStorageKeyName, JSON.stringify(data));
},
recordList: data,
createRecord: (record: RecordItem) => {
const record2: RecordItem = clone(record);
record2.createdAt = new Date();
data && data.push(record2);
recordStore.save();
},
};
recordStore.fetch();
export default recordStore;
-
搜索引用此
recordStore.ts的index2.ts引用了recordStore的fetch、save属性,可能与另外的数据可能也使用fetch、save相冲突,所以需要重命名为fetchRecord、saveRecord -
再合并tag:
//tagStore.ts
import createId from '@/lib/createId';
const localStorageKeyName = 'tagList';
const tagStore = {
tagList: [] as Tag[],
fetchTags() {
this.tagList = JSON.parse(window.localStorage.getItem(localStorageKeyName) || '[]');
return this.tagList;
},
findTag(id: string) {
return this.tagList.filter(t => t.id === id)[0];
},
createTag(name: string) {
const names = this.tagList.map(item => item.name);
if (names.indexOf(name) >= 0) {
window.alert('标签名重复了');
return 'duplicated';
}
const id = createId().toString();
this.tagList.push({id, name: name});
this.saveTags();
window.alert('添加成功');
return 'success';
},
removeTag(id: string) {
let index = -1;
for (let i = 0; i < this.tagList.length; i++) {
if (this.tagList[i].id === id) {
index = i;
break;
}
}
this.tagList.splice(index, 1);
this.saveTags();
return true;
},
updateTag(id: string, name: string) {
const idList = this.tagList.map(item => item.id);
if (idList.indexOf(id) >= 0) {
const names = this.tagList.map(item => item.name);
if (names.indexOf(name) >= 0) {
return 'duplicated';
} else {
const tag = this.tagList.filter(item => item.id === id)[0];
tag.name = name;
this.saveTags();
return 'success';
}
} else {
return 'not found';
}
},
saveTags() {
window.localStorage.setItem(localStorageKeyName, JSON.stringify(this.tagList));
}
};
tagStore.fetchTags();
export default tagStore;
-
解决创建标签的bug:
-
优化前代码:
//Tags.vue
@Prop() readonly dataSource: string[] | undefined;
create() {
const name = window.prompt('请输入标签名');
if (name === '') {
window.alert('标签名不能为空');
} else if (this.dataSource) {
this.$emit('update:dataSource',
[...this.dataSource, name]);
}
- 优化后代码:
//Tags.vue
<template>
<div class="tags">
<div class="new">
<button @click="create">添加标签</button>
</div>
<ul class="current">
<li v-for="tag in tagList" :key="tag.id"
:class="{selected:selectedTags.indexOf(tag)>=0}"
@click="toggle(tag)">{{tag.name}}
</li>
</ul>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import {Component, Prop} from 'vue-property-decorator';
import store from '@/store/index2';
@Component
export default class Tags extends Vue {
tagList = store.fetchTags();
selectedTags: string[] = [];
toggle(tag: string) {
const index = this.selectedTags.indexOf(tag);
if (index >= 0) {
this.selectedTags.splice(index, 1);
} else {
this.selectedTags.push(tag);
}
this.$emit('update:value', this.selectedTags);
}
create() {
const name = window.prompt('请输入标签名');
if (!name) {
return window.alert('标签名不能为空');
}
store.createTag(name);
}
}
</script>