跳到主要内容位置
Hello! 我是潜心专研的小张同学

在这里,有技术博客、以及UP主的想法和生活点滴。授人以鱼不如授人以渔,分享个人所学所见,在你学习前端开发的路上助你一臂之力!

QQ 1 群:706947563

去B站关注

最新博客 

Vue3中的slot插槽知识总结,看这一篇就够了!

Vue 中的 slot 是一种用于组件内容分发的技术。它允许父组件向子组件传递任意内容,根据插槽的位置显示在任意位置,从而使组件更加灵活和可组合。

作用: 增加了组件的复用性,同时也可以让代码结构更清晰,利于团队协作和组件库的开发。

1 默认插槽#

最基本的插槽类型。当使用子组件时,没有指定具体名字的插槽的内容就会被渲染到默认插槽位置。

例子:

<!-- BaseCard.vue -->
<template>
<div class="card">
<header class="card-header">
<h3>卡片标题</h3>
</header>
<section class="card-body">
<!-- 默认插槽 -->
<slot></slot>
</section>
<footer class="card-footer">卡片底部信息</footer>
</div>
</template>
<script>
export default {
name: 'BaseCard'
}
</script>
<!-- 使用 BaseCard 的父组件 -->
<template>
<BaseCard>
<p>这是一段使用默认插槽填充的内容。</p>
</BaseCard>
</template>
<script>
import BaseCard from './BaseCard.vue'
export default {
components: { BaseCard }
}
</script>

2 具名插槽#

为不同的插槽区域使用 name 属性命名,父组件在传递内容时可以使用<template #插槽的名字>... </template><template v-slot:插槽的名字>...</template>来指定父组件传递的内容显示在子组件的位置。

例子:

<!-- ProfileCard.vue -->
<template>
<div class="profile-card">
<div class="profile-avatar">
<slot name="avatar">
<!-- 可选:默认头像 -->
<img src="default-avatar.png" alt="默认头像" />
</slot>
</div>
<div class="profile-info">
<slot name="info">
<!-- 可选:默认信息 -->
<p>暂无详细信息</p>
</slot>
</div>
</div>
</template>
<script>
export default {
name: 'ProfileCard'
}
</script>
<!-- 使用 ProfileCard 的父组件 -->
<template>
<ProfileCard>
<template v-slot:avatar>
<img src="user-avatar.jpg" alt="用户头像" />
</template>
<template v-slot:info>
<h2>张三</h2>
<p>前端开发工程师,热爱分享技术。</p>
</template>
</ProfileCard>
</template>
<script>
import ProfileCard from './ProfileCard.vue'
export default {
components: { ProfileCard }
}
</script>

3 条件插槽#

根据判断条件渲染不同插槽,比如v-if...v-else

例子:

<!-- StatusCard.vue -->
<template>
<div class="status-card">
<!-- 默认插槽作为 fallback 方案 -->
<slot></slot>
</div>
</template>
<script>
export default {
name: 'StatusCard'
}
</script>
<!-- 使用 StatusCard 的父组件 -->
<template>
<StatusCard>
<template v-if="isLoggedIn">
<p>欢迎回来,{{ username }}!</p>
</template>
<template v-else>
<p>请登录以继续操作。</p>
</template>
</StatusCard>
</template>
<script>
import StatusCard from './StatusCard.vue'
export default {
components: { StatusCard },
data() {
return {
isLoggedIn: false,
username: '张三'
}
}
}
</script>

4 动态插槽#

可以理解为具名插槽的升级吧,只是这个插槽的名字是一个变量,会变。通过这个插槽名字的变化,他就能在子组件的固定位置动态显示父组件传过来的不同插槽内容。

例子:

假设你有一个 DynamicTab 组件,根据选中的标签动态展示内容

<!-- DynamicTab.vue -->
<template>
<div class="dynamic-tab">
<div class="tab-header">
<button v-for="(tab, index) in tabs" :key="index" @click="activeTab = tab.name">
{{ tab.label }}
</button>
</div>
<div class="tab-content">
<!-- 动态插槽,根据 activeTab 来决定显示哪个 slot -->
<slot :name="activeTab"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'DynamicTab',
data() {
return {
activeTab: 'tab1',
tabs: [
{ name: 'tab1', label: '标签 1' },
{ name: 'tab2', label: '标签 2' },
{ name: 'tab3', label: '标签 3' }
]
}
}
}
</script>
<style scoped>
.tab-header button {
margin-right: 8px;
}
.tab-content {
border: 1px solid #ccc;
padding: 16px;
margin-top: 8px;
}
</style>

在父组件中使用时,通过 v-slot 将每个标签对应的内容关联到正确的名称上:

<!-- 使用 DynamicTab 的父组件 -->
<template>
<DynamicTab>
<template v-slot:tab1>
<p>这里是标签 1 的内容。</p>
</template>
<template v-slot:tab2>
<p>这里是标签 2 的内容。</p>
</template>
<template v-slot:tab3>
<p>这里是标签 3 的内容。</p>
</template>
</DynamicTab>
</template>
<script>
import DynamicTab from './DynamicTab.vue'
export default {
components: { DynamicTab }
}
</script>

5 作用域插槽#

作用域插槽(scoped slot)允许子组件向插槽内容传递数据或方法,从而让父组件在使用插槽时能获得子组件的局部状态或计算结果。 也就是说父组件里面使用子组件的插槽时,可以通过插槽获取到子组件传过来的值。

5.1. 常规用法#

例子:

假设我们有一个 ListDisplay 组件,用于展示一组列表数据,并将每个列表项的数据通过作用域插槽传递给父组件以便自定义渲染

<!-- ListDisplay.vue -->
<template>
<ul>
<li v-for="(item, index) in items" :key="index">
<!-- 将 item 作为 slotProps 传递出去 -->
<slot :item="item"></slot>
</li>
</ul>
</template>
<script>
export default {
name: 'ListDisplay',
props: {
items: {
type: Array,
default: () => []
}
}
}
</script>

在父组件中,我们通过作用域插槽接收子组件传来的 item 数据,并灵活地自定义列表项的展示形式:

<!-- 使用 ListDisplay 的父组件 -->
<template>
<ListDisplay :items="['苹果', '香蕉', '橘子']">
<template v-slot:default="slotProps">
<strong>{{ slotProps.item }}</strong> —— 好吃的水果!
</template>
</ListDisplay>
</template>
<script>
import ListDisplay from './ListDisplay.vue'
export default {
components: { ListDisplay }
}
</script>

5.2. 进阶用法:传递多个数据#

作用域插槽不仅可以传递单个数据,也可以传递多个数据或方法。例如,一个图表组件可能需要传递数值和格式化方法:

<!-- ChartDisplay.vue -->
<template>
<div class="chart">
<slot :data="chartData" :format="formatValue"></slot>
</div>
</template>
<script>
export default {
name: 'ChartDisplay',
data() {
return {
chartData: [10, 20, 30, 40]
}
},
methods: {
formatValue(value) {
return `${value} 单位`
}
}
}
</script>
<!-- 使用 ChartDisplay 的父组件 -->
<template>
<ChartDisplay>
<template v-slot:default="slotProps">
<ul>
<li v-for="(val, index) in slotProps.data" :key="index">
{{ slotProps.format(val) }}
</li>
</ul>
</template>
</ChartDisplay>
</template>
<script>
import ChartDisplay from './ChartDisplay.vue'
export default {
components: { ChartDisplay }
}
</script>

6. 举一反三:插槽混合使用#

下面这个是一个基于 Vue 3+ 组合式 API 语法编写的综合示例,包含默认插槽、具名插槽、条件插槽和作用域插槽。示例中定义了一个 UserCard 组件,以及一个父组件 ParentComponent,父组件在使用 UserCard 时灵活定制各个插槽的内容。

子组件 UserCard.vue:

这个组件是使用 vue3+组合式 API(通过 <script setup> 语法)来定义 props 和内部方法,把多种插槽结合在一起灵活使用,同时包含:

  • 具名插槽 **header**:可自定义头部信息,默认显示用户姓名;
  • 默认插槽:显示主体内容,默认展示用户年龄;
  • 条件插槽 **premium**:当 user.isPremiumtrue 时才展示高级会员区域(可被父组件覆盖);
  • 作用域插槽 **actions**:将用户数据传递到父组件,父组件可自定义操作按钮。、
<!-- UserCard.vue -->
<template>
<div class="user-card" style="border:1px solid #ccc; padding:16px; border-radius:4px;">
<!-- 具名插槽:header -->
<header style="margin-bottom:8px;">
<slot name="header">
<h2>{{ user.name }}</h2>
</slot>
</header>
<!-- 默认插槽:主体内容 -->
<div class="user-content" style="margin-bottom:8px;">
<slot>
<p>年龄:{{ user.age }}</p>
</slot>
</div>
<!-- 条件插槽:premium -->
<div v-if="user.isPremium" class="premium-section" style="margin-bottom:8px; color: darkorange;">
<slot name="premium">
<p>高级会员</p>
</slot>
</div>
<!-- 作用域插槽:actions,将 user 作为属性传递出去 -->
<footer>
<slot name="actions" :user="user">
<button @click="defaultAction">默认操作</button>
</slot>
</footer>
</div>
</template>
<script setup>
const props = defineProps({
user: {
type: Object,
required: true
}
})
function defaultAction() {
alert('默认操作被触发!')
}
</script>

父组件 ParentComponent.vue:

父组件使用组合式 API,通过 <script setup> 导入并使用 UserCard 组件,同时对各个插槽进行定制:

  • 为具名插槽 header 提供自定义头部显示;
  • 默认插槽中覆盖了组件内部提供的主体内容;
  • 条件插槽 premium 被父组件自定义显示高级会员信息;
  • 作用域插槽 actions 接收了传递的 user 数据,自定义一个操作按钮。
<!-- ParentComponent.vue -->
<template>
<UserCard :user="user">
<!-- 具名插槽:header 自定义头部显示 -->
<template v-slot:header>
<h2 style="color: teal;">自定义头部:{{ user.name }}</h2>
</template>
<!-- 默认插槽:自定义主体内容 -->
<p>这是 {{ user.name }} 的详细介绍,年龄:{{ user.age }},充满活力!</p>
<!-- 条件插槽:premium 自定义显示 -->
<template v-slot:premium>
<p style="font-weight: bold;">欢迎高级会员 {{ user.name }},尊享专属特权!</p>
</template>
<!-- 作用域插槽:actions,根据传入的 user 数据自定义操作按钮 -->
<template v-slot:actions="slotProps">
<button @click="handleAction(slotProps.user)" style="background-color: lightblue; padding: 4px 8px;">为 {{ slotProps.user.name }} 执行操作</button>
</template>
</UserCard>
</template>
<script setup>
import UserCard from './UserCard.vue'
import { reactive } from 'vue'
// 使用 reactive 定义用户数据
const user = reactive({
name: '张三',
age: 28,
isPremium: true // 修改为 false 可测试条件插槽不显示的情况
})
function handleAction(user) {
alert(`为 ${user.name} 执行了定制操作!`)
}
</script>

7 小结#

ok,vue 的所有插槽在上面已经总结完毕,可能在我们开发中默认插槽、具名插槽、条件插槽会用的多一些,复杂的组件封装时作用域插槽也会有所使用,动态插槽用的少些,可能是我使用的少。知识点就是上面这些,更多的是在我们日常开发中要灵活使用、融会贯通。

前端 SSE 流式接口请求处理实践教程

一、前言#

在现代 Web 应用中,实时数据更新越来越普遍。比如在聊天、股票行情、系统监控甚至 GPT 流式回复中,用户体验要求数据能边生成边展示。传统 HTTP 请求一次性返回所有数据,很难满足这种需求。而 SSE(Server-Sent Events,服务器发送事件)技术通过建立长连接,让服务器主动向客户端推送数据,完美实现了流式响应。

本文将详细讲解 SSE 的原理、前端如何使用原生 API(EventSource 或基于 fetch 的流处理)来接收流式数据,并给出完整的示例代码和注意事项。

二、SSE 概述#

SSE(Server-Sent Events)是一种基于 HTTP 协议的单向通信方式,允许服务器通过长连接不断推送文本数据到客户端。其特点包括:

  • 单向通信:只支持服务器向客户端推送消息,客户端如需向服务器发送数据,仍需使用普通 HTTP 请求。
  • 长连接机制:建立连接后,服务器保持通道不断开,直到明确结束或发生异常。
  • 自动重连:当连接中断时,浏览器会自动重连,保证数据实时性。
  • 简单易用:基于 HTTP 协议,无需引入额外的协议支持,兼容性较好(IE/Edge 除外)。

服务端需要在响应头中设置:

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

数据格式一般遵循如下规则,每条消息由若干行组成,消息之间用两个换行符分隔。例如:

data: 这是第一条消息
data: 多行数据可以这样发送
data: 第二条消息

三、前端实现原理#

前端接收 SSE 的核心 API 是浏览器内置的 EventSource 对象。使用时只需提供 SSE 接口 URL,然后监听 onmessageonopenonerror 等事件即可。

另外,由于 SSE 只支持 GET 请求,如果需要携带额外请求头或使用 POST 传参,可考虑使用 EventSourcePolyfill 或结合 fetch 实现流式处理(例如通过 TextDecoderStream 解析响应流)。

四、实战示例#

4.1 使用 EventSource 实现 SSE 流式数据接收#

这是最简单的使用方式。假设后端提供了一个 SSE 接口地址 http://example.com/sse

<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>SSE 流式接口示例</title>
</head>
<body>
<h1>实时数据</h1>
<div id="output"></div>
<script>
// 创建 EventSource 实例
const eventSource = new EventSource('http://example.com/sse')
// 连接建立时回调
eventSource.onopen = function (event) {
console.log('SSE 连接已建立')
}
// 接收消息时回调
eventSource.onmessage = function (event) {
console.log('收到消息:', event.data)
const output = document.getElementById('output')
output.innerHTML += event.data + '<br>'
}
// 连接错误时回调
eventSource.onerror = function (error) {
console.error('SSE 连接出错:', error)
// 出现错误后可以选择关闭连接
eventSource.close()
}
</script>
</body>
</html>

在这个示例中,前端建立了一个 SSE 长连接,服务器推送的数据会以事件的形式自动触发 onmessage 回调,数据实时显示在页面上。

4.2 使用 fetch 处理流式响应(扩展方案)#

部分场景下(例如需要携带 POST 参数),可以使用 fetch 发起请求,然后结合 Web Streams API 对响应进行流式处理。示例如下:

// 使用 fetch 请求 SSE 接口(注意:服务器需返回 text/event-stream 格式数据)
fetch('http://example.com/sse', {
method: 'GET',
headers: {
Accept: 'text/event-stream'
}
})
.then(response => {
if (!response.body) {
throw new Error('响应流为空')
}
const reader = response.body.getReader()
const decoder = new TextDecoder()
// 循环读取数据块
function read() {
reader
.read()
.then(({ done, value }) => {
if (done) {
console.log('数据读取完毕')
return
}
const chunk = decoder.decode(value, { stream: true })
console.log('收到流数据:', chunk)
// 这里可以对 chunk 进行分段处理,例如 split('\n\n')
document.getElementById('output').innerHTML += chunk.replace(/\n/g, '<br>') + '<br>'
read() // 继续读取下一块数据
})
.catch(error => {
console.error('读取流错误:', error)
})
}
read()
})
.catch(error => {
console.error('fetch 请求出错:', error)
})

这种方式灵活性更高,但需要自己解析数据流的格式(例如每条 SSE 消息通常以 data: 开头并以两个换行符结尾)。

4.3 结合 Ant Design X Vue 来使用#

Ant Design X Vue 是 Ant Design 团队专门为规范 AI 界面设计打造的一个组件库。

SSE 流式接口请求

// 建立请求连接
const response = await OllamaApiTest({
message: message,
});
const stream = XStream({
readableStream: response.body as ReadableStream<Uint8Array>,
});
const reader = stream.getReader();

AI 聊天界面接收信息流式输出

while (reader) {
const { value, done, timeoutFlag = false }: any = await Promise.race([reader.read(), timeoutPromise])
if (done) {
senderLoading.value = false
onSuccess(current)
break
}
if (timeoutFlag) {
// 超时处理
senderLoading.value = false
current.event_data.content = '请求超时,请重试'
onSuccess(current)
break
}
if (!value) continue
const data = JSON.parse(value.data)
if (data?.event_data?.content_type == 'Text') {
current.event_data.content = current.event_data.content + (data?.event_data?.content || '')
current.event_data.content_type = data?.event_data?.content_type || ''
current.message_id = data?.message_id || ''
current.message_status = data?.message_status || ''
}
// 聊天界面流式输出
onUpdate(current)
}

参考教程链接:

https://antd-design-x-vue.netlify.app/component/use-x-chat.html#%E6%B5%81%E5%BC%8F%E8%BE%93%E5%87%BA

https://antd-design-x-vue.netlify.app/component/x-stream.html#%E9%BB%98%E8%AE%A4%E5%8D%8F%E8%AE%AE-sse

五、常见问题与注意事项#

  1. 请求方法限制 SSE 原生只支持 GET 请求。如果需要发送 POST 请求或者携带自定义请求头,建议使用 EventSourcePolyfill 或者结合 fetch 实现流式响应处理。
  2. 自动重连 浏览器内置的 EventSource 会在连接中断后自动重连,但这也可能导致“重复连接”的问题。需要在 onerror 中根据实际情况判断是否手动关闭连接。
  3. 数据格式解析 服务器返回的数据格式必须符合 SSE 协议规范。若返回数据分块不完整,可使用字符串分割(例如通过 split('\n\n'))来确保每条消息完整解析。
  4. 跨域问题 如果前后端分离部署,注意配置 CORS,确保 SSE 请求允许跨域访问。

六、总结#

本文详细介绍了 SSE 的基本原理及其在前端的实现方式。

  • 通过 EventSource 建立长连接,前端能够实时监听服务器推送的事件。
  • 如果需要更灵活的处理(如 POST 请求、携带请求头),可以结合 fetch 与 Web Streams API 实现流式数据解析。

希望通过本教程,你能快速掌握 SSE 的前端实现技巧,打造更流畅的实时交互体验!

七、比较火的插件分享#

github 时下比较流行的库 https://github.com/trending?since=daily

使用AI自动写代码,DeepSeek+CLine+VSCode实战教程

0 前言#

最近 DeepSeek 火了起来,各个媒体都在宣传,那 DeekSeek 到底是个啥?从程序员角度来看:DeepSeek 一个人工智能助手,可以帮助我们解决代码知识点疑问,以及某个晦涩难懂的知识点问题,处理生活中的一些文本信息问题等。

其实这些能力早在数年前 ChatGPT 也已经具备了,那么为什么最近 DeepSeek 这么火呢?一个原因就是因为它是国产的,那必须得支持。还有一个原因就是它实惠呀,便宜!目测目前比 ChatGPT 要便宜了近 10 倍不止吧~ 咱能用得起了…… 在价格下降的同时,表现的能力也是越来越聪明。

下面主要是介绍 DeepSeek+CLine+VSCode 的实战教程,所表现出了的能力也是相当出色的,可以直接操控我们的编译器来写代码, 自动写代码!也就是说它除了回答我们一些专业问题之外,还可以直接对我们的文件目录及文件内容进行增删改查!

1 先上结果#

pEnSBqJ.png](https://imgse.com/i/pEnSBqJ)

2 注册使用 deepseek 模型的账号#

这里注册 deepseek 账号我们可以选择在其官网上注册使用,也可以选择在其他平台上注册使用。但是现在 deepseek 官网因为频繁受到某国不知名网络的攻击已经关闭了新的注册入口。

现在主要是来介绍在华为云上面的注册方法,使用的效果也是相当可以的,华为云也是相对稳定一些。

首先点击下面的链接可以注册个账号,现在注册可以免费获得 14 块钱的使用额度:

https://cloud.siliconflow.cn/i/10XZLZAC

3 创建秘钥#

我们注册好账号后,再来创建一个 API 秘钥,后面要用到

pEnStP0.png](https://imgse.com/i/pEnStP0)

4 vscode 安装 cline 及配置 deepseek 模型#

刚刚的账号及 API 秘钥弄好以后,我们来到 vscode

4.1 在 vscode 中安装 cline 插件#

安装一个 cline 插件,这个插件可以自动操作我们的 vscode 终端,配合 deepseek、chatgpt 这个 AI 助手来使用效果相当强劲

pEnSbJP.png

4.2 配置 deepseek 模型#

下载好 cline 插件后,我们点击 vscode 侧边栏的 cline 的界面,在里面配置 deepseek 模型,见图 4-2-1

pEnSqRf.png

(图 4-2-1)

图 4-2-1 里面 2.2 文字内容:

https://api.siliconflow.cn/v1

图 4-2-1 里面的 2.3 秘钥就是之前我们创建的那个秘钥

图 4-2-1 里面的 2.4 内容,我选的是 deepseek 最聪明的(最贵的)那个模型:

deepseek-ai/DeepSeek-R1

配置完以后我们带点击一下那个“Done”按钮,就会保存我们的配置。

5 开始使用 vscode+cline+deepseek 写代码#

刚刚我们都已经配置好了,现在开始使用

pEnSXQS.png

pEnSvLQ.png

ok,到这里已经配置完成啦~ 可以体验 AI 写代码的魅力了~

阿里云域名申请ssl证书,通过https方式访问域名

之前我们提到了如何创建子域名,并且已经创建了子域名,那么我们创建了域名以后如何更好升级域名的安全性呢?其实这些域名都是以 http 开头的,不是 https,这个时候我们就要来申请 ssl 证书,通过部署 ssl 证书将域名升级为以 https 的形式来访问。

1. 填写完对应申请的域名,然后点“提交审核”#

img

img

2. SSL 证书验证#

接下了我们要进行一下 ssl 证书验证,点击“验证按钮”

img

然后我们可以看到这个记录值,记录下来,这个要域名解析那边填写一下

img

3. 域名解析添加记录值#

我们来域名解析页面,点击“解析设置”按钮

img

然后点击“添加记录”按钮,填写我们刚刚记录的值,记住一定要完全一致哦,不要填多了(之前我就是主机记录那里字符填多了,导致域名好几填都没有验证通过。。)。

img

4. 再次验证 SSL 证书#

这个时候我们再次回到 ssl 证书管理页面,点击验证(一般 2、3 分钟就可以生效),这个时候就可以验证成功了。

img

5. 小结#

至此,SSL 证书申请的流程已经操作完毕了,只需要静静等待邮箱发送申请成功的邮件就可以了,一般大概 10 分钟左右吧。

阿里云创建子域名(二级域名),同时结合腾讯云

申请子域名基于我们已经有了主域名的情况,如果我们没有主域名先要去搞一个主域名。

那么我们为啥需要子域名了,那肯定是一个域名不够用,需要多个域名,不同的域名对应不同的网站,这样一个几十块的域名就能创建出几十个子域名(对应几十个网站),单从使用性价比方面还是不错的,hhhh

1. 进入域名解析界面,添加子域名#

进入域名解析界面,点击添加域名,我这里添加一个二级域名,然后我输入了二级域名,下面有提示要进行“TXT授权校验”,没事,那我们就去TXT授权校验。

img

2. 进行TXT授权校验#

点击这个链接

img

点击完出现这个信息,记一下,我们要去填到对应的地方

img

域名:zhangqiang.hk.cn
主机记录:alidnscheck
记录值:c350e166c50d4d6a8b2993efcd93430b

3. 添加记录值#

3.1. 腾讯云#

因为我的域名是从腾讯云转过来的,所以我需要也在腾讯云里面添加一下记录值(如果你都是用的阿里云的,那就不用管了),创建如下

img

添加完点击确认,这上面说是要24小时,但是只要我们配置正确的话5、6分钟就会生效了

img

3.2. 阿里云#

阿里云的域名解析里面也要添加记录值

img

4. 验证子域名,添加域名#

刚刚记录值我们都已经填完了,有延迟,需要等5分钟左右,再来验证,我这里是等了5分钟左右的,然后验证成功

img

img

5. 小结#

至此,阿里云创建子域名(二级域名)同时结合腾讯云完成,我们只要在服务器上面给这个域名部署相关的资源(比如网站资源、后台服务等)就可以访问到了。

解决 Vite 项目启动时端口重复问题的总结


背景#

在前端开发的江湖上,Vite 就像一个轻盈迅捷的剑客,以“快”和“爽”闻名。默认栖身于本地 3000 端口,简直是开发者的“专属包间”。但江湖人多,难免遇到这种情况:你刚起剑练招,发现隔壁的同门师兄已经占了你的包间。于是,你只能抱着代码,哼哼唧唧换个地方继续修炼——这种“被端口抢占”的故事,简直就是开发者的日常笑话!

例如:

  1. 多人协作:团队中的多个开发者同时运行了 Vite 项目,使用了相同的端口。
  2. 多项目运行:本地同时启动了多个 Vite 项目或其他服务(如 Webpack、Node.js 服务器),导致端口冲突。
  3. 默认配置不足:Vite 默认没有处理端口占用的逻辑,遇到冲突会直接报错,开发中断。

为了解决这个问题,我研究了几种方法,最终采用了 detect-port 插件,实现了动态检测和自动调整端口的功能,极大地提升了开发效率。


解决方案#

1. 常见解决方法#

1.1 *手动更改端口**#

vite.config.ts 文件中修改 server.port 的值。例如:

export default defineConfig({
server: {
port: 3001 // 手动指定一个新的端口
}
})

缺点:需要手动调整端口,效率较低,且不方便多人协作。

1.2 尝试端口范围#

一些开发者会通过运行脚本动态尝试多个端口,但编写脚本可能较繁琐且不直观。

1.3 使用第三方工具#

利用工具如 detect-portportfinder,自动检测端口是否被占用并返回可用端口。

2. Detect-port 的解决方案(本人的解决方法)#

我最终采用了 detect-port 插件,因为它简单易用,能够自动检测当前端口是否被占用,并返回一个可用的端口。

Step 1: 安装 detect-port#

在项目根目录下运行以下命令:

pnpm install detect-port --save-dev

或者使用 npm/yarn 安装:

npm install detect-port --save-dev
yarn add detect-port --dev

Step 2: 配置 Vite 项目#

修改 vite.config.ts 文件,引入 detect-port,实现动态端口检测:

import { defineConfig } from 'vite'
import detectPort from 'detect-port'
export default defineConfig(async () => {
const DEFAULT_PORT = 3000 // 默认端口
// 使用 detect-port 检测端口是否被占用
const port = await detectPort(DEFAULT_PORT)
console.log(`Selected port: ${port}`) // 输出实际选用的端口
return {
server: {
host: '0.0.0.0', // 允许局域网访问
port, // 动态端口
open: true, // 自动打开浏览器
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
}
})

Step 3: 效果展示#

  • 如果默认端口(如 3000)已被占用,detect-port 会自动检测下一个可用端口,例如 3001 或 3002。
  • 启动时会输出实际使用的端口号:
Selected port: 3001

这样,我们无需手动修改端口设置,提升了开发效率。lalala~

3. 优化点#

在实践中,我还添加了一些优化:

3.1 添加日志输出#

使用 console.log 记录端口信息,便于调试:

console.log(`Using port ${port}. To change, edit vite.config.ts`)

3.2 范围检测(可选)#

通过扩展 detect-port,可尝试在特定范围内寻找端口:

const port = await detectPort(3000)
if (port !== 3000) {
console.log(`Port 3000 is in use. Using port ${port} instead.`)
}

小结#

问题总结#

Vite 默认端口冲突的问题在多人协作和多项目运行的场景中非常常见。传统的手动解决方案需要频繁调整配置,显得麻烦且低效。

我的解决方案#

利用 detect-port 插件,可以轻松实现动态端口检测和分配:

  • 自动检测端口冲突。
  • 动态分配可用端口,提升开发效率。
  • 配置简单、易于扩展。

未来优化#

  • 扩展端口范围检测:可以尝试自定义检测端口范围(如 3000-3100)。
  • 集成其他工具:例如结合 vite-plugin-inspect,提供更多启动信息。
  • 多环境支持:为不同环境(开发、测试、生产)定制端口策略。

在团队开发中,这种自动化的方式能够显著减少端口冲突问题,提升协作效率,非常值得推广。

Vue3中watch中props监听加箭头函数与不加箭头函数的区别

1 前言#

今天写项目时,遇到一个问题,我需要打开点击一个按钮打开一个弹框页面,然后通过watch去监听传进来的值,但是呢,写了watch我点击按钮只有首次打开能够监听到,尽管加上了deep: true页面也不能监听到变化,点击效果如下图:

image-20240903172257604

2 代码实例#

直接上我的关键代码,这个是我弹框页面的代码:

<template>
<div class="content_container">1111</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, watch, onActivated } from 'vue'
import { ElMessageBox } from 'element-plus'
const props = defineProps(['currentRow'])
console.log('props', props)
watch(
props.currentRow,
(newVal, oldVal) => {
console.log('newVal', newVal)
},
{
immediate: true,
deep: true
}
)
</script>

解决后的代码:

<template>
<div class="content_container">1111</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, watch, onActivated } from 'vue'
import { ElMessageBox } from 'element-plus'
const props = defineProps(['currentRow'])
console.log('props', props)
watch(
() => props.currentRow,
(newVal, oldVal) => {
console.log('newVal', newVal)
},
{
immediate: true,
deep: true
}
)
</script>

其实,只是修改了一句话,多加了一个箭头函数~ 就可以了!他就可以每次进来就都监听了,为啥呢?请看下面的代码精读环节

3 代码精读#

3.1 直接监听 props 属性#

javascriptwatch(
props.currentRow,
(newVal, oldVal) => {
console.log('newVal', newVal);
},
{
immediate: true,
deep: true
}
);

当你直接监听 props.currentRow 时,watch 函数会尝试将 props.currentRow 当作一个响应式引用进行监听。然而,props 本身并不是一个响应式引用,而是一个对象。因此,这种方式可能会导致以下问题:

  • 非响应式引用: 如果 props.currentRow 是一个简单的值(如字符串或数字),那么 watch 可能无法正确地监听到它的变化。
  • 对象或数组内部属性变化: 即使设置了 deep: truewatch 仍然可能无法正确地监听到对象或数组内部属性的变化。

3.2 使用箭头函数监听 props 属性#

javascriptwatch(
() => props.currentRow,
(newVal, oldVal) => {
console.log('newVal', newVal);
},
{
immediate: true,
deep: true
}
);

使用箭头函数 () => props.currentRow 可以确保每次 props.currentRow 发生变化时,都会重新计算并触发 watch 回调。这种方式更可靠,因为它明确地告诉 watch 如何获取最新的 props.currentRow 值。

4 总结#

在正常的开发中,我们应该使用箭头函数组合watch来进行props值的变化,来确保每次 props 变化时都能重新计算。

pnpm依赖安装解决pnpm项目从一个文件夹复制到另一个文件夹运行失败问题解决-以vbenAdmin项目为例

1 前言#

最近在使用 vben admin 项目迁移时,从一个文件夹复制到另一个文件夹运行不起来,排查了一天,终于把问题排查出来了,特地记录一下,便于自己看也便于大家看。

使用项目安装包版本:

v2.11.5 https://github.com/vbenjs/vue-vben-admin/tags

2 解决方案#

2.1 软链接问题解决#

问题: pnpm 使用硬链接或符号链接来管理 node_modules 中的依赖项。如果链接路径在新的文件夹或文件系统中失效,可能会导致依赖解析失败。

解决方法: 尝试删除并重新安装所有依赖,确保链接正确创建。

rm -rf node_modules pnpm-lock.yaml

node_modules 文件夹一定要全部删除,pnpm-lock.yaml文件也要删除,上述是用命令删除的,也可以自己手动删(所有的 node_modules 都要删)

image-20240830135249168

2.2 文件路径过长问题#

问题: 在 Windows 系统上,文件路径过长(超过 260 个字符)可能导致文件系统问题,导致文件无法被正确访问。

解决方法: 尝试将项目目录移动到一个路径较短的位置,比如 C:\Projects\my-project,然后重新安装依赖。

node_modules 文件夹里面的内容路径会很长,所以整个项目文件夹所在路径不宜过长,否则路径可能会超过 260 个字符。

image-20240830135718984

2.3 pnpm 缓存问题#

问题: pnpm 的缓存可能导致一些包没有被正确更新或安装。

解决方法: 清除 pnpm 的缓存:

依次执行下面命令

  • 清理未使用的缓存包
pnpm store prune
  • 清空整个 pnpm 的缓存存储库
pnpm store clear

2.4 pnpm 缓存深度清理#

执行下面这句话:

pnpm store path

然后我们看到这个路径

image-20240830140127029

进入这个目录,将这 3 个文件夹都手动删除

image-20240830140240181

2.5 pnpm install#

删完之后,执行pnpm install

pnpm install

image-20240830141017683

2.6 pnpm dev#

等待依赖安装完毕后,我们执行pnpm dev

pnpm dev

程序运行成功

image-20240830142025994

3 pnpm 安装依然失败解决方案#

当我们试了各种方法还是不行,诶?这个时候我们返璞归真,是不是我们的 pnpm 的源下载不了的原因呢?国内的 pnpm 下载出现网络相关的问题还是挺多的,那么让我们来替换成国内专属的 pnpm 源试试

3.1 国内可用源#

3.1.1 淘宝 pnpm 源

https://registry.npmmirror.com

3.1.2 腾讯云 pnpm 源

https://mirrors.cloud.tencent.com

3.1.3 cnpm 源

https://r.cnpmjs.org/

3.2 设置国内可用源命令#

pnpm config set registry https://registry.npmmirror.com

3.3 查看设置的源#

pnpm config get registry

返回信息内容是你刚刚设置地址,即设置成功。

image-20240929154343078

3.4 再次 pnpm install#

ok,pnpm 相关依赖在徐徐下载,我的终于又又装成功了~

4 遗留的瑕疵#

项目虽然可以成功运行了,也可以正常写代码的了,但是终端还有几个 warning ,如果有大佬知道如何解决也可以指点一下,感谢~

image-20240830142224622

为什么 2!=false 和 2!=true 返回的都是true

前言#

今天突然想起一个奇怪的问题,记录一下,我在控制台执行内容如下:

image-20240821171734282

由上图可见,2 != false2 != true 返回的值竟然都是true,那么为什么呢,请看下文:

1 != 操作符的作用#

  • != 是“不等于”操作符。它会在比较前执行类型转换,然后再比较两个值是否不相等。

    在 JavaScript 中,2 != false2 != true 返回 true 的原因涉及到 JavaScript 中的类型转换和比较规则。

2 类型转换#

当使用 != 进行比较时,JavaScript 会尝试将比较的两个值转换为相同的类型,然后再进行比较。以下是 2 != false2 != true 的过程:

2 != false#

  1. false 会被转换为数字类型。根据 JavaScript 的转换规则,false 被转换为 0
  2. 现在表达式变成了 2 != 0
  3. 20 不相等,因此返回 true

2 != true#

  1. true 会被转换为数字类型。根据 JavaScript 的转换规则,true 被转换为 1
  2. 现在表达式变成了 2 != 1
  3. 21 不相等,因此返回 true

总结#

  • 2 != false 返回 true 是因为 20 不相等。
  • 2 != true 返回 true 是因为 21 不相等。

这就是为什么 2 != false2 != true 都会返回 true