window全局变量
在main.ts中声明全局变量
window.tagList = tagListModel.fetch()
在custom.d.ts中用interface给tagList声明类型
type tag = {id: stringname: string}interface Window {tagList: tag[];}
封装tagList的增删改查
main.ts
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.updateTag = (id:string, name: string) => {return tagListModel.update(id, name)}window.removeTag = (id:string) => {return tagListModel.remove(id)}window.findTag = (id:string) => {return window.tagList.find(t => t.id === id)}
custom.d.ts
interface Window {tagList: Tag[];createTag: (name: string) => void;updateTag: TagListModel['update'];removeTag: TagListModel['remove'];findTag: (id: string) => Tag | undefined}
封装recordList
window.recordList = recordListModel.fetch()window.createRecord = (record: RecordItem) => {recordListModel.create(record)}
摆脱对window的依赖
上面封装全局变量的问题:
- 全局变量太多
 - 严重依赖window
 
解决全局变量太多的问题:
将上面封装的都放到window.store对象里
interface Window {store: {tagList: Tag[];createTag: (name: string) => void;updateTag: TagListModel['update'];removeTag: TagListModel['remove'];findTag: (id: string) => Tag | undefined;recordList: RecordItem[];createRecord: (record: RecordItem) => void;}}
window.store = {recordList: recordListModel.fetch(),createRecord: (record: RecordItem) => {recordListModel.create(record)},tagList: tagListModel.fetch(),createTag: (name: string) => {const message = tagListModel.create(name);if (message === 'duplicated') {window.alert('标签名已存在');} else if (message === 'success') {window.alert('创建成功');}},updateTag: (id:string, name: string) => {return tagListModel.update(id, name)},removeTag: (id:string) => {return tagListModel.remove(id)},findTag(id:string) {return this.tagList.find(t => t.id === id)}}
解决严重依赖window的问题:
在store文件夹新建index.ts文件, 声明独立的store对象
import recordListModel from '@/models/recordListModel';import tagListModel from '@/models/tagListModel';const store = {recordList: recordListModel.fetch(),createRecord: (record: RecordItem) => {recordListModel.create(record)},tagList: tagListModel.fetch(),createTag: (name: string) => {const message = tagListModel.create(name);if (message === 'duplicated') {window.alert('标签名已存在');} else if (message === 'success') {window.alert('创建成功');}},updateTag: (id:string, name: string) => {return tagListModel.update(id, name)},removeTag: (id:string) => {return tagListModel.remove(id)},findTag(id:string) {return this.tagList.find(t => t.id === id)}}export default store
将recordList和tagList分离
新建recordStore.ts
import recordListModel from '@/models/recordListModel';export default {recordList: recordListModel.fetch(),createRecord: (record: RecordItem) => {recordListModel.create(record)},}
新建tagStore.ts
import tagListModel from '@/models/tagListModel';export default {tagList: tagListModel.fetch(),createTag: (name: string) => {const message = tagListModel.create(name);if (message === 'duplicated') {window.alert('标签名已存在');} else if (message === 'success') {window.alert('创建成功');}},updateTag: (id:string, name: string) => {return tagListModel.update(id, name)},removeTag: (id:string) => {return tagListModel.remove(id)},findTag(id:string) {return this.tagList.find(t => t.id === id)}}
修改store/index.ts为
import recordStore from '@/store/recordStore';import tagStore from '@/store/tagStore';const store = {...recordStore,...tagStore}export default store
Q&A
问:如果多次引入store, 会有几个store?  
答:只会有一个
通过console.log来验证
import store from '@/store/index2'import store2 from '@/store/index2'console.log(store === store2) // 得到true
问:如果多次引入,store会执行几次?  
答: 只会执行1次
在store中加一个console.log
将models融合进store
model和store有功能重合, 将它们整合成一个
recordStore
import clone from '@/lib/clone'const localStorageItemName = 'recordList';const recordStore = {recordList: [] as RecordItem[],fetchRecords(){this.recordList = JSON.parse(window.localStorage.getItem(localStorageItemName) || '[]') as RecordItem[];return this.recordList},createRecord (record: RecordItem){const record2: RecordItem = clone(record);record2.createAt = new Date();this.recordList.push(record2);this.saveRecords()},saveRecords() {window.localStorage.setItem(localStorageItemName, JSON.stringify(this.recordList));}}recordStore.fetchRecords()export default recordStore
tagStore
import createId from '@/lib/idCreator';const localStorageItemName = 'tagList';const tagStore = {tagList: [] as Tag[],fetchTags() {this.tagList = JSON.parse(window.localStorage.getItem(localStorageItemName) || '[]');return this.tagList;},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.save();window.alert('创建成功');return 'success';},updateTag(id:string, name: string){const ids = this.tagList.map(item => item.id);if (ids.indexOf(id) >= 0) {const tag = this.tagList.filter(item => item.id === id)[0];if (tag.name === name) {return 'duplicated';} else {tag.name = name;this.save();return 'success';}} else {return 'not found';}},removeTag (id:string) {let index = -1for (let i = 0; i < this.tagList.length; i++) {if (this.tagList[i].id === id){index = ibreak}}this.tagList.splice(index, 1)this.save()return true},findTag(id:string) {return this.tagList.find(t => t.id === id)},save() {window.localStorage.setItem(localStorageItemName, JSON.stringify(this.tagList));}}tagStore.fetchTags()export default tagStore
可选链操作符(?.)和空值合并操作符(??)
当不确定对象属性是否存在时,用可选链操作符,不存在则返回undefined
空值合并操作符,当左侧为null或undefined时,返回右侧值,否则返回左侧值(包括0和’’)
(详情见MDN)
let customer = {name: "Carl",details: { age: 82 }};let customerCity = customer?.city ?? "暗之城";console.log(customerCity); // “暗之城”
store的bug之值与地址
在Money组件中, recordList是直接赋值的store.recordList, 当store.recordList变化时, recordList也跟着变化,这是因为传的是对象的地址
export default class Money extends Vue {recordList = store.recordList;// ...}
如果store中有一个基本类型的变量count
const store = {count: 0,addCount(){this.count += 1},...recordStore,...tagStore}
如果将count赋值给Money组件的data, 当store中的count变化时, Money组件中的data并不会变化,因为传的是值
解决办法是使用computed, 同时要把store传给组件的data, 告诉vue要监听store的变化
@Component({components: {Tags, FormItem, Types, NumberPad},computed:{count(){return store.count}}})export default class Money extends Vue {store = storeaddCount(){store.addCount()}// ...}
如果其他组件也要用store, 可以把store传给app.vue的data
<script lang="js">import store from '@/store/index2'export default {data(){return {store: store}}}</script>
不管store中是什么类型的变量,在组件中都应该使用computed
把store2变成this.store2
每个组件使用store都需要import store, 
我们可以在main.js中将store注册为全局的
import store2 from '@/store/index2'Vue.prototype.$store2 = store2
这样组件中就可以直接使用this.$store2而不需要引入store了
但是TypeScript不认识$store2, 需要声明类型
在custom.d.ts中
import Vue from 'vue'declare module 'vue/types/vue' {interface Vue {$store2: any}}
总结
全局状态管理(也叫全局数据管理)的好处是什么?
- 解耦:将所有数据相关的逻辑放入 store(也就是 MVC 中的 Model,换了个名字而已)
 - 数据读写更方便:任何组件不管在哪里,都可以直接读写数据
 - 控制力更强:组件对数据的读写只能使用 store 提供的 API 进行(当然也不排除有猪队友直接对 tagList 和 recordList 进行 push 等操作,这是没有办法禁止的)
 
