主题
ref与v-for结合使用与ref与组件结合使用(Lesson5 3/3)
本页对应 Lesson05,共 6 段视频,约 59 分钟。
学习目标
- 能够使用
ref获取表单输入值,并在v-for列表与组件场景中通过ref访问对应实例完成数据收集或方法调用。 - 能够将微博案例拆分为列表、发布、分页等组件,并正确完成组件注册、嵌套与父子传参。
- 能够调用接口获取微博列表,将数据渲染到页面,并在新增成功后通过事件总线或状态提升触发列表刷新。
- 能够实现分页展示、页码高亮与页码切换,并在顶、踩、删除后刷新当前页数据。
- 能够按接口文档完成添加、顶踩、删除请求,并根据响应结果进行成功提示、失败中断与列表更新。
知识点
本节要解决的问题
这一节只围绕一个核心:ref 在普通元素上和 v-for 列表上到底怎么取。笔记里的示例很短,但包含两个容易混淆的点:同名 ref 会覆盖,ref 与 v-for 结合会得到数组。转写稿里虽然大量在讲微博案例的组件拆分、添加、分页、顶踩和删除,但其中也用到了 ref 取输入框内容,并暴露了“取不到值要先检查 $refs 和 value”的调试过程。下面把这两条线合起来讲清楚。
普通元素上的 ref:为什么需要、怎么想、怎么做
为什么需要:Vue 推荐数据驱动视图,但有些场景需要直接拿到 DOM,例如取输入框内容、读取元素文本、做聚焦等。ref 就是模板里给元素或组件起一个引用名,然后在实例里通过 this.$refs.名字 访问。
怎么想:先给元素写 ref="my",再在方法里打印 this.$refs.my。如果页面上有多个同名 ref,Vue 不会给你一个集合,而是后出现的覆盖前面的,所以最终拿到的是最后一个。
怎么做:看笔记中的两个 div:
html
<div ref="my">1</div>
<div ref="my">2</div>在 fn 里如果执行 console.log(this.$refs.my),得到的是第二个 div,它的 innerText 是 2。所以笔记注释写“如果设置 ref 的值相同,那么后者会覆盖前者”。
易错点:不要以为同名 ref 会自动变成数组;只有 v-for 场景才会得到数组。取不到时先检查是否漏写 this.$refs、名字是否拼错、方法是否在 DOM 渲染后触发。
ref 与 v-for 结合:为什么得到数组、怎么取
为什么需要:列表渲染时,每个 p 都是同一类元素,如果给它们同一个 ref,业务上往往需要拿到这一组元素,而不是最后一个。Vue 对 v-for 上的 ref 做了特殊处理,会收集成数组。
怎么想:模板里写 ref="myP" v-for="item in arr" :key="item",那么 this.$refs.myP 就是数组,数组顺序与渲染顺序一致。要取第一个元素,就写 this.$refs.myP[0]。
怎么做:笔记中的实际输出是:
js
console.log(this.$refs.myP[0].innerText)因为 arr 是 [1,2,3,4],第一个 p 的文本是 1,所以控制台输出 1。
易错点:v-for 的 ref 不要当单个元素用;数组下标从 0 开始;key 要写稳定值,笔记里用 :key="item";列表更新后 $refs.myP 可能变化,不要在数据还没渲染时就去取。
完整示例:保留笔记代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="app">
<button @click="fn">你过来呀!</button>
<div ref="my">1</div>
<div ref="my">2</div>
<hr/>
<p ref="myP" v-for="item in arr" :key="item">{{item}}</p>
</div>
</body>
<script>
// 注意:如果设置ref的值相同,那么后者会覆盖前者。
// 注意:如果ref与v-for结合使用,那么获取到的是数组。
new Vue({
el:"#app",
data:{
arr:[1,2,3,4]
},
methods:{
fn(){
// console.log(this.$refs.my)
console.log(this.$refs.myP[0].innerText)
}
}
})
</script>
</html>用转写稿补一个真实调试过程:ref 取输入框
转写稿里添加微博时,需要收集输入框内容。原话大意是“给 input 增加一个 ref,叫 contentRef,然后通过 this.$refs.contentRef.value 收集内容”。这里纠正同音字:REFREM 是 ref,扩到REO/contREF 是 contentRef,dollarREFS 是 $refs,Y 是 value。
为什么需要:输入框的值不在 data 里时,可以用 ref 直接读 DOM 的 value。
怎么想:先确认输入框已经渲染,再在点击事件里取 this.$refs.contentRef.value,然后判空。转写稿里判空写的是“长度等于 0 就提示请输入微博”。
怎么做:
js
const content = this.$refs.contentRef.value
if (content.length === 0) {
alert('请输入微博')
return
}易错点:漏写 .value 会拿到元素本身;在 mounted 之前取可能拿不到;发送请求时如果接口要求 JSON,请求体要按接口文档传对象并设置对应类型,不要传错格式。转写稿还提到:删除或顶踩后要按“当前页”重新获取列表,否则容易回到第一页。这也是 ref 调试之外的常见联动问题。
小结:先打印,再取值
遇到 ref 问题,最稳的调试顺序是:先 console.log(this.$refs) 看有哪些引用;再打印具体名字,看它是元素还是数组;最后取 innerText 或 value。记住两句话:同名普通 ref 后者覆盖前者;v-for 上的 ref 得到数组。
本节目标:ref 与组件结合使用
为什么需要:父组件有时需要读取子组件内部状态,或者触发子组件里的数据变化。只靠 props 只能“父传子”,子组件自己的 data 父组件默认拿不到。ref 提供了一种直接拿到组件实例的方式。
怎么想:先给子组件在模板上打一个 ref 标记,父组件挂载完成后通过 this.$refs.标记名 找到这个子组件实例,再访问它的 data 或方法。
怎么做:笔记中的核心写法是 <child ref="suibian"></child>,父组件方法里执行 this.$refs.suibian.num++。
易错点:$refs 不是响应式的,不要在模板里依赖它做渲染;组件还没挂载时取不到;ref 名字不要和已有 data、方法重名,避免混淆。
ref 加在普通元素和组件上,拿到的东西不同
为什么需要:同样叫 ref,加在 input 上和加在子组件上,结果不一样。不理解这一点,就容易把组件实例当 DOM 用。
怎么想:ref 是“引用标记”。加在普通标签上,$refs.xxx 得到 DOM 元素;加在组件标签上,得到组件实例。转写稿里的微博案例就用 ref 标记输入框,在方法里通过 this.$refs.xxx.value 收集输入内容,再判断是否为空、发请求。
怎么做:普通元素取 .value、.innerHTML 等;组件实例取 data 里的属性或组件方法。本节代码取的是子组件实例上的 num。
易错点:组件 ref 不能想当然地取 .value;在 v-for 里给组件加 ref,$refs 往往变成数组,要按数组处理;$refs 的值要在 mounted 之后访问才稳。
父组件操作子组件数据状态的推导
为什么需要:笔记注释写得很明确:“ref 与组件结合使用,获取到的是该组件实例。通过该特点可以实现父级操作子组件中的数据状态。”
怎么想:子组件 Child 的 data 返回 { num: 1 }。父组件点击 app 按钮时,如果能拿到 Child 实例,就能访问实例上的 num。this.$refs.suibian 正是这个实例,所以 this.$refs.suibian.num++ 改的就是子组件的数据。子组件模板里又绑定了 {{num}},数据一变,视图跟着更新。
怎么做:父组件按钮绑定 fn,fn 中执行自增;子组件自己的按钮也绑定 num++。两个按钮操作的是同一个 num。
易错点:这是演示 ref 能力,不是鼓励到处直接改子组件 data。正式项目更推荐 props 向下、事件向上,或者用事件总线处理兄弟组件通信。转写稿里添加微博后要让列表更新,用的就是事件总线发布“get 微博”消息,而不是靠 ref 跨兄弟组件硬改。
完整代码保留
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="app">
<button @click="fn">app</button>
<hr/>
<child ref="suibian"></child>
</div>
</body>
<script>
// ref与组件结合使用,获取到的是该组件实例。
// 通过该特点可以实现父级操作子组件中的数据状态。
new Vue({
el:"#app",
methods:{
fn(){
// console.log(this.$refs.suibian.num);
this.$refs.suibian.num++;
}
},
components:{
Child:{
data(){
return {
num:1
}
},
template:(`
<button @click="num++">{{num}}</button>
`)
}
}
})
</script>
</html>调试过程与转写稿补充
为什么需要:看懂代码不够,要知道出错时怎么查。转写稿里的微博案例反复调试了“收集输入—判断空—发请求—更新列表”的过程,和本节 ref 机制是同一条线。
怎么想:先确认 $refs 里有没有目标。可以在 fn 里先 console.log(this.$refs.suibian),确认拿到的是组件实例而不是 DOM;再打印 this.$refs.suibian.num 看数据。转写稿中也先输出收集到的内容,再判断长度是否等于 0,为空就提示“请输入微博”。
怎么做:点击父按钮,观察子按钮数字是否变化;点击子按钮,观察父按钮是否还能继续改同一个数字。若报错“Cannot read property 'num' of undefined”,优先检查 ref 名字是否写错、组件是否已挂载。
易错点:不要一开始就在 created 里取组件 ref;不要在 ref 名字上随意改大小写;直接修改子组件状态后,如果发现视图没更新,先确认改的是不是响应式 data,而不是普通变量。转写稿里分页当前页可能大于总页数,服务端会纠正到最后一页;删除后重新获取当前页也可能被服务端纠正。这类“状态要以接口返回为准”的思路,也适用于理解组件状态不要乱猜。
先启动环境:数据库、API 与依赖
这个案例“麻雀虽小,五脏俱全”,增删改查、组件拆分、兄弟组件通信、分页都能练到。为什么先启动环境?因为页面上的添加、渲染、删除、顶踩、分页都要调接口,接口背后还要读写 MongoDB;环境没起来,后面所有功能都只能“看代码猜结果”。
怎么想:按依赖顺序启动——先数据库,再 API,最后页面。怎么做:本节笔记给出的启动步骤如下,先原样保留:
bash
# 启动数据库
mongod --dbpath e:\mongo
# 进入 api 目录安装依赖
cnpm i
# 启动 api
node server.js注意 --dbpath 后面的目录必须真实存在;转写稿里反复强调“这个地址得有”。如果本机把 MongoDB 服务禁用了,就手动执行 mongod,看到光标持续闪烁说明挂载成功;再开一个终端用 mongo 进入数据库环境。API 目录安装依赖时用 cnpm i(npm/yarn 也可,以本机为准),启动 server.js 后接口端口按转写稿是 8090,并已处理跨域。易错点:不要在 API 目录里随便删依赖或重复下载整套资料;转写稿明确提醒“有 node_modules 就不要乱下这个 API”。启动后先按接口文档确认添加、获取、删除、修改的参数和返回格式,再写前端。
拆分组件:先让结构跑起来
为什么拆?上方“发微博/添加”和下方“列表”是兄弟关系,分页又适合独立成组件;不拆的话,添加成功后很难通知列表更新。怎么想:先按页面区域拆成“添加组件”“列表组件”“分页组件”,再处理通信。怎么做:在组件目录新建添加组件和列表组件,把原页面对应 DOM 剪切进去;模板较长时把 template 单独提出来,组件里 export default { template, data, methods... }。父组件引入并注册:
js
import TakeComment from './components/TakeComment.vue'
import CommentList from './components/CommentList.vue'
export default {
components: { TakeComment, CommentList }
}易错点:组件名、引入路径、注册名要一致;剪切 DOM 后别忘了把依赖的样式和图片一起复制过来,否则页面“啥也看不到”。先保证拆分后页面能显示,再写功能。
添加微博:ref 收集、接口调用与失败中断
为什么用 ref?输入框内容需要被按钮事件读取。怎么想:按钮点击 → 取输入值 → 校验空值 → 按接口文档发请求 → 判断成功/失败 → 成功后清空输入并通知列表刷新。怎么做:给输入框加 ref="contentRef",按钮加 @click="addWeibo";方法里取 this.$refs.contentRef.value,为空提示“请输入微博”。请求按文档发 POST,请求体为 JSON;响应里用 ok===1 判断成功,失败用 msg 提示。转写稿还提到失败时用 new Promise(() => {}) 让后续代码不再执行,这是“中断流程”的做法。易错点:XHR 不要直接把对象塞进 send,要按封装/文档序列化并设置 Content-Type: application/json;成功判断不要只看 HTTP 状态,还要看业务字段 ok。数据库可用 db.集合名.find() 验证是否真的写入。
渲染与兄弟通信:事件总线
为什么添加成功后下面不更新?因为列表组件没有重新获取数据。怎么想:列表在 mounted 获取一次;添加、顶踩、删除成功后都要再获取。兄弟组件通信用事件总线:在入口给 Vue.prototype.$bus = new Vue();列表组件 mounted 里 this.$bus.$on('get微博', this.getWeibo);添加组件成功后 this.$bus.$emit('get微博')。怎么做:列表数据放 contentList: [],接口返回后赋值 this.contentList = res.data.contentList;用 v-for 渲染,v-if="contentList.length===0" 显示“暂无留言”。易错点:订阅名和发布名必须一致;列表组件销毁时最好 $off,避免重复订阅。
分页:props、高亮与页码来源
为什么分页不能前端自己算当前页?转写稿调试发现:你传 20 页,但后端总共只有 3 页,后端会把页码纠正到最后一页。怎么想:总页数和当前页应以接口返回为准;分页组件通过 props 接收 pageCount、pageNum。怎么做:页码从 1 开始 v-for,高亮写 :class="{ active: i === pageNum }";点击页码发布事件 this.$bus.$emit('get微博', i),列表组件接收页码调用获取接口。易错点:顶踩/删除后重新获取时要传“当前页”,不要默认第一页,否则用户在第 3 页操作后会被拉回第 1 页;删除后当前页可能变空,仍以服务端返回为准。
顶踩与删除:改完仍留在当前页
顶踩按文档是修改接口,传唯一标识 _id 和类型(1 顶、2 踩),成功后重新获取当前页。删除按文档把 id 拼在地址里,请求方式为 DELETE;点击删除传 item._id,删除成功后仍获取当前页。转写稿调试点很关键:顶踩后回到第一页,是因为刷新时没把当前页码传进去;删除报错则要检查请求方法/封装是否写错。易错点:_id 是唯一标识,别写成 id;删除和顶踩都要“先调接口,成功后再刷新列表”,失败则按接口返回提示处理。
课堂画面






代码示例
示例一:ref 与 v-for 结合使用
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ref与v-for结合使用</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="app">
<button @click="fn">你过来呀!</button>
<div ref="my">1</div>
<div ref="my">2</div>
<hr/>
<p ref="myP" v-for="item in arr" :key="item">{{item}}</p>
</div>
</body>
<script>
// 注意:如果设置ref的值相同,那么后者会覆盖前者。
// 注意:如果ref与v-for结合使用,那么获取到的是数组。
new Vue({
el: "#app",
data: {
arr: [1, 2, 3, 4]
},
methods: {
fn() {
// console.log(this.$refs.my) // 只拿到第 2 个 div
// console.log(this.$refs.myP) // 拿到的是一个数组
console.log(this.$refs.myP[0].innerText)
}
}
})
</script>
</html>要点解释
- 两个
div的ref都写成my,this.$refs.my只会拿到最后一个,前面的被覆盖 —— 这是最容易踩的坑,同名ref不要用在并列元素上。 ref写在v-for的元素上,this.$refs.myP得到的是数组,顺序和渲染顺序一致,用下标[0]取第一个。$refs不是响应式的,只在渲染完成后才有值;在created里访问是undefined,至少放到mounted或点击事件里。:key必须绑定,否则列表更新时 DOM 复用,$refs数组顺序可能和arr对不上。
示例二:ref 与组件结合使用
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ref与组件结合使用</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="app">
<button @click="fn">app</button>
<hr/>
<child ref="suibian"></child>
</div>
</body>
<script>
// ref与组件结合使用,获取到的是该组件实例。
// 通过该特点可以实现父级操作子组件中的数据状态。
new Vue({
el: "#app",
methods: {
fn() {
// console.log(this.$refs.suibian.num);
this.$refs.suibian.num++; // 父组件直接改子组件的数据
}
},
components: {
Child: {
data() {
return {
num: 1
}
},
template: `
<button @click="num++">{{num}}</button>
`
}
}
})
</script>
</html>要点解释
ref写在自定义组件标签上,this.$refs.suibian拿到的是组件实例,不是 DOM 元素;可以直接.num访问子组件的data、.fn()调子组件的方法。- 父点
app按钮和子组件自己点按钮,改的是同一份num,因为拿到的就是同一个实例。 - 易错点:
ref写在组件上就不能再用innerText、style这类 DOM 属性了,要操作 DOM 得写this.$refs.suibian.$el。 - 这只是"能改",属于父子通信的兜底手段;正式项目更推荐
props/$emit。本节转写稿里"添加完成后让下面的列表刷新"用的就是另一种方案 —— 事件总线(见示例三)。
示例三:ref 收集输入 + 事件总线让兄弟组件刷新(转写稿微博案例简化版)
转写稿中列表数据来自
axios请求接口(get微博/post微博),这里用本地数组代替接口,其余结构(ref取值、校验、$bus通知)与课上完全一致。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>微博案例:ref + 事件总线</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="app">
<weibo-input></weibo-input>
<hr/>
<weibo-list></weibo-list>
</div>
</body>
<script>
// 模拟数据库里的数据(真实项目里是 axios 请求接口拿回来的)
let db = [
{ _id: 1, content: '第一条微博' },
{ _id: 2, content: '第二条微博' }
];
// 事件总线:兄弟组件通信
Vue.prototype.$bus = new Vue();
// 上面的组件:输入 + 提交
Vue.component('weiboInput', {
template: `
<div>
<input ref="contentRef" placeholder="请输入微博内容">
<button @click="postWeibo">提交评论</button>
</div>
`,
methods: {
postWeibo() {
// 用 ref 拿到输入框的值(转写稿:this.$refs.contentRef.value)
let content = this.$refs.contentRef.value;
if (content.length < 1) {
alert('请输入微博内容');
return; // 校验不通过直接中断
}
db.push({ _id: Date.now(), content: content });
// 清空输入框
this.$refs.contentRef.value = '';
// 发布消息,通知兄弟组件重新获取数据
this.$bus.$emit('get微博');
}
}
});
// 下面的组件:列表
Vue.component('weiboList', {
template: `
<div>
<p v-if="list.length === 0">暂无留言</p>
<ul>
<li v-for="item in list" :key="item._id">{{ item.content }}</li>
</ul>
</div>
`,
data() {
return { list: [] }
},
mounted() {
// 订阅:只要有人发 'get微博',就重新取一次数据
this.$bus.$on('get微博', this.getWeibo);
// 打开页面先取一次
this.getWeibo();
},
beforeDestroy() {
// 一定要解绑,否则组件销毁后订阅还在,会重复触发
this.$bus.$off('get微博', this.getWeibo);
},
methods: {
getWeibo() {
this.list = db.slice(); // 真实项目:this.$axios.get(url).then(...)
}
}
});
new Vue({ el: '#app' });
</script>
</html>要点解释
ref拿到的输入框值要在点击时取,不能在data里写死;取值用this.$refs.contentRef.value,少写.value就是拿到整个 DOM 元素。- 校验用
content.length < 1,return必须写,转写稿里就是用这个思路挡住"空内容提交"。 - 兄弟组件(输入组件、列表组件)之间没有父子关系,用
Vue.prototype.$bus = new Vue()造一个全局事件总线;发布$emit、订阅$on,两个名字必须一字不差(课上叫get微博)。 - 易错点一:
$on写在mounted里,且beforeDestroy中要$off,否则切换页面后消息被处理多次。 - 易错点二:转写稿里顶/踩、删除之后都要带上当前页码重新
getWeibo(pageNum),否则默认回第 1 页;这里同理,$emit之后列表重新取数即可。
常见错误
两个普通元素写同一个
ref="my",只拿到最后一个- 现象:
this.$refs.my是单个 DOM,this.$refs.my.innerText永远是 "2",第一个 div 拿不到。 - 原因:非
v-for场景下ref是"键值对"语义,同名键后者覆盖前者,不会自动变数组。 - 修法:改成唯一名字(
ref="a"/ref="b");确实要批量取就套上v-for(此时才是数组),或用this.$el.querySelectorAll。
- 现象:
v-for里的ref数组拿到null或长度对不上- 现象:
this.$refs.myP[2].innerText报Cannot read property 'innerText' of undefined。 - 原因:
ref数组按渲染顺序收集,循环项里有v-if未通过时该项不渲染,数组长度小于arr.length;或在mounted之前访问。 - 修法:用
this.$refs.myP.filter(Boolean)过滤;访问时机放到mounted/this.$nextTick之后。
- 现象:
axios 传对象与传字符串,后端收到的内容不一样
- 现象:
post(url, { content })后端能拿到;改成post(url, content)后后端req.body.content为undefined。 - 原因:第二个参数是对象时默认
Content-Type: application/json;是字符串时退化为application/x-www-form-urlencoded,字段名丢了,服务端body-parser解析路径不同。 - 修法:统一传对象并在拦截器里固定 header,或显式
headers: {'Content-Type': 'application/json'}且传JSON.stringify(...)。
- 现象:
加了响应拦截器
return res.data后,业务里仍写res.data.ok- 现象:
TypeError: Cannot read properties of undefined (reading 'ok'),成功失败判断永远走不进去。 - 原因:拦截器已经把响应体剥了一层,
then里收到的就是业务数据本身。 - 修法:全项目统一约定返回层级——要么拦截器只
return response,要么业务里直接res.ok;拦截器内判断失败时return Promise.reject(...)中断后续流程,别让失败的代码继续往下跑。
- 现象:
兄弟组件:添加成功后列表不刷新
- 现象:接口返回成功、数据库已有数据,但下面的列表还是旧的,必须手动 F5。
- 原因:添加在 A 组件、列表在 B 组件,A 改了数据 B 无从得知,
mounted只跑一次。 - 修法:事件总线(
$emit/$on同名事件)或状态提升到公共父级;$on必须在beforeDestroy里$off同名事件,否则来回切组件后一次添加会触发多次请求。
删除 / 顶踩后页码跳回第一页
- 现象:在第 4 页点"顶",列表刷成了第 1 页的数据,但页码高亮还在 4。
- 原因:刷新函数形参没传,
pageNum = pageNum || 1兜底成了 1。 - 修法:刷新时显式传当前页
this.get微博(this.pageNum);删除后再判断"当前页 > 总页数"则把页码减一,同时把页码回传给分页组件,别让服务端纠正的页码和前端状态脱节。
练习
- 给微博案例的输入框加一个
ref,页面mounted后自动聚焦;再写一个"清空"按钮,清空后重新聚焦。要求说明为什么用ref而不是querySelector。 - 分页组件目前把
pageNum当普通数据用。请改成"父传props+ 子$emit('change', n)"的单向数据流,并说明改造后"点击页码高亮不更新"的问题为什么消失。 - 在
comment组件里删除第 4 页最后一条数据,观察请求的页码与返回内容。写出你的页码修正逻辑(含"删空后显示暂无留言"的边界)。 - 给事件总线补上完整生命周期:列出
$on注册与$off解绑分别写在哪两个钩子、为什么。写一个能复现"添加一次触发三次请求"的步骤。 - 用
ref+v-for实现:点击任意一条微博内容,把它content变成可编辑的input(同一时刻只允许一条处于编辑态)。要求只用一个ref名,并说明如何正确取到当前被点击那一条的 DOM。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 组件拆分 | 14:49 |
| 2 | 获取数据 | 14:56 |
| 3 | 完成添加与渲染 | 11:49 |
| 4 | 分页 | 11:11 |
| 5 | 顶与踩 | 04:14 |
| 6 | 删除 | 02:49 |
小结
ref在普通元素上是"单个",在v-for上才是"数组",同名覆盖是设计行为,不是 bug。ref拿到的是真实 DOM / 组件实例,属于"逃生舱":能不用就不用,能绑事件、能绑v-model的地方优先用数据驱动。- 跨组件通信先想清楚关系:父子走
props/$emit,兄弟走事件总线或状态提升;总线一定要配$off,否则是内存泄漏 + 重复请求双杀。 - axios 实例按"服务器"维度创建,
baseURL集中管理;Content-Type与传参类型强相关,接口文档怎么写就怎么传。 - 响应拦截器是"统一出口",剥层级和失败中断都放这里,业务代码里不要再做二次判断。
- 所有"改数据后要刷新"的操作,都必须把当前页码一起带上,否则默认值会把用户送回第一页。
- 分页是前端状态与服务端状态的对账:前端页码可能不准(超过总页数会被服务端纠正),所以页码以服务端返回的为准回写。
- 本地跑不起来先别改代码:
mongod --dbpath→cnpm i→node server.js,三步顺序错一步就是Network Error。