window全局变量
在main.ts中声明全局变量
window.tagList = tagListModel.fetch()
在custom.d.ts中用interface给tagList声明类型
type tag = {
id: string
name: 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 = -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.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 = store
addCount(){
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 等操作,这是没有办法禁止的)