主题
订单支付与问诊室通知(三)
本页对应 订单支付与问诊室通知,共 7 段视频,约 54 分钟。
学习目标
- 能够说明 socket.io 与 WebSocket 的关系,并完成 socket.io-client 的安装与模块导入。
- 能够使用
io()建立客户端连接,通过on监听服务端事件、通过emit向服务端发送消息并验证通信结果。 - 能够根据前后端约定自定义事件名称,完成客户端与服务端之间带参数的消息收发与联调。
- 能够创建问诊室页面模板并配置分包路由,在支付成功后携带
orderId、token等参数建立问诊室 WebSocket 连接。 - 能够监听历史消息事件,解析并保存消息列表数据,按
messageType区分文字、图片、患者信息、温馨提示、处方、评价等消息并选择对应模板渲染。
知识点
一、先明确:socket.io 客户端是干什么的
为什么需要:原生 WebSocket 能通信,但事件命名、断线兼容、参数传递等都要自己处理。socket.io 基于 WebSocket 做了二次封装,客户端用 on 收消息、用 emit 发消息,语义清楚。
怎么想:把“客户端”和“服务端”想成两个能互相触发事件的对象。客户端触发服务端监听的事件,服务端也能触发客户端监听的事件。谁发消息谁用 emit,谁收消息谁用 on。
怎么做:先记住两个核心方法:
js
socket.on('事件名', (data) => {}) // 接收数据
socket.emit('事件名', data) // 发送数据易错点:转写稿里的“昂”就是 on,“一米特/emit”就是 emit;不要把 on 和 emit 的职责记反。事件名不是固定死的,但前后端必须约定一致。
二、安装与导入:客户端包别装错
为什么需要:项目里要用模块方式引入 socket.io,不能一直靠 CDN。装错包会导致导入时报“找不到 socket.io-client”。
怎么想:socket.io 同时有服务端包和客户端包。浏览器端要装的是带 client 的包,导入时也要带 client。
怎么做:
bash
npm install socket.io-clientjs
import { io } from 'socket.io-client'转写稿中曾先装错,后来重新安装带 client 的包才解决。调试时看报错信息:如果提示找不到 socket.io-client,先检查安装名和导入名。
易错点:socket.io 更偏服务端;socket.io-client 才是客户端。安装和导入都要带 client,不要只凭编辑器提示随手选。
三、建立连接:io(地址, 配置)
为什么需要:真正开发时不能写死本地地址,要使用后端提供的接口地址,并携带登录和订单参数。
怎么想:io 是一个函数,第一个参数是后端地址,第二个参数是配置。问诊室必须带 token,因为患者登录后才能聊天;还要带 orderId,后端要知道你已支付。
怎么做:按转写稿整理连接代码:
js
const socket = io(接口地址, {
auth: { token },
query: { orderId },
transports: ['websocket', 'polling'],
timeout: 5000
})其中 token 从 pinia 的 user store 取,orderId 从地址参数取。transports 表示优先 WebSocket,不支持时用长轮询;timeout 是连接超时设置。验证时打开浏览器开发者工具的 WS 面板,看到连接信息即建立成功。
易错点:token、orderId 要先准备好再传;转写稿口述的“长轮巡”应写作“长轮询”。服务端代码修改后要 Ctrl+C 两次停掉,再重新运行,并刷新浏览器。
四、收发消息与自定义事件
为什么需要:连接只是通道,真正业务是互相发消息。客户端要能触发服务端事件,也要监听服务端事件。
怎么想:客户端 emit('send to server', 数据),服务端就 on('send to server', ...) 接收;服务端 emit('send to client', 数据),客户端就 on('send to client', ...) 接收。自定义事件同理,比如前后端约定 abc。
怎么做:
js
socket.emit('send to server', '来自客户端的数据')
socket.on('send to client', (data) => {
console.log(data)
})
socket.emit('abc', { msg: 'A111' })服务端对应监听:
js
socket.on('abc', (msg) => {
console.log(msg)
})易错点:客户端触发了事件,服务端没监听就是“白触发”;事件名、参数结构要按接口文档来。转写稿里的 C log 即 console.log。
五、接入问诊室:历史消息与消息类型
为什么需要:建立连接后,服务端会返回历史消息;聊天列表里文字、图片、温馨提示、患者信息、处方、评价等布局不同,不能用一个模板硬套。
怎么想:历史消息用 on 接收,事件名按接口文档约定为 chat message list。拿到 result 后先判断 code,成功再把 data 存入 messageList,供页面渲染。
怎么做:
js
socket.on('chat message list', (result) => {
const { code, data } = result
if (code !== 1) return
messageList.value = data
})消息类型按转写稿梳理:1 文字消息,4 图片消息,21 患者信息,22 处方消息,23 评价消息,31 普通消息,32 温馨提示。不同 msgType 对应不同模板,建议一个类型拆一个组件。
易错点:chat message list 不要写成转写稿口述的“Tide message list”;msgType 不要写成“MSDt”;31 不要听成“30亿”。先看接口文档和 WS 返回数据全貌,再按类型渲染。
安装与导入:先分清服务端包和客户端包
为什么需要:客户端要发送消息,先得把 socket.io 的客户端库装进项目。转写稿里一开始装错了包,后来重新装 socket.io-client,这是一个很典型的坑:socket.io 更多是服务端用的,浏览器端要引的是带 client 的那个。
怎么想:先判断当前代码跑在哪一端。静态页面可以用 CDN 引入;Vue 项目里按普通模块导入更合适。导入时看包名,必须确认是 socket.io-client,不要只看“socket.io”几个字。
怎么做:
bash
npm install socket.io-clientjs
import { io } from 'socket.io-client'易错点:装错包、导错包都会报“找不到模块”之类的错误。转写稿里就是先装了不带 client 的包,导入 socket.io-client 时找不到,重新安装带 client 的包后才正常。另一个易错点是:服务端代码和客户端代码不是同一个包,不要混用。
建立连接:io 函数拿到 socket 实例
为什么需要:emit 不能凭空发消息,必须先有和服务端的连接。连接建立后,客户端才能触发事件、监听事件。
怎么想:把 socket.io 想成“客户端和服务端之间的通道”。io(地址, 配置) 负责建立通道,返回一个 socket 实例;后续发消息、收消息都围绕这个实例做。地址不是随便写的,开发时可以用本地地址,真正联调时要让后端提供接口地址;参数也要按接口文档传,比如登录 token、订单 orderId。
怎么做:
js
const socket = io('后端提供的地址', {
auth: {
token
},
query: {
orderId
},
transports: ['websocket', 'polling'],
timeout: 5000
})转写稿里解释过:transports 是兼容处理,优先用 WebSocket,不支持时用长轮询;timeout 是连接超时设置。orderId 可以从地址参数里取,token 从 Pinia 的用户信息里取。注意转写稿里提到:解构出来的 token 没有响应式,但这里不改它,所以问题不大;如果要响应式,需要按 ref 的方式加 .value。
易错点:地址写错、参数名写错、token 没取到,都会导致连接失败。验证时打开浏览器开发者工具的“网络/WS”,看连接是否建立、请求里有没有带上参数。
用 emit 发送消息:客户端触发服务端事件
为什么需要:建立连接只是“能说话”,真正聊天要客户端主动把消息发出去。socket.io 里客户端发送数据靠 emit。
怎么想:记住两个词:on 用来监听、接收;emit 用来触发、发送。客户端 emit 的事件,是服务端要监听的事件;事件名不是固定死的,而是前后端约定好的。转写稿里用 send to server 举例:客户端触发服务端的事件,并携带“来自客户端的数据”。
怎么做:
js
socket.emit('send to server', '来自客户端的数据')自定义事件也一样:
js
socket.emit('ABC', { msg: 'A111' })服务端要对应监听:
js
socket.on('ABC', (msg) => {
console.log(msg)
})转写稿里强调:如果服务端没有监听这个事件,客户端触发就是“白触发”。修改服务端代码后要重启服务:Ctrl + C 两次停掉,再按向上箭头调出刚才的命令重新运行;浏览器端也要刷新。
易错点:事件名前后端不一致;只写了 emit 但服务端没写 on;改了服务端忘记重启;浏览器没刷新。发送后要看服务端控制台有没有打印,才能确认数据真的到了。
用 on 接收消息与调试:服务端发、客户端收
为什么需要:聊天不是单向的。客户端发出去后,服务端可能回消息,也可能主动推送历史消息列表。客户端要接收,就得用 on 监听约定事件。
怎么想:把“客户端”和“服务端”想成父子组件通信的双方:一方 emit 触发事件,另一方 on 监听事件。服务端 emit('send to client', 数据),客户端就 on('send to client', 回调) 接收;服务端推送历史消息时,客户端监听 chat message list。
怎么做:
js
socket.on('chat message list', (result) => {
console.log(result)
})转写稿里先打印 result 看数据全貌,再按业务取 code、data,判断 code !== 1 就 return,成功则把数据保存到 messageList,供页面渲染。后续列表里会有不同类型,如 msgType 为 21 是患者信息、31 是普通消息、32 是温馨提示等,需要按类型展示不同模板。
易错点:on 的事件名写错;把“发送”和“接收”弄反;拿到数据不判断 code 直接渲染;服务端推送了但客户端没监听。调试时先看控制台打印,再看网络 WS 里的请求和数据,确认连接与事件都正常。
一、先明确:socket.io 的自定义事件是什么
为什么需要:原生 WebSocket 只提供底层连接,业务里“发消息、收消息、开处方、结束问诊”都需要自己约定消息格式。socket.io 在 WebSocket 基础上做了二次封装,提供 on 和 emit,让前后端用“事件名 + 数据”通信。
怎么想:把客户端和服务端想成父子组件:emit 是触发事件,on 是监听事件。事件名不是固定的,由前后端约定;约定后不能随意改。
怎么做:客户端监听服务端事件用 socket.on('事件名', 回调);客户端给服务端发数据用 socket.emit('事件名', 数据)。服务端同理:socket.on 接收,socket.emit 发送。
易错点:emit 第二个参数才是数据;事件名前后端必须完全一致;服务端没监听时,客户端触发就是“白触发”。
js
// 客户端触发服务端事件
socket.emit('ABC', { data: 'A111' })
// 服务端监听客户端事件
socket.on('ABC', (msg) => {
console.log(msg)
})二、客户端连接:io() 拿到 socket 实例
为什么需要:只有先建立连接,后面的 on、emit 才有意义。
怎么想:socket.io-client 暴露 io 函数,传入后端地址和配置,返回 socket 实例。实例上有 on、emit。
怎么做:安装客户端包,注意不是服务端包:
bash
npm install socket.io-clientjs
import { io } from 'socket.io-client'
const socket = io('后端接口地址', {
auth: { token },
query: { orderId },
transports: ['websocket', 'polling'],
timeout: 5000
})易错点:转写里安装时曾装错,报“确定这个存在吗”,原因是装成了服务端包;客户端要带 client。transports 表示优先 WebSocket,不支持时用长轮询;timeout 是连接超时。orderId 从地址参数取,token 从 Pinia 用户 store 取;解构 token 当前只读,若要响应式需用 value。
三、接收与发送:on 接数据,emit 发数据
为什么需要:聊天室既要收历史消息,也要发新消息。
怎么想:记住一句话:on 用来接收数据,emit 用来发送数据。
怎么做:先监听后端约定的事件名,例如历史消息 chat message list:
js
socket.on('chat message list', (result) => {
console.log(result)
})先打印全貌,确认结构后再解构核心数据;异常就 return,成功则存入 ref 消息列表。
易错点:不要自己改事件名;转写中口述的“Tide message list”应以后端文档的 chat message list 为准。调试时打开浏览器 WS 面板,看到连接成功和返回数据,再写渲染。
四、自定义事件联调:客户端 emit,服务端 on
为什么需要:验证“自定义事件”不是写死 API,而是前后端约定。
怎么想:客户端触发 ABC,服务端必须监听 ABC;服务端改代码后要重启。
怎么做:服务端示意:
js
io.on('connection', (socket) => {
socket.on('ABC', (msg) => {
console.log(msg)
})
})修改服务端后,终端 Ctrl+C 两次停止,再按上箭头重跑;浏览器刷新后,在控制台执行 socket.emit('ABC', { data: 'A111' })。
易错点:服务端没重启,旧代码仍在跑;浏览器没刷新,旧连接仍存在;事件名大小写、空格不一致。
五、反向自定义事件:服务端 emit,客户端 on
为什么需要:医生回复、开处方、结束问诊,都是服务端主动推给客户端。
怎么想:服务端 emit 触发的是客户端事件,客户端必须提前 on 监听。
怎么做:服务端 socket.emit('send to client', data),客户端 socket.on('send to client', (data) => { ... })。转写中服务端随机返回“你好 / 我在写代码 / 快下课了”,客户端能收到,说明双向事件打通。
易错点:客户端监听写在连接之后;回调参数别漏;服务端推送的数据结构要按接口文档处理。
六、消息列表:一个列表,多种模板
为什么需要:问诊室列表里不只有文字,还有患者信息、温馨提示、图片、处方、评价等。
怎么想:用 msgType 区分模板,例如 21 患者信息、31 普通消息、32 温馨提示、22 处方、23 评价。一个类型一个组件,加载时按类型渲染。
怎么做:先 console.log(result) 看数据全貌,再按 msgType 拆分组件。不要把所有布局硬塞进一个模板。
易错点:不要把“列表布局都一样”的经验套过来;类型值要以后端返回为准,转写中的“MSEtype”应纠正为 msgType。
代码示例
示例一:Node 服务端(socket.io server)
js
// server.js —— 终端执行:node server.js(先 npm i socket.io)
const http = require('http')
const { Server } = require('socket.io')
const httpServer = http.createServer()
// 把 http 服务交给 socket.io,得到 io 实例(名字随意起)
const io = new Server(httpServer, { cors: { origin: '*' } })
const msgs = ['你好', '我在写代码', '快下课了']
// on('connection'):当有客户端连接建立好之后触发
io.on('connection', (socket) => {
console.log('有客户端建立连接了')
// 服务端 emit:给客户端发数据,客户端用 on('sendToClient') 接收
socket.emit('sendToClient', msgs[Math.floor(Math.random() * msgs.length)])
// 客户端 emit('sendToServer', 数据) 时,这里被触发
socket.on('sendToServer', (data) => {
console.log('收到客户端的数据:', data)
// 收到后随机回一条
socket.emit('sendToClient', msgs[Math.floor(Math.random() * msgs.length)])
})
// 自定义事件 ABC,客户端 emit('ABC', { a: 111, b: 222 })
socket.on('ABC', (msg) => {
console.log('收到 ABC 事件的数据:', msg)
})
})
httpServer.listen(3000, () => {
console.log('socket.io 服务已启动:http://localhost:3000')
})关键行与易错点
new Server(httpServer)必须把 http 服务传进去,否则只有 http 没有 ws 能力;io.on('connection', ...)是“连接建立好之后”的入口,所有socket.on都写在这个回调里。- 服务端发数据用
socket.emit,收数据用socket.on,事件名(sendToClient/sendToServer/ABC)全是前后端自己约定的,不是固定死的,但两边必须写成同一个字符串。 - 服务端代码每改一次都要重启:
Ctrl + C停掉,按方向键上翻出上条命令回车重跑;不重启你会发现“客户端触发了但服务端没反应”。
示例二:原生 HTML 客户端(CDN 引入 + 收发消息)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>socket.io 客户端</title>
<!-- CDN 引入的是客户端包 socket.io.min.js -->
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
</head>
<body>
<button id="send">给服务端发消息</button>
<button id="abc">触发自定义事件 ABC</button>
<ul id="list"></ul>
<script>
// io() 是库提供的全局函数:第一个参数是地址,第二个参数是配置(可省略)
const socket = io('http://localhost:3000')
socket.on('connect', () => {
console.log('建立连接了')
})
// on:监听事件,用来接收数据
socket.on('sendToClient', (data) => {
console.log('收到来自服务器的数据:', data)
const li = document.createElement('li')
li.textContent = data
document.getElementById('list').appendChild(li)
})
// emit:触发事件,用来发送数据(事件名 + 数据)
document.getElementById('send').onclick = () => {
socket.emit('sendToServer', '快睡觉吧')
}
document.getElementById('abc').onclick = () => {
socket.emit('ABC', { a: 111, b: 222 })
}
</script>
</body>
</html>关键行与易错点
- 记死两句话:on 用来接收数据,emit 用来发送数据;
emit第一个参数是事件名,第二个参数才是数据(和 Vue 组件里$emit('事件名', 数据)完全一个套路)。 - 客户端
socket.emit('sendToServer', ...)触发的是服务端的事件,所以服务端必须有对应的socket.on('sendToServer'),否则就是“白触发”。 - 先启动服务端(
node server.js,光标停住不动说明服务器正在运行),再打开/刷新页面;打开页面后控制台出现“建立连接”,就说明浏览器和服务器已经架好桥了。
示例三:Vue 项目里建立连接 + 接收历史消息(问诊室)
js
// 安装:npm i socket.io-client ← 包名带 client,别装成服务端的 socket.io
import { io } from 'socket.io-client'
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import { useUserStore } from '@/stores/user'
const route = useRoute()
const userStore = useUserStore()
const { token } = userStore // 解构后没有响应式,这里只读不改,够用
// 地址栏参数:支付成功跳回来时自动带上 orderId
const orderId = route.query.orderId
const messageList = ref([])
// 建立连接:地址 + 一堆参数(参数以接口文档为准)
const socket = io('后端提供的接口地址', {
auth: { token }, // 登录信息,接口文档要求传 token
query: { orderId }, // query 传参,类似地址栏 ?orderId=xxx
transports: ['websocket', 'polling'], // 优先 websocket,不支持则长轮询
timeout: 5000 // 5 秒连不上就放弃
})
// on 接收服务端推过来的数据,事件名由后端接口文档规定,不能随便改
socket.on('chat message list', (result) => {
const { code, data } = result
if (code !== 1) return // 数据有问题直接返回
messageList.value = data // ref 必须用 .value 赋值
})关键行与易错点
- 装包容易翻车:导入时写
import { io } from 'socket.io-client',安装时也必须是socket.io-client,装成socket.io会报“找不到模块”。 io()第一个参数是后端给的真实接口地址(不是本地测试的localhost:3000),第二个参数要照着接口文档传:auth放 token、query放orderId——没交钱没订单,后端不会让你聊。transports: ['websocket', 'polling']是 socket.io 的兼容处理:支持 WebSocket 就用 WebSocket,不支持就退回长轮询。- 事件名(如
chat message list)是前后端约定好的,改了前端就收不到数据;验证是否连上,可到浏览器 Network 面板的 WS 里看有没有连接信息。 - 用
ref存的列表赋值时不要忘了.value;const { token } = userStore这种解构不具备响应式,只读场景可以,要跟着变必须用userStore.token。
常见错误
1. 装错包,导入即报错
- 现象:
import { io } from 'socket.io-client'报"找不到模块 / 无法解析",控制台提示确定该依赖存在吗。 - 原因:执行了
npm install socket.io,装的是服务端包;客户端要的是socket.io-client。转写稿里讲师本人也踩了这个坑。 - 修法:删掉重装
npm i socket.io-client,导入路径与安装包名必须一致(带-client)。
2. 服务端改了代码,客户端触发事件毫无反应
- 现象:前端
emit出去了,服务端console.log不打印;或新加的on('abc')一直不触发。 - 原因:Node 进程不会热更新,改完
.js没重启,跑的还是旧代码;同时浏览器页面也没刷新,旧连接还挂着。 - 修法:终端
Ctrl + C(两次)停掉 → 上箭头调回上条命令重跑 → 浏览器刷新页面重连。
3. 事件名前后端对不上,emit 白触发
- 现象:客户端
emit('sendToServer'),服务端on('send-to-server'),两边都"正常执行"但谁也收不到谁。 - 原因:事件名是自定义字符串,不做任何校验;接口文档常写成
chat-message-list这种带连字符的形式,直接照抄进on()就错了。另外服务端压根没写on监听时,客户端触发同样无效。 - 修法:以接口文档为准逐字对齐(文档里的连字符写法对应代码里的事件名要去掉
-,如chatMessageList);先确认服务端已有on监听,再写客户端emit。
4. on / emit 方向写反
- 现象:页面收不到消息;或想发消息却写成了
socket.on('sendToServer', data),回调永远不执行。 - 原因:把"监听"和"触发"混为一谈。
on= 接收数据(监听事件),emit= 发送数据(触发事件),且emit触发的是对方那边的事件。 - 修法:按"谁发谁 emit,谁收谁 on"自查一遍:客户端发 → 客户端 emit + 服务端 on;服务端发 → 服务端 emit + 客户端 on。
5. 连接参数缺失,连上了但拿不到数据
- 现象:WS 面板里能看到连接,但
chatMessageList不回调;或直接被后端拒绝。 - 原因:
io(url, { auth, query, transports, timeout })里的auth.token、query.orderId是后端校验身份和订单的必传项,漏传就查不到会话。 - 修法:对照接口文档补齐
auth与query;orderId从路由参数取(注意地址参数都是字符串),token从 pinia 取——直接解构 store 会丢响应式,后续要用就用store.token或storeToRefs。
6. 一个列表用一套模板渲染所有消息
- 现象:患者信息、温馨提示、图片、处方、评价全挤成同一个白底黑字气泡,样式全乱。
- 原因:问诊室列表的
msgType有 9 种(1 文字、4 图片、21 患者信息、22 处方、23 评价、31 普通消息、32 温馨提示……),不是"一个列表一种布局"的常规场景。 - 修法:按
msgType拆成独立组件,父级用条件渲染/动态组件分发;接收数据时先判code !== 1直接 return,再把data存进ref([])的messageList。
练习
最小可跑通闭环:起一个 Node 服务端(
on('connection')里socket.emit('welcome', ...)),客户端io('http://localhost:3000'),用on收欢迎语、用emit发一条自定义事件abc带对象数据,服务端console.log打出来。要求:全程在浏览器 Network → WS 面板里找到收发帧。造一次"白触发"并修复:故意把服务端
on('abc')改名为on('abcd')而不动客户端,观察现象;然后只改客户端或只改服务端各修一次,说明为什么"两边都跑起来了却谁也收不到"。连接参数拼装:在问诊室页面中,从
useRoute()取orderId、从 pinia 取token,组装成io(url, { auth: { token }, query: { orderId }, transports: ['websocket', 'polling'], timeout: 5000 })。要求说明transports数组两项的优先级含义,以及timeout超时后会发生什么。历史消息接入:监听后端约定的事件(如
chatMessageList),回调里打印完整result看清数据结构,再解构出code与data,判code !== 1提前 return,成功则存入messageList(初始为ref([]))。在控制台验证三条初始数据的msgType分别是 21、31、32。类型分发设计:不写具体样式,只写出
messageList的渲染骨架——按msgType映射到 5 个以上不同子组件的方案(v-if链 / 动态组件:is/ 映射表三种任选其一),并说明为什么这个列表不能沿用患者列表"一套模板循环"的写法。
课堂画面






原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | socket.io客户端介绍 | 06:26 |
| 2 | socket.io客户端发送消息 | 09:13 |
| 3 | socket.io自定义事件 | 07:16 |
| 4 | 优医咨询-问诊室页面模板 | 05:36 |
| 5 | 优医咨询-问诊室建立websocket连接 | 12:19 |
| 6 | 优医咨询-问诊室历史消息 | 06:02 |
| 7 | 优医咨询-消息类型说明 | 07:55 |
小结
- 客户端只装
socket.io-client、只导socket.io-client;socket.io是服务端包,两个名字差一个后缀,装错就是编译期报错。 - 一个
io(url, options)得到一个实例,实例上只需记牢两件事:on收数据、emit发数据;emit触发的是对方那边注册的事件。 - 事件名完全自定义、无编译期校验,前后端必须逐字对齐,接口文档是唯一权威;服务端没写
on,客户端emit就是空放。 - 服务端 Node 代码不热更新,每次改动都要
Ctrl + C重启并刷新浏览器,否则你调试的是旧逻辑。 auth(token)与query(orderId)是后端鉴权和定位会话的必传项,地址参数一律是字符串;store 解构会丢响应式,要用就保留store.xxx写法。transports: ['websocket', 'polling']体现 socket.io 的兼容策略:优先 WebSocket,不支持则降级长轮询;timeout是连接建立的兜底。- 问诊室是"一个列表、多种模板"的典型场景,
msgType(1/4/21/22/23/31/32…)决定渲染哪套布局,必须按类型拆组件,而不是复用同一套列表模板。 - 收数据的固定套路:
on→ 打印全貌看清结构 → 解构code/data→ 判code !== 1提前 return → 存入ref数组 → 交给模板按类型分发。