文章目录
一、单向数据流
1. 理念示意图
“单向数据流” 理念示意图
2. 简述
- state,驱动应用的数据源;
- view,以声明方式将 state 映射到视图;
- actions,响应在 view 上的用户输入导致的状态变化。
但是,当我们的应用遇到**多个组件共享状态(数据)**时,单向数据流的简洁性很容易被破坏(回忆 search-blog
和 search-history
的代码):
- 多个视图依赖于同一状态(数据)。
- 来自不同视图的行为需要变更同一状态(数据)。
所以我们不得不通过 父子组件传递数据 的方式,来频繁的修改状态(数据)。但是这种方式是 非常脆弱,通常会导致无法维护的代码。
二、什么是全局状态管理模式
所以我们就需要就想到了一种方案,我们把:
把多个组件之间共用的数据抽离出来,通过一个 单例模式 进行管理,而这种管理的方式就叫做【全局状态管理模式】。
具备 【全局状态管理模式】 的库,就是 【全局状态管理工具】
而在 vue
中存在一个专门的 【全局状态管理工具】,叫做 vuex
。
因为 uniapp
追随 vue
和 微信小程序
的语法,所以我们可以在 uniapp
中使用 vuex
来进行 【全局状态管理】,并且这是一个 非常被推荐 的选择。
三、重点概念
3.1. 什么是全局状态管理模式?
模式:把多个组件之间共用的数据抽离出来,通过一个 单例模式 进行管理。
3.2.全局状态管理工具?
工具:具备 【全局状态管理模式】 的库
3.3. 什么是 vuex
对 vue
应用程序进行全局状态管理的工具
四、在项目中导入 vuex
4.1. 状态管理配置
在项目根目录下创建store文件夹,并在store文件夹下创建index.js
内容如下:
// 1. 导入 Vue 和 Vuex
import Vue from 'vue';
// uniapp 已默认安装,不需要重新下载
import Vuex from 'vuex';
// 2. 安装 Vuex 插件
Vue.use(Vuex);
// 3. 创建 store 实例
const store = new Vuex.Store({});
export default store;
4.2. 注册vuex
在 main.js 中注册 vuex 插件
// 导入 vuex 实例
import store from './store';
...
const app = new Vue({
...App,
store // 挂载实例对象
});
app.$mount();
五、测试 vuex 是否导入成功
,可以按照模块单独配置,然后,在index.js中引入,来进行对各模块的统一状态管理
5.1. 创建搜索模块
在store文件夹下,创建modules文件夹,并在此文件夹下创建search模块(search.js)
内容如下:
export default {
// 独立命名空间
namespaced: true,
// 通过 state 声明数据
state: () => {
return {
msg: 'hello vuex'
};
}
};
5.2. 注入模块
在 index.js
中注入模块
...
// 导入 search.js 暴露的对象
import search from './modules/search';
// 2. 安装 Vuex 插件
Vue.use(Vuex);
// 3. 创建 store 实例
const store = new Vuex.Store({
modules: {
search
}
});
export default store;
5.3. 页面使用模块中的数据
有3步:
- 导入 mapState 函数
- 在 computed 中,通过 mapState 函数,注册 state 中的数据,导入之后的数据可直接使用(就像使用 data 中的数据一样)
// mapState(模块名, [‘字段名’,‘字段名’,‘字段名’])- 使用导入的 vuex 模块中的数据
在index.vue 中使用 模块中的数据
<template>
<view class="search-blog-container">
<!-- 3. 使用导入的 vuex 模块中的数据 -->
<view>{{ msg }}</view>
...
</view>
</template>
<script>
// 1. 导入 mapState 函数
import { mapState } from 'vuex';
...
export default {
...
computed: {
// 2. 在 computed 中,通过 mapState 函数,注册 state 中的数据,导入之后的数据可直接使用(就像使用 data 中的数据一样)
// mapState(模块名, ['字段名','字段名','字段名'])
...mapState('search', ['msg'])
}
};
</script>
5.4. 构建 search 模块
search.js
export default {
// 独立命名空间
namespaced: true,
// 通过 state 声明数据
state: () => ({
searchData: []
}),
// 更改 state 数据的唯一方式是:提交 mutations
mutations: {
/**
* 添加数据
*/
addSearchData(state, val) {
if (!val) return;
const index = state.searchData.findIndex((item) => item === val);
if (index !== -1) {
state.searchData.splice(index, 1);
}
state.searchData.unshift(val);
},
/**
* 删除指定数据
*/
removeSearchData(state, index) {
state.searchData.splice(index, 1);
},
/**
* 删除所有数据
*/
removeAllSearchData(state) {
state.searchData = [];
}
}
};
5.5. 对vuex数据操作
对vuex数据操作步骤:
- 导入mapMutations 函数,处理mutation的问题
- 利用 mapMutations函数,可以直接把vuex中的mutation合并到当前组件的methods中,合并之后,使用mutation就像使用methods中的方法一样
<template>
<view class="hot-container">
<view v-for="(item, index) in tabData" :key="index">
{{ item.label }} - {{ index }}
</view>
</view>
</template>
<script>
// 1. 导入 mapState 函数
// 2. 导入mapMutations 函数,处理mutation的问题
import { mapState, mapMutations } from 'vuex';
import { getHotTabs } from 'api/hot';
export default {
data() {
return {
tabData: [],
};
},
// 组件实例配置完成,但是DOM尚未渲染,进行网络请求,配置响应数据
created() {
this.loadHotTabs();
},
methods: {
// 利用 mapMutations函数,可以直接把vuex中的mutation合并到当前组件的methods中,合并之后,
// 使用mutation就像使用methods中的方法一样
...mapMutations('search', ['removeSearchData', 'removeAllSearchData']),
// 删除所有数据
onDelAllClick() {
this.removeAllSearchData();
},
// 删除指定数据 index角标
onClearAll(item, index) {
this.removeSearchData(index);
},
/**
* 获取热搜标题数据
*/
async loadHotTabs() {
// uniapp 支持 async await
const res= await getHotTabs();
this.tabData = res.content;
console.log('res', res.content);
},
},
computed: {
// 2. 在 computed 中,通过 mapState 函数,注册 state 中的数据,导入之后的数据可直接使用(就像使用 data 中的数据一样)
// mapState(模块名, ['字段名','字段名','字段名'])
...mapState('search', ['searchData']),
},
};
</script>
<style lang="scss"></style>
六、vuex数据持久化
已完成 数据和组件的分离,所以【数据持久化】不需要涉及到组件内的代码
用到的api是uni.setStorage,参数key value形式
案例演示:搜索模块
6.1. 搜索模块数据持久化
store/search.js
const STORAGE_KEY = 'search-list';
const HISTORY_MAX = 10;
export default {
// 独立命名空间
namespaced: true,
// 通过 state 声明数据
state: () => ({
// 优先从 storage 中读取
searchData: uni.getStorageSync(STORAGE_KEY) || []
}),
// 更改 state 数据的唯一方式是:提交 mutations
mutations: {
/**
* 保存数据到 storage
*/
saveToStorage(state) {
uni.setStorage({
key: STORAGE_KEY,
data: state.searchData
});
},
/**
* 添加数据
*/
addSearchData(state, val) {
if (!val) return;
const index = state.searchData.findIndex((item) => item === val);
if (index !== -1) {
state.searchData.splice(index, 1);
}
// 判断是否超过了最大缓存数量
if (state.searchData.length > HISTORY_MAX) {
state.searchData.splice(HISTORY_MAX - 1, state.searchData.length - HISTORY_MAX - 1);
}
state.searchData.unshift(val);
// 调用 saveToStorage
this.commit('search/saveToStorage');
},
/**
* 删除指定数据
*/
removeSearchData(state, index) {
state.searchData.splice(index, 1);
// 调用 saveToStorage
this.commit('search/saveToStorage');
},
/**
* 删除所有数据
*/
removeAllSearchData(state) {
state.searchData = [];
// 调用 saveToStorage
this.commit('search/saveToStorage');
}
}
};
6.2. 搜索api
搜索结果 - 获取搜索结果数据
import request from '../utils/request';
/**
* 搜索结果
*/
export function getSearchResult(data) {
return request({
url: '/search',
data
});
}
6.3. 页面使用
search-result-list.vue
<template>
<view> 搜索结果 </view>
</template>
<script>
import { getSearchResult } from 'api/search';
export default {
name: 'search-result-list',
props: {
// 搜索关键字
queryStr: {
type: String,
required: true
}
},
data() {
return {
// 数据源
resultList: [],
// 页数
page: 1
};
},
created() {
this.loadSearchResult();
},
methods: {
/**
* 获取搜索数据
*/
async loadSearchResult() {
const { data: res } = await getSearchResult({
q: this.queryStr,
p: this.page
});
this.resultList = res.list;
console.log(this.resultList);
}
}
};
</script>
<style lang="scss"></style>