一、手写store
发现bug-----把获取到的标签列表变成全局吧
(一)需求
- 发现bug:在记账页面新增标签,但是新增的标签不会自动同步到标签页面,必须要刷新一下才行
- 原因:记账页面和标签页面都是各自获取标签列表的
tagListModel.fetch(); - 那在最高层获取标签列表,记账页面和标签页面用获取到的东西就行了
(二)步骤
- 在main.ts里获取标签列表。在全局获取标签列表,标签列表直接成为了windows的tagList属性。其他组件直接用
window.tagList = tagListModel.fetch() - 注意:要在custom.ts里面声明window有一个属性tagList
- 修改用到了tagList的组件。
把所有数据都全局吧
(一)步骤
- 在main.ts里把各个属性挂在window上
- 记得在custom.ts里面声明window有这些属性
- 修改用到了这些属性的组件
(二)缺点
- 全局变量太多
- 严重依赖window
(三)具体代码
main.ts
import Vue from 'vue';
import App from './App.vue';
import './registerServiceWorker';
import router from './router'; //导入一个文件夹,如果这个文件夹有index文件,那就是导入的是index文件里的东西
import store from './store';
import Nav from '@/components/Nav.vue';
import Layout from '@/components/Layout.vue';
import Icon from '@/components/Icon.vue';
import {tagListModel} from '@/models/tagListModel';
import {recordListModel} from '@/models/recordListModel';
Vue.config.productionTip = false;
Vue.component('Nav', Nav); //把Nav组件变成全局组件
Vue.component('Layout', Layout);
Vue.component('Icon', Icon);
// tag store
//在全局获取标签列表,标签列表直接成为了windows的tagList属性。其他组件直接用
window.tagList = tagListModel.fetch();
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);
};
window.findTag=(id: string)=>{
return window.tagList.filter(t => t.id === id)[0]
};
//record store
window.recordList = recordListModel.fetch();
window.createRecord=(record: RecordItem)=>{
recordListModel.create(record)
}
;
new Vue({
router, //把router传给Vue
store,
render: h => h(App)
}).$mount('#app');
custom.d.ts
type RecordItem = {
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'; // 联合类型
save: () => void;
update: (id: string, name: string) => 'success' | 'not found' | 'duplicated';
remove: (id: string) => boolean;
}
interface Window {
tagList: Tag[]; //声明window有个属性tagList
createTag: (name: string) => void;
removeTag: (id: string) => boolean;
updateTag: (id: string, name: string) => 'success' | 'not found' | 'duplicated';
findTag: (id: string) => Tag;
recordList: RecordItem[];
createRecord: (record: RecordItem) => void;
}
优化
(一)只有一个全局变量window.store,其他的全局变量放到window.store里面
main.ts
window.store = {
// tag store
//在全局获取标签列表,标签列表直接成为了windows的tagList属性。其他组件直接用
tagList: tagListModel.fetch(),
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);
},
findTag(id: string) {
return this.tagList.filter(t => t.id === id)[0];
},
//record store
recordList: recordListModel.fetch(),
createRecord: (record: RecordItem) => {
recordListModel.create(record);
}
}
custom.d.ts
interface Window {
store: {
tagList: Tag[]; //声明window有个属性tagList
createTag: (name: string) => void;
removeTag: (id: string) => boolean;
updateTag: (id: string, name: string) => 'success' | 'not found' | 'duplicated';
findTag: (id: string) => Tag;
recordList: RecordItem[];
createRecord: (record: RecordItem) => void;
}
}
(二)不用window,而是用store
- 新建store/index2.ts,把window.store写在这个文件夹里成为一个store对象(里面是所有关于数据的操作),导出这个store对象,谁要用数据就引入这个store对象就行了
- 注意:Money组件要用store,Label组件也要用store,那 他们导入的store是一个吗?不是一个的话,不就又回到起点了吗?答案:是的!你可以在同一个组件里导入两次store,console.log(store===store2),得到true
- 注意:那store/index2.ts里的代码会执行几次?在index2.ts里console.log('代码执行了一次'),发现虽然那么多组件都导入了store/index2.ts,但是代码只执行了一次
import {tagListModel} from '@/models/tagListModel';
import {recordListModel} from '@/models/recordListModel';
const store = {
// tag store
//在全局获取标签列表,标签列表直接成为了windows的tagList属性。其他组件直接用
tagList: tagListModel.fetch(),
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);
},
findTag(id: string) {
return this.tagList.filter(t => t.id === id)[0];
},
//record store
recordList: recordListModel.fetch(),
createRecord: (record: RecordItem) => {
recordListModel.create(record);
}
};
export default store
import recordStore from '@/store/recordStore';
import tagStore from '@/store/tagStore';
const store = {
...recordStore,
...tagStore
};
export default store
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
//在全局获取标签列表,标签列表直接成为了windows的tagList属性。其他组件直接用
tagList: tagListModel.fetch(),
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);
},
findTag(id: string) {
return this.tagList.filter(t => t.id === id)[0];
},
}
重构:将Model和Store合体
需求
- 刚开始用Model是因为还没有接触到store。
- 其实,store就是Model(MVC中的M)。所以把他俩合体吧。
- 重构结束就把Model删掉吧
全局状态管理(也叫全局数据管理)的好处是什么?
- 解耦:将所有数据相关的逻辑放入 store(也就是 MVC 中的 Model,换了个名字而已)
- 数据读写更方便:任何组件不管在哪里,都可以直接读写数据
- 控制力更强:组件对数据的读写只能使用 store 提供的 API 进行(当然也不排除有猪队友直接对 tagList 和 recordList 进行 push 等操作,这是没有办法禁止的)
二、使用Vuex:src/store/index.ts
import Vue from 'vue';
import Vuex from 'vuex';
import clone from '@/lib/clone';
import createId from '@/lib/createId';
import router from '@/router';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
recordList: [],
tagList: [],
currentTag: undefined
} as RootState,
mutations: {
setCurrentTag(state, id: string) {
state.currentTag = state.tagList.filter(t => t.id === id)[0];
},
updateTag(state, payload: { id: string; name: string }) {
const {id, name} = payload;
const idList = state.tagList.map(item => item.id);
if (idList.indexOf(id) >= 0) {
const names = state.tagList.map(item => item.name);
if (names.indexOf(name) >= 0) {
window.alert('标签名重复了');
} else {
const tag = state.tagList.filter(item => item.id === id)[0];
tag.name = name;
store.commit('saveTags');
}
}
},
removeTag(state, id: string) {
let index = -1;
for (let i = 0; i < state.tagList.length; i++) {
if (state.tagList[i].id === id) {
index = i;
break;
}
}
if (index >= 0) {
state.tagList.splice(index, 1);
store.commit('saveTags');
router.back();
} else {
window.alert('删除失败');
}
},
fetchRecords(state) {
state.recordList = JSON.parse(window.localStorage.getItem('recordList') || '[]') as RecordItem[];
},
createRecord(state, record) {
const record2: RecordItem = clone(record);
record2.createdAt = new Date();
state.recordList.push(record2);
store.commit('saveRecords');
},
saveRecords(state) {
window.localStorage.setItem('recordList',
JSON.stringify(state.recordList));
},
fetchTags(state) {
state.tagList = JSON.parse(window.localStorage.getItem('tagList') || '[]');
},
createTag(state, name: string) {
const names = state.tagList.map(item => item.name);
if (names.indexOf(name) >= 0) {
window.alert('标签名重复了');
}
const id = createId().toString();
state.tagList.push({id, name: name});
store.commit('saveTags');
window.alert('添加成功');
},
saveTags(state) {
window.localStorage.setItem('tagList', JSON.stringify(state.tagList));
},
}
});
export default store;