Skip to content

异步获取数据列表并完成渲染-未使用actions与异步获取数(Lesson12 1/3) ​

本页对应 Lesson12,共 7 段视频,约 1 小时 26 分。

学习目标 ​

  • 能独立创建并注册 Vuex store,在组件中读取 state 并完成页面渲染。
  • 能通过 commit 提交 mutations 修改 state,正确传递 payload,并使用 mapMutations 的数组/对象写法简化调用。
  • 能说明 mapMutations 将 mutations 映射为组件方法的实现原理,并手写等价映射函数验证效果。
  • 能定义 getters 及 mapGetters 简写,在组件中读取派生状态,并通过修改依赖 state 验证缓存行为。
  • 能按功能拆分 Todo 组件,在 Vuex 中完成添加任务功能,并验证空标题、重复标题提示及输入框清空。
  • 能使用 actions 完成异步获取数据列表并渲染,分别实现未使用 actions 与使用 actions 的版本并对比调用流程。

知识点 ​

先把列表数据放进仓库:state 与 mutation 的最小闭环 ​

为什么需要:列表数据要被组件渲染,也要在请求回来后统一更新。转写稿里反复强调,Vuex 是集中式数据管理:组件通过 $store.state 取状态,修改状态要走 commit 调用 mutations,不能随手直接改。本节先把 items 放进 state,再准备一个 UP_ITEMS mutation 专门接收接口返回的数组。

怎么想:仓库只负责“存”和“按规则改”。异步请求本身不放进 mutation,因为转写稿明确说 mutations 一般是同步操作;本节又明确“未使用 actions”,所以请求放在组件里,拿到数据后再提交 mutation。

怎么做:在 src/store/index.js 中创建仓库:

js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);
// 数据状态
const state = {
	items:[]
};
const mutations = {
	UP_ITEMS(state,items){
		state.items = items;
	}
};
const getters = {

};
const store = new Vuex.Store({
	state,
	mutations,
	getters
});
export default store;

易错点:Vue.use(Vuex) 不能漏;new Vuex.Store 的 S 大写;mutation 名建议全大写、多个单词用下划线,转写稿也提到这是规范。getters 本节为空对象即可,不要为了“看起来完整”编造计算逻辑。

在组件 mounted 中发异步请求:先确认返回结构,再提交 ​

为什么需要:页面一挂载就要拿到远程列表。转写稿里的调试思路是:先打印接口返回,确认数据在哪一层,再决定 commit 什么,不要凭感觉直接写。

怎么想:axios.get(...).then(...) 里先 console.log(value.data.items),确认它是要渲染的数组;确认后再 this.$store.commit("UP_ITEMS", value.data.items)。这样 mutation 只负责把数组赋给 state.items,职责清楚。

怎么做:在 src/App.vue 中:

vue
<template>
    <div>
        <div v-for="item in $store.state.items" :key="item.id">
            {{item.full_name}}
        </div>
    </div>
</template>

<script>
import axios from "axios";
export default {
    name: "App.vue",
    mounted(){
        axios.get("https://api.github.com/search/repositories?q=r&sort=stars").then(value=>{
            console.log(value.data.items);
            this.$store.commit("UP_ITEMS",value.data.items);
        })
    }
}
</script>

<style scoped>

/*app*/
.todo-container {
    width: 600px;
    margin: 0 auto;
}
.todo-container .todo-wrap {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
}

</style>

易错点:不要在 then 里写 this.$store.state.items = value.data.items,这绕过了 mutation,违背集中式管理;转写稿里专门演示过“直接改能显示但仓库数据没变”的错误示范。commit 第一个参数是 mutation 名,第二个参数是 payload;本节 payload 就是整个数组。请求失败没有写 catch,本节笔记未涉及,不要额外编造处理方案。

模板渲染:用 $store.state.items 直接取,key 用稳定 id ​

为什么需要:数据进入仓库后,模板要能响应式更新。转写稿里说 state 的内容是响应式的,mutation 赋值后视图会跟着变。

怎么想:v-for="item in $store.state.items" 直接读仓库状态;每一项展示 item.full_name,:key="item.id" 用接口返回的稳定 id,避免用索引造成复用问题。

怎么做:保持上面的模板写法即可。请求成功后,UP_ITEMS 把 items 替换成新数组,列表自动渲染。

易错点:$store 不要写成 $store.state.state.items;item.full_name 要和接口字段一致,写错不会报错但页面空白,所以转写稿强调先 console.log 看结构。key 不要漏,否则列表更新时可能复用异常。

本节边界:未使用 actions 时,异步逻辑留在组件 ​

为什么需要:明确本节为什么没有 actions。转写稿说 mutations 是同步操作,异步请求若硬塞进 mutation 会破坏“先请求、后提交”的边界。

怎么想:当前规模小,组件挂载时请求一次,成功后 commit,已经能完成“异步获取数据列表并完成渲染”。等需要复用请求、统一管理异步流程时,再引入 actions;本节不展开。

怎么做:按上面 mounted + axios + commit 的链路执行。调试时可在控制台查看 $store.state.items 是否已变成接口数组。

易错点:不要把 axios.get 写进 mutations 里;也不要在组件里直接改 state。转写稿的核心结论是:获取走 $store.state,修改走 this.$store.commit 调用 mutations 中的方法。

一、为什么把异步请求放进 actions ​

为什么需要: 组件里直接发请求再改 state,会破坏 Vuex 的集中式管理。转写稿复习时反复强调:修改数据状态要走正规途径,不能绕过仓库随手改。异步请求属于“行为”,应该放进 actions,请求成功后再通过 commit 调 mutations 改状态。

怎么想: state 存数据,mutations 做同步更新,actions 承接异步行为。actions 里的函数可通过 $store.dispatch('action名', payload) 调用;函数第一个参数是 context,常用 commit 调 mutation,第二个参数是荷载 payload。

怎么做: 在 src/store/index.js 中定义 state.items、mutations.UP_ITEMS、actions.getItems。getItems({commit}, q) 用 axios.get 请求 GitHub,拿到 data.items 后 commit("UP_ITEMS", data.items)。

易错点: 不要在 action 里直接写 state.items = ...;不要忘记 import axios;GitHub 返回的是 data.items,不是整个 data;mutation 名建议全大写,和 commit 里的名字一致。

js
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
Vue.use(Vuex);
// 数据状态
const state = {
	items:[]
};
// 同步更新状态方法
const mutations = {
	UP_ITEMS(state,items){
		state.items = items;
	}
};
// 计算属性
const getters = {

};
// 异步行为:
// actions是一个对象,对象的属性名即是action的名字,类型是一个函数,该函数可以通过$store.dispatch('action的名字',payload)调用。
const actions = {
	// getItems是action的名字
	// 第一个参数一般命名为context, 是一个对象,该对象中拥有store中常用的方法和属性
	// context->state在此处可以获取,但不建议修改。
	// dispatch:调用actions下的方法
	// commit:调用mutations下的方法
	// state:获取state下的数据
	// getters:获取getters中定义的计算属性。
	
	// 第二个参数一般命名为payload,荷载数据。
	// getItems(context,payload,b,c,d){
	// 	console.log("getItems",context.state,payload,b,c,d)
	// }
	
	async getItems({commit},q){
		// 获取数据
		const {data} = await axios.get("https://api.github.com/search/repositories?q="+q+"&sort=stars");
		// 更新数据状态
		commit("UP_ITEMS",data.items);
	}
}
const store = new Vuex.Store({
	state,
	mutations,
	getters,
	actions
});
export default store;

二、在组件中映射并触发 action ​

为什么需要: 组件里如果每次都写 this.$store.dispatch("getItems", payload) 会比较重复,mapActions 可以把 action 映射成组件方法。

怎么想: mapActions 和 methods 结合使用;数组写法要求方法名与 action 名一致,对象写法可以给方法起别名。笔记里两种写法都保留:...mapActions(["getItems"]) 和 ...mapActions({ one:"getItems" })。

怎么做: 在 mounted 中触发。未简写是 this.$store.dispatch("getItems","react");简写是 this.getItems("react");起别名后是 this.one("react")。三者最终都是调用 getItems,并把 "react" 作为 q 传给 action。

易错点: mapActions 放在 methods,不要放 computed;数组写法的方法名必须和 action 名对应;对象写法 one 是组件方法名,值 "getItems" 才是 action 名;mounted 里调用哪个映射名,就执行哪个方法。

vue
<template>
    <div>
        <div v-for="item in $store.state.items" :key="item.id">
            {{item.full_name}}
        </div>
    </div>
</template>

<script>
// methods结合使用的是:mapMutations,mapActions
// computed结合使用的是:mapState,mapGetters
import {mapActions} from "vuex";
export default {
    name: "App.vue",
    methods:{
        ...mapActions(["getItems"]),
        // 相当于:
        // ...{
        //     getItems(payload){
        //         this.$store.dispatch("getItems",payload)
        //     }
        // }

        ...mapActions({
            one:"getItems"
        }),
        // 相当于:
        // ...{
        //     one(payload){
        //         this.$store.dispatch("getItems",payload)
        //     }
        // }
    },
    mounted(){
        // 第一个参数是action的名字。作用:调用store->index.js->actions->getItems,传递参数react
        // 1- 未简写
        // this.$store.dispatch("getItems","react");
        // 2- 简写
        // this.getItems("react");
        // 3- 简写
        this.one("react");
    }
}
</script>

<style scoped>
/*app*/
.todo-container {
    width: 600px;
    margin: 0 auto;
}
.todo-container .todo-wrap {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
}
</style>

三、渲染与调试过程 ​

为什么需要: 请求成功后必须把 state.items 渲染出来,才能验证 action、mutation、state 是否串起来。

怎么想: 模板里用 v-for="item in $store.state.items",key 用 item.id,显示 item.full_name。转写稿的调试习惯是先看控制台/DevTools,再看视图是否变化;本节可以先确认 mounted 是否触发 dispatch,再看 Network 是否请求 GitHub,最后看 Vue Devtools 中 state.items 是否从空数组变成列表。

怎么做: 刷新页面,若列表出现说明链路通了;若为空,先检查 q 是否传入、URL 拼接是否正确、commit("UP_ITEMS", data.items) 是否执行。

易错点: 不要用数组下标当 key;不要忘记 return 或 await 导致数据未取到;getters 为空不影响本节;如果 action 名或 mutation 名拼错,控制台会提示,按名字逐项核对。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

课堂画面 6

代码示例 ​

示例一:异步获取数据列表并渲染(未使用 actions) ​

js
// src/store/index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);
// 数据状态
const state = {
	items: []
};
const mutations = {
	UP_ITEMS(state, items) {
		state.items = items;
	}
};
const getters = {

};
const store = new Vuex.Store({
	state,
	mutations,
	getters
});
export default store;
vue
<!-- src/App.vue -->
<template>
    <div>
        <div v-for="item in $store.state.items" :key="item.id">
            {{item.full_name}}
        </div>
    </div>
</template>

<script>
import axios from "axios";
export default {
    name: "App.vue",
    mounted(){
        axios.get("https://api.github.com/search/repositories?q=r&sort=stars").then(value=>{
            console.log(value.data.items);
            this.$store.commit("UP_ITEMS",value.data.items);
        })
    }
}
</script>

<style scoped>
/*app*/
.todo-container {
    width: 600px;
    margin: 0 auto;
}
.todo-container .todo-wrap {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
}
</style>

要点与易错点:

  • Vue.use(Vuex) 必须先执行,否则 new Vuex.Store 会报“Vuex 未定义”。
  • this.$store.commit("UP_ITEMS", value.data.items):第一个参数是 mutations 里的方法名(类型),第二个参数是荷载数据 payload,只接收这一个 payload,多传无效。
  • 模板里直接写 $store.state.items 可读性差、同质化严重,后面可用 mapState 简化。
  • v-for 必须带 :key="item.id",否则渲染列表会有警告。
  • 此写法把异步请求逻辑放在组件里,数据请求分散,不适合多组件复用——这正是需要 actions 的原因。

示例二:异步获取数据列表并渲染(使用 actions) ​

js
// src/store/index.js
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
Vue.use(Vuex);
// 数据状态
const state = {
	items: []
};
// 同步更新状态方法
const mutations = {
	UP_ITEMS(state, items) {
		state.items = items;
	}
};
// 计算属性
const getters = {

};
// 异步行为:
// actions 是一个对象,对象的属性名即是 action 的名字,类型是一个函数,
// 该函数可以通过 $store.dispatch('action的名字', payload) 调用。
const actions = {
	// 第一个参数一般命名为 context,是一个对象,拥有 store 中常用的方法和属性
	// context -> state:获取 state 中的数据(不建议在此直接修改)
	// context -> dispatch:调用 actions 下的方法
	// context -> commit:调用 mutations 下的方法
	// context -> getters:获取 getters 中定义的计算属性
	// 第二个参数一般命名为 payload,荷载数据
	async getItems({commit}, q){
		// 获取数据
		const {data} = await axios.get("https://api.github.com/search/repositories?q="+q+"&sort=stars");
		// 更新数据状态
		commit("UP_ITEMS",data.items);
	}
}
const store = new Vuex.Store({
	state,
	mutations,
	getters,
	actions
});
export default store;
vue
<!-- src/App.vue -->
<template>
    <div>
        <div v-for="item in $store.state.items" :key="item.id">
            {{item.full_name}}
        </div>
    </div>
</template>

<script>
// methods 结合使用的是:mapMutations, mapActions
// computed 结合使用的是:mapState, mapGetters
import {mapActions} from "vuex";
export default {
    name: "App.vue",
    methods:{
        ...mapActions(["getItems"]),
        // 相当于:
        // ...{
        //     getItems(payload){
        //         this.$store.dispatch("getItems",payload)
        //     }
        // }

        ...mapActions({
            one:"getItems"
        }),
        // 相当于:
        // ...{
        //     one(payload){
        //         this.$store.dispatch("getItems",payload)
        //     }
        // }
    },
    mounted(){
        // 第一个参数是 action 的名字,作用是调用 store->index.js->actions->getItems,并传递参数 react
        // 1- 未简写
        // this.$store.dispatch("getItems","react");
        // 2- 简写
        // this.getItems("react");
        // 3- 简写
        this.one("react");
    }
}
</script>

<style scoped>
/*app*/
.todo-container {
    width: 600px;
    margin: 0 auto;
}
.todo-container .todo-wrap {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
}
</style>

要点与易错点:

  • 异步请求必须写在 actions 中,mutations 里只能写同步操作;最终改状态仍要由 commit("UP_ITEMS", data.items) 完成。
  • async getItems({commit}, q) 对 context 做了对象解构,只取 commit;若写成 getItems(context, q),则内部要用 context.commit。
  • $store.dispatch("getItems","react") 第一个参数是 action 名,第二个参数是 payload,与 commit 的用法一一对应。
  • mapActions 必须展开(...)到 methods 中;写成数组时方法名与 action 名一致,写成对象时属性名可自定义(如 one),值必须对应 store 中的 action 名。
  • ...mapActions([...]) 和 ...mapActions({...}) 同时存在时,会得到 getItems 和 one 两个方法,不要误以为只有一个。
  • 不要在组件里直接 this.$store.state.items = ... 修改状态,会绕过 Vuex 的集中式管理,视图与数据可能不一致。

常见错误 ​

现象原因修法
列表能渲染,但 DevTools 里 state 不记录更新,严格模式下还报错在组件里直接改 this.$store.state.items只通过 commit("UP_ITEMS", data.items) 修改
组件里写 axios.get(...).then(...commit...),多个组件重复请求、逻辑难复用异步逻辑散落在组件,未放入 actions把请求放进 actions.getItems({commit}, q),组件只 dispatch 或 mapActions
调用 action 后页面没数据,控制台也无明显报错action 里拿到 data.items 后忘记 commit,或 mutation 名大小写不一致检查 commit("UP_ITEMS", data.items) 与 mutations.UP_ITEMS 完全一致
this.getItems is not a function 或映射后方法为 undefinedmapActions 没放在 methods 中展开,或对象映射别名写错methods:{ ...mapActions(["getItems"]) };别名用 ...mapActions({ one: "getItems" }),调用 this.one("react")
action 收到的参数是 undefined 或结构不对dispatch 第二个参数只能传一个 payload,多个参数未包成对象dispatch("getItems", { q, page }),action 中按 payload.q 或解构使用
请求失败后 loading 不消失、组件无法等待结果action 没 return Promise,或没有 try/catch/finallyreturn 请求 Promise;组件可 await this.getItems(...);在 finally 收尾状态

练习 ​

  1. 把“未使用 actions”的 App.vue 改造成 actions 版本:组件内不再出现 axios,只保留 dispatch 或 mapActions 调用。
  2. 给 getItems 增加 loading 和 error 两个状态:请求开始/成功/失败时分别更新,并在模板中显示加载与错误提示。
  3. 增加一个搜索输入框和按钮:点击按钮时把输入关键词传给 getItems,并处理空关键词不请求。
  4. 使用 mapActions 的对象写法给 getItems 起别名 searchRepos,在 mounted 和按钮点击中分别调用一次,确认不会重复请求。
  5. 增加一个 getter 返回当前列表数量,在页面显示“共 N 条”,并验证列表更新后数量同步变化。

原视频对照 ​

#视频时长
1复习15:40
2操作数据状态-mutations25:22
3操作数据状态-mutations简写02:22
4操作数据状态-mutations原理11:42
5计算属性getters15:46
6组件拆分06:02
7完成添加09:27

小结 ​

  1. 组件里出现 axios + commit 的组合,基本应视为异步逻辑需要下沉到 actions 的信号。
  2. 组件只负责触发 action 和展示 state/getters;请求、数据加工、提交 mutation 放到 store 中更稳定。
  3. mutation 只做同步状态赋值,异步请求不要写进 mutation。
  4. action 名、mutation 名、映射名要统一命名习惯,避免大小写和别名导致的隐性问题。
  5. mapActions 用于 methods,调用时记得加 this;对象映射适合解决命名冲突。
  6. action 返回 Promise 后,组件才能可靠地 await、收尾 loading、处理失败提示。
  7. 列表渲染的 key 用稳定唯一值,异步数据替换后要检查 key 是否导致复用异常。
  8. 请求类页面至少要把 loading、error、空列表三种界面状态纳入设计。