【项目】记账App(六)----全局数据管理

281 阅读5分钟

一、手写store

发现bug-----把获取到的标签列表变成全局吧

(一)需求

  • 发现bug:在记账页面新增标签,但是新增的标签不会自动同步到标签页面,必须要刷新一下才行
  • 原因:记账页面和标签页面都是各自获取标签列表的tagListModel.fetch();
  • 那在最高层获取标签列表,记账页面和标签页面用获取到的东西就行了

(二)步骤

  1. 在main.ts里获取标签列表。在全局获取标签列表,标签列表直接成为了windows的tagList属性。其他组件直接用 window.tagList = tagListModel.fetch()
  2. 注意:要在custom.ts里面声明window有一个属性tagList
  3. 修改用到了tagList的组件。

把所有数据都全局吧

(一)步骤

  1. 在main.ts里把各个属性挂在window上
  2. 记得在custom.ts里面声明window有这些属性
  3. 修改用到了这些属性的组件

(二)缺点

  1. 全局变量太多
  2. 严重依赖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

  1. 新建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

2. 记得修改相关组件 3. 在优化:把store/index2.ts再分成两个文件recordStore.ts和tagStore.ts然后在引入 index2.ts

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删掉吧

全局状态管理(也叫全局数据管理)的好处是什么?

  1. 解耦:将所有数据相关的逻辑放入 store(也就是 MVC 中的 Model,换了个名字而已)
  2. 数据读写更方便:任何组件不管在哪里,都可以直接读写数据
  3. 控制力更强:组件对数据的读写只能使用 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;