PAYATHON 2026

discord.js 中 component.toJSON 报错及未知行错误 [已解决]

排障赵师傅

结论

问题出在这段调用:

actionRow.addComponents(button =>
  button
    .setCustomId('button')
    .setLabel('Open form')
    .setStyle(discord.ButtonStyle.Primary)
)

ActionRowBuilder#addComponents() 接收的是已经创建好的组件 Builder。普通回调没有说明要创建 ButtonBuilder、Select Menu 还是其他组件,因此这个回调函数会被当作 component 保存。发送消息时,discord.js 会依次调用每个 component 的 toJSON()。普通函数没有这个方法,所以会报错:

TypeError: component.toJSON is not a function

解决办法是显式创建 ButtonBuilder,或者使用 addPrimaryButtonComponents() 这类已经指定按钮类型的方法。

修改方法一:显式创建 ButtonBuilder

可以先创建按钮,再把它传给 addComponents():

require('dotenv').config();
const discord = require('discord.js');

const client = new discord.Client({
  intents: [discord.GatewayIntentBits.Guilds],
});

const button = new discord.ButtonBuilder()
  .setCustomId('button')
  .setLabel('Open form')
  .setStyle(discord.ButtonStyle.Primary);

const container = new discord.ContainerBuilder()
  .setAccentColor(0x0099ff)
  .addActionRowComponents(actionRow =>
    actionRow.addComponents(button)
  );

client.once(discord.Events.ClientReady, readyClient => {
  const channel = readyClient.channels.cache.get(/* channel ID */);

  channel.send({
    components: [container],
    flags: discord.MessageFlags.IsComponentsV2,
  });
});

client.login(process.env.TOKEN);

也可以直接在 addComponents() 中创建实例:

const container = new discord.ContainerBuilder()
  .setAccentColor(0x0099ff)
  .addActionRowComponents(actionRow =>
    actionRow.addComponents(
      new discord.ButtonBuilder()
        .setCustomId('button')
        .setLabel('Open form')
        .setStyle(discord.ButtonStyle.Primary)
    )
  );

这样存入 Action Row 的就是实现了 toJSON() 的 ButtonBuilder,可以正常序列化。

修改方法二:使用按钮类型对应的回调方法

如果当前安装的 discord.js 版本提供 addPrimaryButtonComponents(),也可以保留回调写法:

const container = new discord.ContainerBuilder()
  .setAccentColor(0x0099ff)
  .addActionRowComponents(actionRow =>
    actionRow.addPrimaryButtonComponents(button =>
      button
        .setCustomId('button')
        .setLabel('Open form')
    )
  );

addPrimaryButtonComponents() 已经指定了按钮类型,能够把 Primary Button Builder 传给回调。按钮样式也由这个方法确定,无须再调用:

.setStyle(discord.ButtonStyle.Primary)

discord.js 各版本提供的辅助方法可能不同。如果当前版本没有 addPrimaryButtonComponents(),直接使用第一种写法创建 ButtonBuilder 即可。

为什么堆栈只指向 node_modules

Builder 在调用 addComponents() 时,通常不会立即完成所有校验。直到发送消息或调用 container.toJSON(),错误才会暴露。

序列化过程大致如下:

ContainerBuilder.toJSON()
  -> ActionRowBuilder.toJSON()
    -> component.toJSON()

因此,堆栈最终停在:

this.components.map(component => component.toJSON())

这行代码只是第一次真正用到错误 component 的地方,问题实际发生在此前的组件构建阶段。

调试时,可以在发送消息前主动序列化:

console.dir(container.toJSON(), { depth: null });

如果组件树有问题,错误会直接在这里出现,比从 channel.send() 的堆栈向前排查更容易缩小范围。

Invalid Array Length 和 undefined 的原因

Invalid Array Length 通常说明某个复合组件生成了空数组,或者子组件数量不符合验证规则。如果 separators、sections 或其他组件也使用了不合适的通用回调,回调可能会被保存成错误值,目标 Builder 也可能没有收到有效的子组件。

可以逐层检查组件结构:

console.dir(container.components, { depth: null });

数组中的每个元素都应该是实际的 Builder 对象,不能是函数、undefined 或空数组。排查时还可以暂时只保留一个 Action Row 和一个 Button。确认消息可以发送后,再逐个加回 Section、Separator 和 Thumbnail。

container.components[0].components[0] 不是稳定的公共调试接口。不同 Builder 保存内容的结构可能不同,回调也可能稍后才会解析。不要通过内部数组继续调用 setCustomId(),应保留显式创建的 Builder 引用:

const button = new discord.ButtonBuilder()
  .setCustomId('button')
  .setLabel('Open form')
  .setStyle(discord.ButtonStyle.Primary);

console.log(button.toJSON());

其他注意事项

回调使用花括号时,需要显式返回 Builder:

.addActionRowComponents(actionRow => {
  return actionRow.addComponents(button);
})

原示例中的箭头函数没有花括号,会隐式返回结果,所以外层回调没有问题。参数列表末尾的逗号也是合法的 JavaScript 语法,不会引发这次错误。

使用 Components V2 前,还要确认当前安装的 discord.js 版本包含 ContainerBuilder、MessageFlags.IsComponentsV2 和代码中使用的专用组件方法。不同版本的 Builder API 可能存在差异,不能直接混用其他版本文档中的回调写法。

持续时间文本和 custom thumbnail 可能只是让消息重新走到了有问题的组件分支。component.toJSON is not a function 表明 Action Row 中存在无法序列化的值,与文本内容本身没有直接关系。

备注:内容仅供参考。