Vuex Axios

Vuex

Vuex是一个专门为Vue.js应用所设计的集中式状态管理架构,它借鉴了Flux和Redux的设计思想,但简化了概念,采用了一种为能更好地发挥Vue.js数据相应机制而专门设计的实现。

Vuex是一个专为Vue.js应用开发的状态管理模式,采用集中式存储管理应用组件状态,并以响应规则保证状态以一种可预测的方式发生变化。

# 安装Vuex
$ npm i vuex --S

# 引入
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

状态管理模式

# 计数器
new Vue({
  // state 驱动应用的数据源
  data () {
    return {
      count:0
    }
  },
  // view 以声明方式将数据源映射到视图
  template:`<span>{{count}}</span>`,
  //actions 响应在视图上的用户输入导致的中状态变化
  methods:{
    increment() {
      this.count ++
    }
  }
})

状态state的概念,简单来说可视为项目中使用的数据的集合。Vuex使组件本地状态和应用层级状态有了一定的差异。

  • 组件本地状态:表示仅仅在组件内部使用的状态,类似于通过配置选项传入Vue组件内部。
  • 应用层级状态:表示同时被多个组件共享的状态层级

单向数据流

假如有一个父组件同时包含两个子组件,父组件可以很容易的使用prop属性向子组件传递数据。两个子组件如何和对象互相通信呢?子组件如何传递数据给父组件呢?项目规模很小时,可通过事件派发和监听来完成父组件和子组件的通信。随着项目规模增长,遇到的问题是:

  • 保持对所有事件追踪变得越来越困难,到底哪个事件是哪个组件派发的,哪个组件该监听哪个事件呢?
  • 项目逻辑分散在各个组件当中,很容易导致逻辑混乱,不利于项目维护。
  • 父组件变得和子组件耦合度越来越严重,因为它需要明确的派发和监听子组件的某些事件。
4933701-c6946fdef5d709cd.png
单向数据流

当应用遇到多个组件共享状态时,单向数据流的间接性很容易被破坏:

  • 多个视图依赖于同一个状态
    传参的方法对于多层嵌套的组件将会非常繁琐,对于兄弟组件间的状态传递无能为力。
  • 来自不同视图的行为需要变更同一状态
    采用父子组件直接饮用或通过事件来变更和同步状态的多份拷贝

为什么不把组件的共享状态抽取出来以一个全局单例模式管理呢?在这种模式下,组件树构成一个巨大的视图,不管在树的哪个位置,任何组件都能获取状态或触发行为。

另外,通过定义和隔离状态管理中各种概念并强制遵守一定的规则,代码将会变得更加结构化且易于维护。

核心概念

  1. 单一状态树
    使用一个对象包含全部应用层级状态,它作为唯一数据源存在,这意味着,每个应用将紧紧包含一个仓库store的实例。单一状态树让我们能够直接地定位任意特定的状态片段,在调用过程中也能轻易地取得整个当前应用状态的快照。
  2. 获取状态
    派生状态getters用来从仓库store中获取Vue组件数据
  3. 状态变更
    状态变更mutators的事件处理器可用来驱动状态的变化
  4. 异步操作
    异步操作actions可给组件使用的函数,以此用来驱动事件处理器mutations
4933701-066aaf208bff9f98.png
Vuex应用中的数据流向

Vuex规定,属于应用层级的状态只能通过状态变更mutation中的方法来修改,而派发mutation中的事件只能通过action

从左至右,从组件触发,组件中调用action,在action层可以和后台数据交互,比如获取初始化数据源,或者中间数据的过滤等。然后在action中去派发mutationmutation去触发状态的改变,从而触发视图的更新。

注意

  • 数据流都是单向的
  • 组件能够调用action
  • action用来派发mutation
  • 只有mutation可以改变状态
  • store是响应式的,无论state什么时候更新,组件都将同步更新。

开始

每个Vuex应用的核心就是仓库(store),store基本就是一个容器,包含着应用中大部分的状态(state)。Vuex和单纯的全局对象由两点不同:

  • Vuex的状态存储时响应式的,当Vue组件从store中读取状态时,若store中的状态发生变化,相应的组件也会得到高效更新。
  • 不能直接改变store中的状态,改变store中的状态唯一途径就是显式地提交(commit)mutation。这样使得我们可以方便地跟踪每一个状态的变化。

仓库

Vuex应用状态state都存放在store中,Vue组件可从store中获取状态,可把store通俗的理解为一个全局变量的仓库。和单纯的全局变量的区别是当store中的状态发生变化时,相应的Vue组件也会得到更新。

//仓库
const store = new Vuex.Store({
  //状态
  state:{
    count:0
  },
  //状态变更
  mutations:{
    increment(state){
      state.count++
    }
  }
})
//获取状态对象
store.state.count
//触发状态变更
store.commit('increment')

状态

单一状态树

Vuex使用单一状态树,用一个对象包含了全部应用层级状态,并作为一个“唯一数据源(SSOT)”而存在。这意味着,每个应用将仅仅包含一个状态实例。单一状态树让我们能够直接地定位任意特定的状态片段,在调试过程中也能轻易地去的整个当前应用状态的快照。

在Vue组件中获得Vuex状态

由于Vuex的状态存储的是响应式的,从store实例中读取状态最简单是方式是在计算属性中返回某个状态。

// 创建组件
const Counter = {
  template:`<div>{{count}}</div>`,
  computed:{
    count () {
      // 每当store.state.count变化时会重新求取计算属性并触发更新相关联的DOM
      return store.state.count
    }
  }
}

这种模式导致组件依赖全局状态单例,在模块化的构建系统中,在每个需要使用state的组件中需要频繁地导入,并在测试组件时需模拟状态。

Vuex通过store选项,提供了一种机制将状态从根组件注入到每个子组件中。

const app = new Vue({
  el:'#app',
  store,//将store对象提供给store选项,把store实例注入到所有的子组件中。
  componenets:{Counter},
  template:`<div class="app"><counter></counter></div>`
})

通过在根实例中注册store选项,该store实例会注入到根组件下的所有子组件中,且子组件能通过this.$store访问到。

const Counter = {
  template:`<div>{{count}}</div>`,
  computed:{
    count () {
      return this.$store.state.count
    }
  }
}

当一个组件需要获取多个状态时,将这些状态都声明为计算属性会有些重复和冗余,为解决这个问题,使用mapState辅助函数帮助我们生成计算属性。

// 单独构建版本中辅助函数为Vuex.mapState
import {mapState} from 'vuex'

export default {
  computed:mapState({
    //箭头函数使得代码更简洁
    count:state=>state.count,
    //传字符串count等同于state=>state.count
    countAlias:'count',
    //为了能够使用this获取局部状态必须使用常规函数
    countPlusLocalState(state){
      return state.count + this.localCount
    }
  })
}

当映射的计算属性的名称和state的子节点名称相同时,也可以给mapState传入一个字符串数组。

// 映射this.count为store.state.count
computed:mapState(['count'])

mapState函数返回的是一个对象,如何将其与局部计算属性混合使用呢?通常需要使用一个工具函数将多个对象合并为一个,以便于将最终对象传给computed属性。自从有了对象展开运算符可极大地简化写法。

computed:{
  localComputed () {
    // 使用对象展开运算符对此对象混入到外部对象中
    ...mapState({
    //...
    })
  }
}

使用Vuex并不意味着需要将所有状态放入Vuex,虽然将所有状态放入Vuex会使状态变化更显式和易调用,但也会使代码变得冗长且不直观。有些状态 严格属于单个组件,最好还是作为组件的局部状态。

getter

有时需从仓库中的状态中派生一些状态

computed:{
  // 对列表进行过滤并计数
  doneTodosCount() {
    return this.$store.state.todos.filter(todo=>todo.done).length
  }
}

如果有多个组件需要用到此属性,要么复制函数或抽取到一个共享函数然后再多出导入,但是无论哪种方式都不是很理想。

Vuex允许在仓库中定义getter(可认为是store的计算属性),就像计算属性一样,getter的返回值会根据它的依赖被缓存起来,只有当当它的依赖发生变化了才会被重新计算。

const store = new Vuex.Store({
  state:{
    todos:[
      {id:1, text:'...', done:true},
      {id:2, text:'...', done:false}
    ]
  },
  // getter类似仓库的计算属性一样,其返回值会根据其依赖被缓存起来,只有当以来之发生改变才会被重新计算。
  getters:{
    doneTodos:state=>{
      return state.todos.filter(todo=>todo.done)
    }
  }
})

getter会暴露为store.getters对象,可以以属性的形式访问其值:

# getter会暴露为store.getters对象,可以以属性的形式访问其值:
store.getters.doneTodos

# getter也可以接受其他getter作为第二个参数
getters:{
  doneTodosCount:(state, getters)=>{
    return getters.doneTodos.length
  }
}

store.getters.doneTodosCount

# 可以很容易在任何组件中使用它
computed:{
  doneTodosCount(){
    return this.$store.getters.doneTodosCount
  }
}

注意:getter在通过属性访问时是作为Vue的响应式系统的一部分缓存其中的。

可以通过让getter返回一个函数来实现给getter传参,在对store中的数组进行查询时非常有用。

getters:{
  //可以通过让getter返回一个函数来实现给getter传参
  getTodoById:(state)=>(id)=>{
    return state.todos.find(todo=>todo.id === id)
  }
}

store.getters.getTodoById(2)

注意,getter在通过方法访问时,每次都会去进行调用,而不会缓存结果。

mapGetters 辅助函数

# mapGetters辅助函数仅仅是将store中的getter映射到局部计算属性
import {mapGetters} from 'vuex'

export default {
  computed:{
    //使用对象展开运算符将getter混入computed对象中
    ...mapGetters(['doneTodosCount','anotherGetter'])
  }
}

# 若想将一个getter属性另取一个名字,使用对象形式。
mapGetters({
  //将this.doneCount映射为this.$store.getters.doneTodosCount
  doneCount:'doneTodosCount'
})

mutation

更改Vuex仓库中的状态的唯一方式是提交mutation。vuex中的mutation非常类似于事件:每个mutation都有一个字符串的事件类型(type)和一个回调函数(handler)。这个回调函数就是实际进行状态更改的地方,并且它会接收state作为第一个参数。

const store = new Vuex.Store({
  //状态
  state:{count:1},
  //状态变更
  mutations:{
    //每个mutation都有一个字符串的事件类型和一个回调函数
    increment (state) {
      state.count++ //变更状态
    }
  }
})

不能直接调用一个mutation handler,这个选项更像是事件注册:“当触发一个类型为increment的mutation时,调用此函数”。要唤醒一个mutation handler,你需要以相应的type调用store.commit方法。

store.commit('increment')

提交载荷

可以像store.commit传入额外的参数,即mutation的载荷payload

mutations:{
  increment (state, n){
    state.count += n
  }
}
// 向store.commit传入额外的参数及mutation的载荷
store.commit('increment', 10)

多数情况下,载荷是一个对象,可包含多个字段并记录mutation会更易读。

mutations:{
  increment(state, payload) {
    state.count += payload.amount
  }
}
// 载荷为对象
store.commit('increment',{acount:10})

对象风格的提交方式

提交mutation的另一种方式是直接使用包含type属性的对象

# 使用包含type属性的对象提交mutation
store.commit({type:'increment', amount:10})

# 使用对象风格提交方式,整个对象都作为载荷传给mutation函数,因此handler保持不变。
mutations:{
  increment(state, payload){
    state.count += payload.amount
  }
}

mutation需遵守vue的响应规则

既然vuex的store中的状态是响应式的,当变更状态时,监视状态的vue组件会也自动更新,这也意味着vuex中的mutation也需要与使用vue一样遵守一些事项:

  1. 最好提前在store中初始化好所有属性
  2. 当需要在对象上添加新属性时,应该
  • 使用Vue.set(obj, 'prop', 123)
  • 以新对象替换老对象
state.obj = {...state.obj, prop:123}

使用常量替代mutation事件类型

使用常量替代mutation事件类型在各种flux实现中最为常见,可使用linter之类的工具发挥作用,同时将常量放在单独的文件中可让代码合作者对整个app包含的mutation一目了然。

// mutation-types.js
export const SOME_MUTATION = ''

//store.js
import Vuex from 'vuex'
import {SOME_MUTATION} from './mutation-types'

const store = new Vuex.Store({
  state:{...},
  mutations:{
    //使用ES2015风格的计算属性命名功能来使用一个常量作为函数名称
    [SOME_MUTATION](state){

    }
  }
})

mutation必须是同步函数

Axios

Axios是一个基于promise的HTTP库,可用于浏览器和Node.js中。

  • 从浏览器中创建XMLHttpRequest
  • 从Node.js中创建HTTP请求
  • 支持Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持防御XSRF
# 安装 axios
$ npm i axios -S

# 执行GET请求
axios.get('/user?id=1').then(function(response){
  console.log(response);
}).catch(function(error){
  console.log(error)
})

axios.get('/user', {params:{id:1}}).then(function(response){
  console.log(response);
}).catch(function(error){
  console.log(error)
})

# 执行POST请求
axios.post('/user',{username:'', password:''}).then(function(response){
  console.log(response);
}).catch(function(error){
  console.log(error)
})

# 执行多个并发请求
function getUsername(){
  return axios.get('/user/1')
}
function getPermission(){
  return axios.get('/user/1/permission')
}
axios.all([getUsername(), getPermission()]).then(axios.spread(function(username, permission){

}))

axios API

# 向axios传递相关配置来创建请求
axios(url[, config])

Vue中使用Axios

Axios并非Vue的插件无法直接引入后使用,只能在每个需要发送请求的组件中即时引入。为了解决这个问题,有两种思路:

  1. 引入Axios修改原型链
# main.js
# 引入
import axios from 'axios`
# 原型链绑定
Vue.prototype.$ajax = axios
# 使用
methods:{
  submit(){
    this.$ajax({
      method:'post',
      url:'url',
      data:{username:'username', password:'password'}
    })
  }
}
  1. 结合Vuex封装action

Vuex的mutation类似于事件,可用于提交Vuex中的状态,action和mutation很类似,区别在于action包含异步操作,还可通过action来提交mutation。最主要的区别在于

  • mutation 固有参数state用于接收Vuex中的state对象
  • action 固有参数context是state的父级,包含着state、getters。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值