window全局变量

在main.ts中声明全局变量

  1. window.tagList = tagListModel.fetch()

在custom.d.ts中用interface给tagList声明类型

  1. type tag = {
  2. id: string
  3. name: string
  4. }
  5. interface Window {
  6. tagList: tag[];
  7. }

封装tagList的增删改查

main.ts

  1. window.tagList = tagListModel.fetch()
  2. window.createTag = (name: string) => {
  3. const message = tagListModel.create(name);
  4. if (message === 'duplicated') {
  5. window.alert('标签名已存在');
  6. } else if (message === 'success') {
  7. window.alert('创建成功');
  8. }
  9. }
  10. window.updateTag = (id:string, name: string) => {
  11. return tagListModel.update(id, name)
  12. }
  13. window.removeTag = (id:string) => {
  14. return tagListModel.remove(id)
  15. }
  16. window.findTag = (id:string) => {
  17. return window.tagList.find(t => t.id === id)
  18. }

custom.d.ts

  1. interface Window {
  2. tagList: Tag[];
  3. createTag: (name: string) => void;
  4. updateTag: TagListModel['update'];
  5. removeTag: TagListModel['remove'];
  6. findTag: (id: string) => Tag | undefined
  7. }

封装recordList

  1. window.recordList = recordListModel.fetch()
  2. window.createRecord = (record: RecordItem) => {
  3. recordListModel.create(record)
  4. }

摆脱对window的依赖

上面封装全局变量的问题:

  1. 全局变量太多
  2. 严重依赖window

解决全局变量太多的问题:
将上面封装的都放到window.store对象里

  1. interface Window {
  2. store: {
  3. tagList: Tag[];
  4. createTag: (name: string) => void;
  5. updateTag: TagListModel['update'];
  6. removeTag: TagListModel['remove'];
  7. findTag: (id: string) => Tag | undefined;
  8. recordList: RecordItem[];
  9. createRecord: (record: RecordItem) => void;
  10. }
  11. }
  1. window.store = {
  2. recordList: recordListModel.fetch(),
  3. createRecord: (record: RecordItem) => {
  4. recordListModel.create(record)
  5. },
  6. tagList: tagListModel.fetch(),
  7. createTag: (name: string) => {
  8. const message = tagListModel.create(name);
  9. if (message === 'duplicated') {
  10. window.alert('标签名已存在');
  11. } else if (message === 'success') {
  12. window.alert('创建成功');
  13. }
  14. },
  15. updateTag: (id:string, name: string) => {
  16. return tagListModel.update(id, name)
  17. },
  18. removeTag: (id:string) => {
  19. return tagListModel.remove(id)
  20. },
  21. findTag(id:string) {
  22. return this.tagList.find(t => t.id === id)
  23. }
  24. }

解决严重依赖window的问题:
在store文件夹新建index.ts文件, 声明独立的store对象

  1. import recordListModel from '@/models/recordListModel';
  2. import tagListModel from '@/models/tagListModel';
  3. const store = {
  4. recordList: recordListModel.fetch(),
  5. createRecord: (record: RecordItem) => {
  6. recordListModel.create(record)
  7. },
  8. tagList: tagListModel.fetch(),
  9. createTag: (name: string) => {
  10. const message = tagListModel.create(name);
  11. if (message === 'duplicated') {
  12. window.alert('标签名已存在');
  13. } else if (message === 'success') {
  14. window.alert('创建成功');
  15. }
  16. },
  17. updateTag: (id:string, name: string) => {
  18. return tagListModel.update(id, name)
  19. },
  20. removeTag: (id:string) => {
  21. return tagListModel.remove(id)
  22. },
  23. findTag(id:string) {
  24. return this.tagList.find(t => t.id === id)
  25. }
  26. }
  27. export default store

将recordList和tagList分离

新建recordStore.ts

  1. import recordListModel from '@/models/recordListModel';
  2. export default {
  3. recordList: recordListModel.fetch(),
  4. createRecord: (record: RecordItem) => {
  5. recordListModel.create(record)
  6. },
  7. }

新建tagStore.ts

  1. import tagListModel from '@/models/tagListModel';
  2. export default {
  3. tagList: tagListModel.fetch(),
  4. createTag: (name: string) => {
  5. const message = tagListModel.create(name);
  6. if (message === 'duplicated') {
  7. window.alert('标签名已存在');
  8. } else if (message === 'success') {
  9. window.alert('创建成功');
  10. }
  11. },
  12. updateTag: (id:string, name: string) => {
  13. return tagListModel.update(id, name)
  14. },
  15. removeTag: (id:string) => {
  16. return tagListModel.remove(id)
  17. },
  18. findTag(id:string) {
  19. return this.tagList.find(t => t.id === id)
  20. }
  21. }

修改store/index.ts为

  1. import recordStore from '@/store/recordStore';
  2. import tagStore from '@/store/tagStore';
  3. const store = {
  4. ...recordStore,
  5. ...tagStore
  6. }
  7. export default store

Q&A

问:如果多次引入store, 会有几个store?
答:只会有一个
通过console.log来验证

  1. import store from '@/store/index2'
  2. import store2 from '@/store/index2'
  3. console.log(store === store2) // 得到true

问:如果多次引入,store会执行几次?
答: 只会执行1次
在store中加一个console.log
image.png

将models融合进store

model和store有功能重合, 将它们整合成一个
recordStore

  1. import clone from '@/lib/clone'
  2. const localStorageItemName = 'recordList';
  3. const recordStore = {
  4. recordList: [] as RecordItem[],
  5. fetchRecords(){
  6. this.recordList = JSON.parse(window.localStorage.getItem(localStorageItemName) || '[]') as RecordItem[];
  7. return this.recordList
  8. },
  9. createRecord (record: RecordItem){
  10. const record2: RecordItem = clone(record);
  11. record2.createAt = new Date();
  12. this.recordList.push(record2);
  13. this.saveRecords()
  14. },
  15. saveRecords() {
  16. window.localStorage.setItem(localStorageItemName, JSON.stringify(this.recordList));
  17. }
  18. }
  19. recordStore.fetchRecords()
  20. export default recordStore

tagStore

  1. import createId from '@/lib/idCreator';
  2. const localStorageItemName = 'tagList';
  3. const tagStore = {
  4. tagList: [] as Tag[],
  5. fetchTags() {
  6. this.tagList = JSON.parse(window.localStorage.getItem(localStorageItemName) || '[]');
  7. return this.tagList;
  8. },
  9. createTag(name: string){
  10. const names = this.tagList.map(item => item.name);
  11. if (names.indexOf(name) >= 0) {
  12. window.alert('标签名已存在')
  13. return 'duplicated';
  14. }
  15. const id = createId().toString()
  16. this.tagList.push({id, name: name});
  17. this.save();
  18. window.alert('创建成功');
  19. return 'success';
  20. },
  21. updateTag(id:string, name: string){
  22. const ids = this.tagList.map(item => item.id);
  23. if (ids.indexOf(id) >= 0) {
  24. const tag = this.tagList.filter(item => item.id === id)[0];
  25. if (tag.name === name) {
  26. return 'duplicated';
  27. } else {
  28. tag.name = name;
  29. this.save();
  30. return 'success';
  31. }
  32. } else {
  33. return 'not found';
  34. }
  35. },
  36. removeTag (id:string) {
  37. let index = -1
  38. for (let i = 0; i < this.tagList.length; i++) {
  39. if (this.tagList[i].id === id){
  40. index = i
  41. break
  42. }
  43. }
  44. this.tagList.splice(index, 1)
  45. this.save()
  46. return true
  47. },
  48. findTag(id:string) {
  49. return this.tagList.find(t => t.id === id)
  50. },
  51. save() {
  52. window.localStorage.setItem(localStorageItemName, JSON.stringify(this.tagList));
  53. }
  54. }
  55. tagStore.fetchTags()
  56. export default tagStore

可选链操作符(?.)和空值合并操作符(??)

当不确定对象属性是否存在时,用可选链操作符,不存在则返回undefined
空值合并操作符,当左侧为null或undefined时,返回右侧值,否则返回左侧值(包括0和’’)
(详情见MDN)

  1. let customer = {
  2. name: "Carl",
  3. details: { age: 82 }
  4. };
  5. let customerCity = customer?.city ?? "暗之城";
  6. console.log(customerCity); // “暗之城”

store的bug之值与地址

在Money组件中, recordList是直接赋值的store.recordList, 当store.recordList变化时, recordList也跟着变化,这是因为传的是对象的地址

  1. export default class Money extends Vue {
  2. recordList = store.recordList;
  3. // ...
  4. }

如果store中有一个基本类型的变量count

  1. const store = {
  2. count: 0,
  3. addCount(){
  4. this.count += 1
  5. },
  6. ...recordStore,
  7. ...tagStore
  8. }

如果将count赋值给Money组件的data, 当store中的count变化时, Money组件中的data并不会变化,因为传的是
解决办法是使用computed, 同时要把store传给组件的data, 告诉vue要监听store的变化

  1. @Component({
  2. components: {Tags, FormItem, Types, NumberPad},
  3. computed:{
  4. count(){
  5. return store.count
  6. }
  7. }
  8. })
  9. export default class Money extends Vue {
  10. store = store
  11. addCount(){
  12. store.addCount()
  13. }
  14. // ...
  15. }

如果其他组件也要用store, 可以把store传给app.vue的data

  1. <script lang="js">
  2. import store from '@/store/index2'
  3. export default {
  4. data(){
  5. return {
  6. store: store
  7. }
  8. }
  9. }
  10. </script>

不管store中是什么类型的变量,在组件中都应该使用computed

把store2变成this.store2

每个组件使用store都需要import store,
我们可以在main.js中将store注册为全局的

  1. import store2 from '@/store/index2'
  2. Vue.prototype.$store2 = store2

这样组件中就可以直接使用this.$store2而不需要引入store了
但是TypeScript不认识$store2, 需要声明类型
在custom.d.ts中

  1. import Vue from 'vue'
  2. declare module 'vue/types/vue' {
  3. interface Vue {
  4. $store2: any
  5. }
  6. }

总结

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

  1. 解耦:将所有数据相关的逻辑放入 store(也就是 MVC 中的 Model,换了个名字而已)
  2. 数据读写更方便:任何组件不管在哪里,都可以直接读写数据
  3. 控制力更强:组件对数据的读写只能使用 store 提供的 API 进行(当然也不排除有猪队友直接对 tagList 和 recordList 进行 push 等操作,这是没有办法禁止的)