分类: 手札

  • VS Code TypeScript 语言服务器频繁崩溃、CPU 常态占用的排查与解决

    一、问题背景

    最近在使用 VS Code 开发一个大型 Vue 项目时,发现 TypeScript 语言服务器(TSServer)持续占用 CPU,并且不断进行项目初始化。

    VS Code 反复出现以下提示:

    正在初始化“frontend/tsconfig.json”

    同时还会收到警告:

    在过去 5 分钟内,JS/TS 语言服务崩溃了 5 次。这可能是由以下其中一个扩展提供的插件引起的:Vue.volar。

    项目本身采用 Monorepo 结构,包含多个 Vue 子项目以及大量 TypeScript 文件,使用了 Vue Official(Volar)扩展。

    主要表现

    • VS Code 长时间处于 TypeScript 项目初始化状态。
    • TSServer 持续占用 CPU,影响编辑器使用体验。
    • JavaScript/TypeScript 语言服务频繁崩溃并自动重启。
    • VS Code 提示可能与 Vue.volar 扩展有关。
    • 重启 VS Code 后问题仍然出现。

    经过排查,最终通过调整 TSServer 的最大堆内存限制解决了反复崩溃的问题。

    二、问题排查

    2.1 查看 TSServer 错误日志

    打开 VS Code,按下 Ctrl + Shift + P,执行:

    TypeScript: Open TS Server Log

    也可以通过:

    视图 → 输出 → TypeScript

    查看语言服务相关信息。

    排查过程中,发现以下错误:

    2026-10-15 16:39:08.294 [error] TSServer exited.
    Code: 134. Signal: null

    其中:

    • TSServer exited:TypeScript 语言服务器异常退出。
    • Code: 134:进程异常终止,通常与 abort() 有关。
    • Signal: null:没有报告具体的终止信号。

    退出码 134 并不直接等于内存溢出,但在 Node.js/V8 环境中,堆内存耗尽是常见诱因之一。

    如果日志中还出现以下错误,则可以进一步确认内存问题:

    FATAL ERROR: Reached heap limit
    Allocation failed - JavaScript heap out of memory

    或者:

    FATAL ERROR: Ineffective mark-compacts near heap limit
    Allocation failed - JavaScript heap out of memory

    本次排查没有直接捕获到上述 V8 致命错误,但后续调整内存上限后的表现进一步支持了内存不足的判断。

    2.2 分析 TypeScript 项目配置

    项目根目录的 tsconfig.json 中,存在以下配置:

    {
      "include": [
        "packages/**/*.ts",
        "packages/**/*.vue",
        "packages/**/*.tsx"
      ],
      "exclude": [
        "node_modules"
      ]
    }

    这意味着 TypeScript 需要分析 packages 目录下大量的 TS、TSX 和 Vue 文件。

    同时,项目中的各个 package 还存在相互依赖。

    在 TSServer 日志中,可以观察到大量模块解析和文件监听记录:

    FileWatcher:: Added:: WatchInfo
    DirectoryWatcher:: Added:: WatchInfo
    WatchType: Failed Lookup Locations

    这些信息说明 TypeScript 正在进行大量模块解析,并对相关路径建立文件监听。

    需要说明的是,Failed Lookup Locations 本身并不是错误,而是 TypeScript 模块解析机制中的正常行为。

    不过,在大型 Monorepo 项目中,跨 package 依赖、庞大的类型声明和复杂的类型推导,都可能显著增加语言服务器的内存消耗。

    三、解决方案:提高 TSServer 最大堆内存

    3.1 修改 VS Code 工作区配置

    1. 按下 Ctrl + Shift + P。
    2. 搜索 Preferences: Open Workspace Settings (JSON)。
    3. 打开工作区设置文件。
    4. 添加以下配置并保存:
    {
      "typescript.tsserver.maxTsServerMemory": 8192
    }

    其中,8192 表示 8192 MB,即 8 GB。

    该配置用于调整 VS Code 启动 TypeScript 语言服务器时使用的 Node.js/V8 堆内存限制。

    需要注意,这不是整个进程的总内存上限,也不代表启动后就会立即占用 8 GB。

    3.2 重新启动 TypeScript 语言服务器

    修改完成后,按下:

    Ctrl + Shift + P

    执行:

    TypeScript: Restart TS Server

    如果需要确保所有相关进程重新启动,也可以执行:

    Developer: Reload Window

    使工作区配置重新生效。

    3.3 验证修改效果

    本次排查中,将最大堆内存配置为 8192 MB 后:

    • TSServer 不再频繁崩溃。
    • VS Code 不再反复出现语言服务崩溃提示。
    • 项目初始化可以正常完成。
    • TSServer 内存占用约为 3.46 GB。
    • 初始化完成后,观察到 TSServer 的 CPU 占用降至 0%。

    说明此前的内存限制很可能不足以支撑当前项目的完整分析。

    对于大型 Vue/TypeScript Monorepo 项目,可以根据实际内存使用情况考虑调整:

    配置值堆内存限制
    30723 GB
    40964 GB
    61446 GB
    81928 GB

    需要注意,VS Code、Node.js 和 TypeScript 的版本可能影响实际默认堆内存限制,不能认为所有环境默认都严格为 3 GB。

    建议先使用 4096 MB 或 6144 MB 进行测试。如果仍然出现堆内存不足,再适当提高。

    本次项目最终使用 8192 MB,语言服务器运行恢复稳定。

    四、查看 TSServer 内存占用

    VS Code 内置了进程资源管理器,可以直接查看语言服务器的资源使用情况。

    按下:

    Ctrl + Shift + P

    执行:

    Developer: Open Process Explorer

    在进程列表中,找到类似:

    electron-nodejs (patch:tsserver.js; tsserver.js)

    本次排查中观察到两个相关进程:

    进程内存占用
    TSServer 进程 1175 MB
    TSServer 进程 23460 MB

    VS Code 的 TypeScript 扩展可能启动多个语言服务器进程,分别承担不同的分析任务。

    其中,占用较高的进程通常负责较为复杂的项目语义分析。

    如果想进一步确认进程实际使用的启动参数,可以在 Windows PowerShell 中执行:

    Get-CimInstance Win32_Process |
        Where-Object {
            $_.CommandLine -like "*tsserver.js*"
        } |
        Select-Object ProcessId, CommandLine |
        Format-List

    检查输出中是否存在:

    --max-old-space-size=8192

    如果存在,说明相应 Node.js 进程已配置对应的 V8 Old Space 限制。

    五、进一步优化:降低 TypeScript 项目分析开销

    提高堆内存上限能够解决内存不足引发的崩溃,但并不意味着项目的 TypeScript 分析开销已经得到优化。

    对于大型 Monorepo 项目,还可以从 tsconfig.json 入手。

    5.1 减少不必要的文件扫描

    例如:

    {
      "compilerOptions": {
        "skipLibCheck": true,
        "checkJs": false
      },
      "include": [
        "src/**/*.ts",
        "src/**/*.tsx",
        "src/**/*.vue"
      ],
      "exclude": [
        "node_modules",
        "dist",
        "build",
        "coverage"
      ]
    }

    其中:

    • skipLibCheck:跳过声明文件的类型检查,减少部分类型检查开销。
    • checkJs: false:关闭 JavaScript 文件的类型错误检查。
    • include:限制直接纳入当前 TypeScript 工程的源码范围。
    • exclude:排除不需要作为根文件加载的目录。

    注意,这只是示例配置,实际使用时应保留原项目已有的必要编译选项。

    另外,exclude 并不能完全阻止通过 import 引用的文件被加载。

    5.2 合理拆分 Monorepo 中的 TypeScript 工程

    如果项目结构类似:

    frontend/
    ├── packages/
    │   ├── portal/
    │   ├── admin/
    │   ├── cloudpivot-form/
    │   └── cloudpivot-portal-designer/
    └── tsconfig.json

    则可以考虑为各个 package 配置独立的 tsconfig.json,避免所有项目文件都被同一个 TypeScript 工程直接包含。

    必要时,还可以结合 TypeScript Project References 管理项目间依赖。

    不过,拆分工程需要结合实际的模块引用关系、构建工具和类型声明生成流程设计,不能简单地删除跨 package 引用。

    5.3 其他 VS Code 配置

    还可以根据项目情况选择性添加:

    {
      "typescript.tsserver.maxTsServerMemory": 8192,
      "typescript.disableAutomaticTypeAcquisition": true,
      "files.watcherExclude": {
        "**/node_modules/**": true,
        "**/dist/**": true,
        "**/build/**": true
      }
    }

    其中,typescript.disableAutomaticTypeAcquisition 主要用于关闭自动获取类型定义的行为。

    files.watcherExclude 则用于减少 VS Code 文件系统监听开销,并不等同于禁止 TypeScript 解析这些目录。

    这些配置可以作为辅助优化手段,但无法替代对实际项目依赖和类型分析开销的排查。

    六、总结

    本次问题的核心表现是:

    大型 Vue Monorepo 项目使 TypeScript 语言服务器消耗大量内存,在原有堆内存限制下频繁异常退出,进而造成不断初始化和 CPU 重复负载。

    最终通过提高 TSServer 堆内存限制解决了问题。

    关键配置只有一项:

    {
      "typescript.tsserver.maxTsServerMemory": 8192
    }

    需要区分两个层面:

    • 提高堆内存上限:解决语言服务器因内存不足而崩溃的问题。
    • 优化 TypeScript 工程配置:减少不必要的文件扫描、模块解析和类型检查开销。

    对于大型 Vue、React 或其他 TypeScript Monorepo 项目,当 VS Code 出现 TSServer 频繁崩溃、重复初始化、CPU 占用异常等现象时,可以优先检查进程内存和退出日志,再根据实际情况调整内存限制。

    如果提高堆内存后语言服务器能够稳定完成初始化,并且空闲时 CPU 占用恢复正常,那么通常不需要急于进行大规模工程改造。

  • Ubuntu 下 aTrustTray2 反复崩溃导致 apport 占用 CPU

    最近在 Ubuntu 上运行深信服 aTrust 时,发现 apport 进程会不定期占用较高 CPU。

    一开始查看:

    ps -ef | grep -E 'apport|aTrust'

    发现 apport 是因为 aTrustTray2 崩溃后被系统拉起,且 aTrustTray2 的 PID 一直变化,说明它在反复:

    启动 → 崩溃 → 被重新拉起

    崩溃信号为:

    SIGABRT

    进一步手动运行:

    QT_DEBUG_PLUGINS=1 \
    /usr/share/sangfor/aTrust/resources/bin/aTrustTray2 -l zh-CN

    发现报错:

    This application failed to start because it could not find or load the Qt platform plugin "xcb"

    继续检查发现,aTrust 自己其实已经带了 libqxcb.so:

    find /usr/share/sangfor/aTrust -name 'libqxcb.so'

    结果:

    /usr/share/sangfor/aTrust/resources/lib/libqxcb.so

    但 aTrustTray2 默认搜索的路径却是:

    /usr/share/sangfor/aTrust/resources/bin/platforms

    而这个目录不存在。

    最终通过创建目录并添加软链接解决:

    sudo mkdir -p /usr/share/sangfor/aTrust/resources/bin/platforms
    
    sudo ln -s \
    /usr/share/sangfor/aTrust/resources/lib/libqxcb.so \
    /usr/share/sangfor/aTrust/resources/bin/platforms/libqxcb.so

    修复后,aTrustTray2 不再反复崩溃,apport 也不再持续占用 CPU。

    问题本质上是:

    aTrust 自带 Qt xcb 插件
    ↓
    插件实际位于 resources/lib
    ↓
    程序却只搜索 resources/bin/platforms
    ↓
    找不到插件后 SIGABRT
    ↓
    apport 不断处理崩溃

    如果以后升级或重装 aTrust 后问题再次出现,可以优先检查:

    ls -l /usr/share/sangfor/aTrust/resources/bin/platforms/libqxcb.so

    看这个软链接是否还存在。

  • R.E.P.O. 自建 Photon Server 部署教程

    本文记录如何使用 Photon On-Premise Server SDK v4 搭建一个可供 R.E.P.O. 使用的自建 Photon Server。

    需要说明的是,这种方式并不是搭建传统意义上的“游戏专用服务器”,而是通过自建 Photon Server 替代默认的 Photon 联机服务,让客户端连接到我们自己的服务器。

    ps:不如官网服务器一根,建议自行申请国际PON服务器和国区APPID

    一、准备内容

    需要准备:

    1. Windows 服务器或 Windows 主机
    2. Photon On-Premise Server SDK v4
    3. R.E.P.O. 游戏本体
    4. BepInEx 需要安装如下mod:
    4.1 Self Hosted Photon         用于修改服务器连接参数
    4.2 PhotonV4ServerVoiceFix     用于修复Photon Server V4无法语音的问题
    
    如果使用Mod管理器安装Mod,建议使用游戏内Mod配置修改Mod
    4.3 REPOConfig                 游戏内Mod配置修改Mod
    4.4 MenuLib                    游戏内Mod配置修改Mod前置
    本文只介绍如何本地部署Photon服务,Mod安装请移步B站相关视频&专栏

    本文使用的 Photon 版本为:

    Photon-OnPremise-Server-SDK_v4-0-29-11263

    客户端 Mod 使用(带*号为必须):

    Self Hosted Photon *
    PhotonV4ServerVoiceFix

    REPOConfig
    MenuLib

    二、安装 Photon Server

    下载并解压 Photon Server SDK,例如:

    D:\Photon-OnPremise-Server-SDK_v4-0-29-11263

    进入目录:

    D:\Photon-OnPremise-Server-SDK_v4-0-29-11263\deploy\bin_Win64

    运行:

    PhotonControl.exe

    运行后,系统托盘区会出现 Photon Control 图标。

    右键托盘图标,选择:

    LoadBalancing (MyCloud)
      → Start as Application

    如果成功启动,说明 Photon Server 已经跑起来了。

    后续长期使用时,也可以选择安装为服务:

    LoadBalancing (MyCloud)
      → Install Service
      → Start as Service

    测试阶段建议先使用 Start as Application,方便查看和重启。

    三、开放服务器端口

    Photon Server v4 默认需要开放以下端口:

    UDP 5055
    UDP 5056
    
    TCP 4530
    TCP 4531
    
    TCP 9090
    TCP 9091

    其中最关键的是:

    UDP 5055<br>UDP 5056

    关于如何开放端口本文也不会详细说明,教程仅给出需要开放的端口以供参考

    四、配置 GameServer 公网地址

    这一步非常重要。

    打开配置文件:

    deploy\LoadBalancing\GameServer\bin\Photon.LoadBalancing.dll.config

    找到:

    <Photon.LoadBalancing.GameServer.GameServerSettings>

    在里面修改公网地址配置。

    公网侧IP地址配置:

    <setting name="PublicIPAddress" serializeAs="String">
      <value>123.123.123.123</value>
    </setting>

    修改完成后,需要重启 Photon以应用修改:

    Photon Control
      → LoadBalancing (MyCloud)
      → Stop
      → Start as Application

    检查 Photon 是否监听端口(非必须)

    在服务器上执行:

    netstat -ano -p udp | findstr ":5055"
    netstat -ano -p udp | findstr ":5056"

    正常情况下应该能看到类似:

    UDP    0.0.0.0:5055    *:*
    UDP    0.0.0.0:5056    *:*

    六、安装客户端 Mod

    R.E.P.O. 客户端需要安装:

    BepInEx *
    Self Hosted Photon *
    PhotonV4ServerVoiceFix *
    
    REPOConfig
    MenuLib

    安装完成后,启动一次游戏,让配置文件自动生成,然后退出游戏。

    配置文件一般在:

    BepInEx\config\Self_Hosted_Photon.cfg

    七、配置客户端连接地址

    Self Hosted Photon配置:

    打开:

    BepInEx\config\Self_Hosted_Photon.cfg

    配置示例:

    ## Settings file was created by plugin Self_Hosted_Photon v0.1.0
    ## Plugin GUID: Self_Hosted_Photon
    
    [General]
    
    ## Enable or disable the plugin.
    # Setting type: Boolean
    # Default value: true
    Enabled = true
    
    [Server]
    
    ## The IP address or hostname of the Photon server.
    # Setting type: String
    # Default value: 127.0.0.1
    Address = 123.123.123.123
    
    ## The port number of the Photon server.
    # Setting type: String
    # Default value: 5055
    Port = 5055
    
    

    如果使用域名,则可以写成:

    ## Settings file was created by plugin Self_Hosted_Photon v0.1.0
    ## Plugin GUID: Self_Hosted_Photon
    
    [General]
    
    ## Enable or disable the plugin.
    # Setting type: Boolean
    # Default value: true
    Enabled = true
    
    [Server]
    
    ## The IP address or hostname of the Photon server.
    # Setting type: String
    # Default value: 127.0.0.1
    Address = www.你的域名.com
    
    ## The port number of the Photon server.
    # Setting type: String
    # Default value: 5055
    Port = 5055
    
    

    如果安装了游戏内Mod配置修改Mod:

    1、启动游戏,点击MODS

    2、点击Self_Hosted_Photon,即可在右侧修改连接配置,修改完点击保存更改即可。

    八、测试连接

    配置完成后,就可以启动 R.E.P.O.进行游玩测试服务器了。

  • 转载:VUE防抖与节流的最佳解决方案——函数式组件

    来源:https://www.ucloud.cn/yun/6841.html

    摘要:案例持续触发事件时,并不立即执行函数,当毫秒内没有触发事件时,才会延时触发一次函数。也以函数形式暴露普通插槽。这样的场景组件用函数式组件是非常方便的。相关阅读函数式组件自定义指令

    前言

    有echarts使用经验的同学可能遇到过这样的场景,在window.onresize事件回调里触发echartsBox.resize()方法来达到重绘的目的,resize事件是连续触发的这意味着echarts实例会连续的重绘这是非常耗性能的。还有一个常见的场景在input标签的input事件里请求后端接口,input事件也是连续触发的,假设我输入了“12”就会请求两次接口参数分别是“1”和“12”,比浪费网络资源更要命的是如果参数为“1”的请求返回数据的时间晚于参数为“12”的接口,那么我们得到的数据是和期望不符的。当然基于axios可以做很多封装可以取消上一个请求或者通过拦截做处理,但还是从防抖入手比较简单。

    防抖和节流到底是啥函数防抖(debounce)

    解释:当持续触发某事件时,一定时间间隔内没有再触发事件时,事件处理函数才会执行一次,如果设定的时间间隔到来之前,又一次触发了事件,就重新开始延时。
    案例:持续触发scroll事件时,并不立即执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发一次handle函数。

    function debounce(fn, wait) {
      let timeout = null
      return function() {
        if(timeout !== null) clearTimeout(timeout)     
        timeout = setTimeout(fn, wait);
      }
    }
    function handle() {   
      console.log(Math.random())
    }
    window.addEventListener("scroll", debounce(handle, 1000))
    

    addEventListener的第二个参数实际上是debounce函数里return回的方法,let timeout = null 这行代码只在addEventListener的时候执行了一次 触发事件的时候不会执行,那么每次触发scroll事件的时候都会清除上次的延时器同时记录一个新的延时器,当scroll事件停止触发后最后一次记录的延时器不会被清除可以延时执行,这是debounce函数的原理函数节流(throttle)

    解释:当持续触发事件时,有规律的每隔一个时间间隔执行一次事件处理函数。
    案例:持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。

    function throttle(fn, delay) { 
      var prev = Date.now()         
      return function() {               
        var now = Date.now()               
        if (now - prev > delay) {                   
          fn()                
          prev = Date.now()             
        }         
      }       
    }       
    function handle() {           
      console.log(Math.random())      
    }
    window.addEventListener("scroll", throttle(handle, 1000))
    

    原理和防抖类似,每次执行fn函数都会更新prev用来记录本次执行的时间,下一次事件触发时判断时间间隔是否到达预先的设定,重复上述操作。

    防抖和节流都可以用于 mousemove、scroll、resize、input等事件,他们的区别在于防抖只会在连续的事件周期结束时执行一次,而节流会在事件周期内按间隔时间有规律的执行多次。

    在vue中的实践

    在vue中实现防抖无非下面这两种方法

    封装utils工具

    封装组件封装utils工具

    把上面的案例改造一下就能封装一个简单的utils工具

    utils.js
    let timeout = null
    function debounce(fn, wait) {
      if(timeout !== null) clearTimeout(timeout)
      timeout = setTimeout(fn, wait)
    }
    export default debounce
    
    app.js
    type="text" @input="debounceInput($event)">
    
    import debounce from "./utils"
    export default {
      methods: {
        debounceInput(E){
          debounce(() => {
            console.log(E.target.value)
          }, 1000)
        }
      }
    }

    封装组件

    至于组件的封装我们要用到$listeners、$attrs这两个属性,他俩都是vue2.4新增的内容,官网的介绍比较晦涩,我们来看他俩到底是干啥的:

    $listeners: 父组件在绑定子组件的时候会在子组件上绑定很多属性,然后在子组件里通过props注册使用,那么没有被props注册的就会放在$listeners里,当然不包括class和style,并且可以通过 v-bind=”$attrs” 传入子组件的内部组件。

    $listeners: 父组件在子组件上绑定的不含.native修饰器的事件会放在$listeners里,它可以通过 v-on=”$listeners” 传入内部组件。

    简单来说$listeners、$attrs他俩是做属性和事件的承接,这在对组件做二次封装的时候非常有用。

    我们以element-ui的el-input组件为例封装一个带防抖的debounce-input组件

    debounce-input.vue
    
    
    
    app.vue
    
    
    

    上面组件的封装用了$attrs,虽然不需要开发者关注属性的传递,但是在使用上还是不方便的,因为把el-input封装在了内部这样对样式的限定也比较局限。有接触过react高阶组件的同学可能有了解,react高阶组件本质上是一个函数通过包裹被传入的React组件,经过一系列处理,最终返回一个相对增强的React组件。那么在vue中可以借鉴这种思路吗,我们来了解一下vue的函数式组件。关于vue函数式组件什么是函数式组件?

    函数式组件是指用一个Function来渲染一个vue组件,这个组件只接受一些 prop,我们可以将这类组件标记为 functional,这意味着它无状态 (没有响应式数据),也没有实例 (没有this上下文)。

    一个函数式组件大概向下面这样:

    export default () => {
      functional: true, 
      props: { 
        // Props 是可选的
      },
      // 为了弥补缺少的实例, 提供第二个参数作为上下文
      render: function (createElement, context) {
        return vNode
      }
    }
    注意:在 2.3.0 之前的版本中,如果一个函数式组件想要接收 prop,则 props 选项是必须的。在 2.3.0 或以上的版本中,你可以省略 props 选项,所有组件上的特性都会被自动隐式解析为 prop。但是你一旦注册了 prop 那么只有被注册的 prop 会出现在 context.prop 里。
    

    render函数的第二个参数context用来代替上下文this他是一个包含如下字段的对象:

    props:提供所有 prop 的对象

    children: VNode 子节点的数组

    slots: 一个函数,返回了包含所有插槽的对象

    scopedSlots: (2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。

    data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件

    parent:对父组件的引用

    listeners: (2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。

    injections: (2.3.0+) 如果使用了 inject 选项,则该对象包含了应当被注入的属性。vm.$slots API 里面是什么

    slots用来访问被插槽分发的内容。每个具名插槽 有其相应的属性 (例如:v-slot:foo 中的内容将会在 vm.$slots.foo 中被找到)。default 属性包括了所有没有被包含在具名插槽中的节点,或 v-slot:default 的内容。slots() 和 children 对比

    你可能想知道为什么同时需要 slots() 和 children。slots().default 不是和 children 类似的吗?在一些场景中,是这样——但如果是如下的带有子节点的函数式组件呢?

      
        first
      
      second
    
    

    对于这个组件,children 会给你两个段落标签,而 slots().default 只会传递第二个匿名段落标签,slots().foo 会传递第一个具名段落标签。同时拥有 children 和 slots(),因此你可以选择让组件感知某个插槽机制,还是简单地通过传递 children,移交给其它组件去处理。一个函数式组件的使用场景

    假设有一个a组件,引入了 a1,a2,a3 三个组件,a组件的父组件给a组件传入了一个type属性根据type的值a组件来决定显示 a1,a2,a3 中的那个组件。这样的场景a组件用函数式组件是非常方便的。那么为什么要用函数式组件呢?一句话:渲染开销低,因为函数式组件只是函数。用函数式组件的方式来实现防抖

    因为业务关系该防抖组件的封装同时支持 input、button、el-input、el-button 的使用,如果是input类组件对input事件做防抖处理,如果是button类组件对click事件做防抖处理。

    const debounce = (fun, delay = 500, before) => {
      let timer = null
      return (params) => {
        timer && window.clearTimeout(timer)
        before && before(params)
        timer = window.setTimeout(() => {
           // click事件fun是Function  input事件fun是Array
          if (!Array.isArray(fun)) {
            fun = [fun]
          }
          for (let i in fun) {
            fun[i](params)
          }
          timer = null
        }, parseInt(delay))
      }
    }
    export default {
      name: "Debounce",
      functional: true, // 静态组件 当不声明functional时该组件同样拥有上下文以及生命周期函数
      render(createElement, context) {
        const before = context.props.before
        const time = context.props.time
        const vnodeList = context.slots().default
        if (vnodeList === undefined){
          console.warn(" 组件必须要有子元素")
          return null
        }
        const vnode = vnodeList[0] || null // 获取子元素虚拟dom
        if (vnode.tag === "input") {
          const defaultFun = vnode.data.on.input
          const debounceFun = debounce(defaultFun, time, before) // 获取节流函数
          vnode.data.on.input = debounceFun
        } else if (vnode.tag === "button") {
          const defaultFun = vnode.data.on.click
          const debounceFun = debounce(defaultFun, time, before) // 获取节流函数
          vnode.data.on.click = debounceFun
        } else if (vnode.componentOptions && vnode.componentOptions.tag === "el-input") {
          const defaultFun = vnode.componentOptions.listeners.input
          const debounceFun = debounce(defaultFun, time, before) // 获取节流函数
          vnode.componentOptions.listeners.input = debounceFun
        } else if (vnode.componentOptions && vnode.componentOptions.tag === "el-button") {
          const defaultFun = vnode.componentOptions.listeners.click
          const debounceFun = debounce(defaultFun, time, before) // 获取节流函数
          vnode.componentOptions.listeners.click = debounceFun
        } else {
          console.warn(" 组件内只能出现下面组件的任意一个且唯一 el-button、el-input、button、input")
          return vnode
        }
        return vnode
      }
    }

    原理也很简单就是在vNode中拦截on下面的click、input事件做防抖处理,这样在使用上就非常简单了。自定义指令 directive

    我们来思考一个问题,函数式组件封装防抖的关节是获取vNode,那么我们通过自定义指令同样可以拿到vNode,甚至还可以得到原生的Dom,这样用自定义指令来处理会更加方便。。。。。。

  • 如何在Nextcloud-AIO容器中重新获取passphrase

    如果你因为某些原因丢失了AIO面板的passphrase,你可以在容器中执行以下命令重新获取:

    sudo docker exec nextcloud-aio-mastercontainer grep password /mnt/docker-aio-config/data/configuration.json

    原文:If you have somehow lost the passphrase that is used for the AIO interface, you can reobtain it by running sudo docker exec nextcloud-aio-mastercontainer grep password /mnt/docker-aio-config/data/configuration.json.

    来自https://github.com/nextcloud/all-in-one/discussions/1786

  • 建站历程

    大概是 2021 还是 2022 年,那是我第一次建站,当时我打算在工作之余记录下平时工作上的一些心得。但是由于种种原因导致的服务无法访问,我的第一个博客网站关停了,数据还待有心之时再去挖掘。

    2025 年 5 月末,春夏交织的这个时节,我又心痒痒了。前情提要是:我之前一直活动在周期性的 MC 服务搭建的“事业”中,随着那时候使用的云服务套餐到期,我对各大云服务厂商的产品做了一番比较,最终阿里云那台 68 元的机器深深吸引了我。当机立断,也就有了现在的这个小网站。

    目前还是打算延续以往的风格,用于记录工作和生活中的点滴,这次搭建服务使用了部署于 Docker 中的 Nginx + Windows中 的 PHP ,数据库依旧是 MySQL。

    为什么要在两个平台上分别部署?别急,我这就告诉你。这里的 Docker 指的是运行于 WSL2 上的 Docker for Windows ,过去的我打算用一台高性能的机器运行 KVM 来拆成PC服务器二合一,但最后又不了了之,索性玩起了 WSL ,在上面部署了 Nextcloud 服务。最近才又想折腾博客,但是 Docker 上的 PHP 部署也有点麻烦, 而Windows 上部署则方便的多,于是乎……虽然最终部署成功,但过程中的坑可不少,这个我们日后再聊。

    那么废话少说,这里是帕琪,这个小站用于记录我的工作及生活中发生的点点滴滴,我们下次再见!