vue(3) promise_vuex_axios
一. Promise
MDN文档Promise 简单的实现:mypromise.js 具体实现参考:从0到1实现Promise Promise是异步编程的一种解决方案。我们封装一个网络请求的函数,因为不能立即拿到结果,往往我们会传入另外一个函数,在数据请求成功时,将数据通过传入的函数回调出去。若请求十分复杂,会出现回调地狱。 代码难看而且不容易维护。Promise以一种更加优雅的方式来进行这种异步操作。
1.1. Promise的基本使用
Promise有3种状态:
- pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。
- fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()
- reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()
语法:new Promise(executor)
,其中executor
是一个双参函数,参数为resolve和reject
,这2个参数也是函数,作为参数传递进来,所以这2个函数被称为回调函数。
const myFirstPromise = new Promise((resolve, reject) => {
// 执行异步操作,最终调用以下任一操作:
//
// resolve(someValue) // fulfilled
// or
// reject("failure reason") // rejected
});
一般使用如下:
const p = new Promise((resolve) => {
setTimeout(() => {
console.log("模拟获取数据");
let data = { name: "oppo", age: 18 };
resolve(data);
}, 2000);
});
let myobj = {};
p.then((data) => {
myobj = data;
}).catch((err) => console.log(err));
console.log(myobj);
- 如何将异步操作放入到promise中
- (resolve, reject) => then/catch
1.2. Promise的链式调用
Promise.resovle():将数据包装成Promise对象,并且在内部回调resolve()函数 Promise.reject():将数据包装成Promise对象,并且在内部回调reject()函数
链式调用 | 简写:
1.3. Promise的all方法
详见MDN文档。
二. Vuex
Vuex的API:https://vuex.vuejs.org/zh/api/ 别人的子模块的组织方式参考:redux-module-menu.js
2.1. 什么是状态管理
简单说,就是管理多个组件共享同一个变量会用到。
2.2. Vuex的基本使用
- 安装:
npm i vuex -S
- 创建src/store/index.js,并写代码:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state:{ // 定义需要管理的状态
count:66
},
mutations:{ // 相当于methods,对state进行操作,执行同步操作,可被devtools捕捉
increment(state){
state.count += 1;
},
decrement(state){
state.count -= 1;
}
},
getters:{ // 获取一些state变异后的状态,相当于computed
// 示例在下面
},
actions:{}, // 执行一些异步操作
modules:{},
})
- 在组件中使用:
computed: {
count() {
return this.$store.state.count;
}
},
methods: {
increment() {
this.$store.commit('increment');
},
decrement() {
this.$store.commit('decrement');
}
}
- state -> 直接修改state(错误)
- mutations -> devtools
2.3. 核心概念
- state -> 单一状态树
状态保存在一个Store对象中,这样管理、维护方便。
- getters ->
getters:{
greaterAgesStus: state => { // 获取年龄不小于20的学生列表
return state.studuents.filter(s => s.age>=20)
},
greaterAgesCount: (state, getters) => { // 可传入第2个默认参数,即getters对象
return getters.greaterAgesStus.length;
},
stuByID: state => { // 若要传入参数,则需在返回的函数中传入参数
return id => {
return state.students.find(s => s.id === id);
}
}
}
// 通过属性访问,会暴露为 store.getters 对象
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
// 在其他组件中使用:
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
// 通过方法访问,传参数时:
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
- mutations ->
如上,第一个参数:state 若想携带额外参数,被称为是mutation的载荷(Payload) 若参数不止一个,则payload可以是一个对象: 另一种commite提交的风格: 响应规则: 若想给state中的对象添加新属性,需要如下操作:
- 方法一:使用Vue.set(obj, ‘newProp’, 123)
- 方法二:用新对象给旧对象赋值
**Mutation常量类型 ** 当需要更新的状态变多,mutations中的方法也随之变多,变得不好管理和使用。 可以使用一些常量来替代mutation事件的类型,并将其放入到单独的文件中,便于管理使用。就是抽离方法名到一个单独文件中,然后都引入这个文件。
- actions -> 异步操作(Promise)
mutaions中存放的是同步的方法,因为devtools工具可以捕捉mutations的快照,只能捕捉同步而无法捕捉异步方法,无法追踪异步操作何时完成。因此,在action中进行异步操作,如网络请求等。 其中的context,指的是与store对象具有相同方法和属性的对象,因此可以context.commit(‘increment’)、context.state 定义了actions中的方法,在组件中如何调用呢?使用dispatch,同样也支持传递参数payload
// 子组件
methods:{
increment(){
this.$store.dispatch('increment', {count:5});
}
}
// store/index.js
mutations:{
increment(state,payload){
state.count += payload.count;
}
},
actions:{
increment(context,payload){ // 传参
setTimeout(()=>{ // 模拟异步而已
context.commit('increment',payload);
},2000)
}
}
可以将异步操作放入actions中:
actions: {
increment(context) {
return new Promise(resolve => {
setTimeout(() => {
const data = { school: 'oppo' };
context.commit('increment', data);
resolve();
}, 2000);
});
}
},
// 子组件调用
methods:{
increment(){
this.$store.dispatch('increment').then(()=>console.log('ok'););
}
}
- modules
当管理的状态越来越多时,store对象就会变得臃肿,如何解决这个问题? Vuex允许我们将store分割成模块(Module),每个模块拥有自己的state、mutations、actions、getters等 那如何组织这样的结构?如图所示: 在组件中如何调用呢?很简单,和之前一样利用this.$store直接调用对应方法。 子模块中actions的写法:(state和mutations写法与上文一致)
// 接收一个context参数对象
const moduleA = {
// ......
actions: {
incrementRootSum({ state, commit, rootState }) { // 第三个参数是根模块节点的状态
if ((state.count + rootState.count) % 2 === 1) {
commit('increment');
}
}
},
// ......
};
子模块中getters的写法:
const muduleA = {
// ...
getters: {
sumWithRootCount(state, getters, rootState) { // 第二个参数是getters对象
return state.count + rootState.count;
}
}
}
2.4. 目录组织方式
三. 网络请求封装
3.1. 网络请求方式的选择
传统的ajax是基于XHR的,如果直接使用XHR,配置、调用十分麻烦,有人封装了jQuery-ajax。 vue开发中不用jQuery-ajax,因为引入重量级jQuery,代码更多。 vue官方推出了Vue-resource,但在vue2.0之后就去掉了,不再更新。 目前:axios(基于XHR),官网API:http://www.axios-js.com/zh-cn/docs/ 别人封装的小参考:关同学的axios封装.zip
3.2. axios的基本使用
在项目开发中:
- 安装:
npm i axios
- 引入:
import axios from 'axios'
- 使用:axios基本使用.js
3.3. axios的相关配置
在开发中,很多参数都是固定的,这时候可以做一些参数抽取,避免重复。 默认axios实例的配置:
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
自定义实例默认值:
// Set config defaults when creating the instance
const instance = axios.create({ // axios的创建实例
baseURL: 'https://api.example.com'
});
// Alter defaults after instance has been created
instance.defaults.baseURL = 'http://www.jd.com';
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
3.4. axios的创建实例
如上。 为什么要创建axios实例?从axios模块导入axios,使用的实例是默认的实例,给该实例进行一些默认配置,该配置就被固定下来。后续开发若想有不一样的配置,就可以创建新的axios实例,并进行相应配置。 举例:
axios.defaults.baseURL = 'http://www.baidu.com';
const Instance = axios.create({
baseURL:'http://www.jd.com'
})
3.5. axios的封装
axios的基本封装。在项目中可灵活再封装axios。
import originalAxios from 'axios';
export default function axios(options) {
return new Promise((resolve, reject) => {
// 1. 创建axios实例
const instance = originalAxios.create({ // 这样写个人感觉不太好,每次调用都要创建新实例
baseURL: '',
timeout: 5000,
headers: ''
});
// 2.传入对象进行网络请求
instance(options)
.then(res => resolve(res))
.catch(err => reject(err));
});
}
// 可再次简化:
export default function axios(options) {
// 1. 创建axios实例
const instance = originalAxios.create({ // 这样写个人感觉不太好,每次调用都要创建新实例
baseURL: '',
timeout: 5000,
headers: ''
});
return instance(options); // 因为axios实例返回的就是Promise对象嘛
}
// ps:上面包装了一个new Promise, 若以后axios不在维护并出现了新的包代替,就可以在这里座一层封,
// 而组件中的代码则无需更换
3.6. 请求里的配置选项
具体解释:http://www.axios-js.com/zh-cn/docs/#请求配置
const configOption = {
url: "/user", // 请求服务器的url
method: "get", // 创建请求的方法, 默认get
baseURL: "http://www.jd.com/api/", // 自动加在url前面
transformRequest: [(data, headers) => data], // 发送请求前对数据进行处理, 只适用于PUT/POST/PATCH
transformResponse: [(data) => data], // 传递给then/catch之前修改响应数据
headers: { "X-Requested-With": "XMLHttpRequest" }, //自定义请求头
params: { ID: 12345 }, // 指定url参数
paramsSerializer: () => {}, // 负责 `params` 序列化的函数
data: {}, // 作为请求主体被发送的数据, 只适用于PUT/POST/PATCH方法
timeout: 5000, // 指定请求超时时间,请求超时则中断请求
withCredentials: false, // 跨域请求时是否需要使用凭证
adapter: () => {}, // 允许自定义处理请求
auth: {}, // 表示应该使用 HTTP 基础验证,并提供凭据
responseType: "json", // 服务器响应的数据类型
responseEncoding: "utf8", // 表示用于解码响应的编码
xsrfCookieName: "XSRF-TOKEN", // 用作 xsrf token 的值的cookie的名称
xsrfHeaderName: "X-XSRF-TOKEN",// XSRF令牌值的http标头的名称
onUploadProgress: () => {}, // 允许为上传处理进度事件
onDownloadProgress: () => {}, // 允许为下载处理进度事件
maxContentLength: 2000, // 定义允许的响应内容的最大尺寸
validateStatus: () => {}, // 定义对于给定的HTTP响应状态码的promise状态是resolve或reject
maxRedirects: 5, // 定义在 node.js 中 follow 的最大重定向数目
socketPath: null, // 定义了在node.js中使用UNIX套接字
httpAgent: new http.Agent({ keepAlive: true }), // 定义在执行http时使用的自定义代理
httpsAgent: new https.Agent({ keepAlive: true }), // 定义在执行https时使用的自定义代理
proxy: { // 定义代理服务器的主机名称和端口
host: "127.0.0.1",
port: 9000,
auth: {
username: "mikeymike",
password: "rapunz3l",
},
},
cancelToken: new CancelToken(function (cancel) {}), // 指定用于取消请求的 cancel token
};
3.7. axios的拦截器
拦截器有什么作用?在请求发送之前、得到响应之后,可以做一些处理。 有请求拦截器、响应拦截器。
// 发送请求拦截器
instance.interceptors.request.use(
config => {
console.log('');
// 1.当发送请求时,在页面中添加一个loading组件,作为动画;
// 2.某些请求要求用户登录,判断用户是否有token,没有则跳转到loading页面
// 3.对请求参数进行序列化
return config;
},
err => {
console.log('');
// 1.比如请求超时,可以将页面跳转到一个错误页面中
return err;
}
);
// 得到响应拦截器
instance.interceptors.response.use(
response => {
console.log('');
// 1.主要是对数据进行过滤
return response;
},
err => {
console.log('');
// 1.根据status判断报错的错误码,跳转到不同的错误提示页面
return err;
}
);
四. 项目开发
首先是用脚手架生成初始项目,前提是已经安装了node/npm/vue脚手架:
vue create 项目名称
然后划分目录结构。
4.1. 划分目录结构
4.2. 引用了两个css文件
在src/asserts/css中引入2个css文件:
- normalize.css:github上下载normalize.css
- base.css:自己写的,定义项目基本的cssbase.css
4.3. vue.config.js和.editorconfig
配置别名,这样就可以避免../../等写法。vue.config.js可以配置。 webpack已经默认配置了1个别名:@:“src” 先在根目录下新建vue.config.js,然后写入以下配置: 新建.editorconfig,用于配置基本的编程规范,如缩进,换行等。
4.4. 项目的模块划分: tabbar -> 路由映射关系
4.5. 首页开发
- navbar 的封装
- 网络数据的请求
- 轮播图
- 推荐信息
git remote add origin https://github.com/coderwhy/testmall.git
git push -u origin master
sync -> 同步
async -> 异步
aysnc operation: 操作
xcode/iphonex/xml
token ->
linus -> linux/git