资讯动态

use_figma 组件与变体 API 模式:Component/Variant/Component Property 全流程实战

发布时间:2026/9/13 5:25:00 来源:尧图企业网站定制
use_figma 组件与变体 API 模式Component/Variant/Component Property 全流程实战【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills本文是 figma-use Skill 的组件向参考文档 component-patterns.md 的深度展开。它系统讲解如何通过 Figma Plugin API 创建 Component、用combineAsVariants组装 Component Set变体、用addComponentProperty定义 TEXT / BOOLEAN / INSTANCE_SWAP 组件属性、把属性链接到子节点以及发现文件既有约定、按 Key 导入团队库组件、操作 Instance 与深度遍历组件元数据。读完本文你将能写出可直接在use_figma中运行的、从零构建可复用设计系统组件集的完整脚本并避开detachInstance()使节点 ID 失效、属性未链接导致无效等高频陷阱。在开始写代码前请先加载 SKILL.md 中列出的 17 条 Critical Rules并阅读 plugin-api-standalone.d.tsFigma Plugin API 的权威类型定义按需 grep。关于“什么时候用变体、什么时候用属性、代码到 Figma 的翻译模型”等设计系统语境可进一步阅读 wwds-components.md。一、创建 ComponentComponentNode 就是可发布的 Framefigma.createComponent()返回一个ComponentNode它行为上等同于FrameNode具备 Auto Layout、fills 等全部属性但额外支持发布、创建实例、合并为变体集合。典型的最小组件脚本如下const comp figma.createComponent(); comp.name MyComponent; comp.layoutMode HORIZONTAL; comp.primaryAxisAlignItems CENTER; comp.counterAxisAlignItems CENTER; comp.paddingLeft 12; comp.paddingRight 12; comp.layoutSizingHorizontal HUG; comp.layoutSizingVertical HUG; comp.fills [{ type: SOLID, color: { r: 0.2, g: 0.36, b: 0.96 } }];几点说明颜色通道是 0–1 范围不是 0–255这也是 SKILL.md 中 Critical Rule #6 的硬性约束越界会抛校验错误。layoutSizingHorizontal/Vertical的取值包括FIXED、HUG、FILL。注意resize()会把两条轴的 sizing mode 重置回FIXED所以正确顺序是先resize()、再设置 sizing mode详见 gotchas.md。在use_figma运行时新建的顶层节点默认落在(0,0)并与既有内容重叠需要扫描页面右侧空白位置放置而组件内部的子节点由父容器 Auto Layout 定位无需手动设 x/y。二、合并为 Component SetVariants命名即属性figma.combineAsVariants(components, parent)接收一个ComponentNode数组传入 Frame 会直接抛错这一点在 gotchas.md 有 WRONG/CORRECT 对照把它们分组进一个ComponentSetNode。类型定义证实了其签名combineAsVariants(nodes: ReadonlyArrayComponentNode, parent, index?): ComponentSetNode并且没有figma.createComponentSet()——Figma 不支持空的组件集见 plugin-api-standalone.d.ts。变体命名使用PropertyValue格式每个唯一组合都必须有对应的子组件存在——缺失的组合在变体选择器中会显示为空白缺口// Each components name encodes its variant properties const comp1 figma.createComponent(); comp1.name sizemd, styleprimary; const comp2 figma.createComponent(); comp2.name sizemd, stylesecondary; const componentSet figma.combineAsVariants([comp1, comp2], figma.currentPage); componentSet.name Button;两个关键注意点变体轴是组合爆炸的每个变体值组合都会在画布上生成一个显式节点冗余组合也无法条件性排除。所以设计系统语境下应只定义真正需要的轴优先用布尔/文本/实例交换属性来替代多余变体维度详见 wwds-components--creating.md。创建前先勘察文件不同文件命名习惯不同StateDefaultvsstatedefaultvsState/Default务必先匹配文件里既有的约定。这也是 SKILL.md 中Discover Conventions Before Creating的强制要求。三、合并后必须重新布局变体RequiredcombineAsVariants不会自动做 Auto Layout——合并后所有子变体都堆叠在(0,0)组件集看起来像一个所有变体重叠在一起的坍缩元素。因此合并后必须手动给每个子变体定位并按实际子节点边界调整组件集尺寸用公式推算尺寸容易出错导致变体落在边界之外const cs figma.combineAsVariants(components, figma.currentPage); // Simple row layout cs.children.forEach((child, i) { child.x i * 150; child.y 0; }); // CRITICAL: resize the component set from actual child bounds let maxX 0, maxY 0; for (const child of cs.children) { maxX Math.max(maxX, child.x child.width); maxY Math.max(maxY, child.y child.height); } cs.resizeWithoutConstraints(maxX 40, maxY 40);对于多轴变体如 size × style × state先解析子组件名称得到各属性值再映射到网格行列for (const child of cs.children) { const props Object.fromEntries( child.name.split(, ).map(p p.split()) ); const col stateValues.indexOf(props.state); const row styleValues.indexOf(props.style); child.x col * colWidth; child.y row * rowHeight; }注意resizeWithoutConstraints与resize不同前者不递归应用约束见 plugin-api-standalone.d.ts 的注释适合直接设置组件集边界。可运行的综合示例见 common-patterns.md 中的 Create Component Variants with Component Properties。四、addComponentProperty返回的是字符串 Key绝不能硬编码addComponentProperty可为组件添加TEXT、BOOLEAN、INSTANCE_SWAP三种非变体属性。类型签名是addComponentProperty(propertyName, type, defaultValue, options?): string见 plugin-api-standalone.d.ts——它返回一个带#uid后缀的字符串 Key如label#4:0后缀不可预测必须捕获返回值直接使用// Returns the key as a string — capture it! const labelKey comp.addComponentProperty(Label, TEXT, Default text); const showIconKey comp.addComponentProperty(Show Icon, BOOLEAN, true); const iconSlotKey comp.addComponentProperty(Icon, INSTANCE_SWAP, iconComponentId);时序要求组件属性必须在调用combineAsVariants之前加到每个变体组件上合并后组件集自动继承所有子组件的属性。不要直接往ComponentSetNode上加属性。这正是 common-patterns.md 中属性必须在 per-variant 循环内添加的原因。两个常见误用详见 gotchas.md// WRONG — guessing / hardcoding the key comp.addComponentProperty(label, TEXT, Button) labelNode.componentPropertyReferences { characters: label#0:1 } // Error: key not found // WRONG — treating the return value as an object const result comp.addComponentProperty(Label, TEXT, Button) const propKey Object.keys(result)[0] // BUG: returns 0 // CORRECT — the return value IS the key string, use it directly const propKey comp.addComponentProperty(Label, TEXT, Button) labelNode.componentPropertyReferences { characters: propKey }五、把属性链接到子节点Required未链接的属性等于无效添加属性但不链接到子节点该属性什么也不做。必须设置子节点的componentPropertyReferences。类型定义确认只有组件子图层或实例子图层才有该字段其余节点为null见 plugin-api-standalone.d.ts。// TEXT property → link to a text nodes characters const labelKey comp.addComponentProperty(Label, TEXT, Button); const textNode figma.createText(); textNode.characters Button; comp.appendChild(textNode); textNode.componentPropertyReferences { characters: labelKey }; // BOOLEAN INSTANCE_SWAP → link to an instance node const showIconKey comp.addComponentProperty(Show Icon, BOOLEAN, true); const iconSlotKey comp.addComponentProperty(Icon, INSTANCE_SWAP, iconComp.id); const iconInstance iconComp.createInstance(); comp.appendChild(iconInstance); iconInstance.componentPropertyReferences { visible: showIconKey, // BOOLEAN controls show/hide mainComponent: iconSlotKey // INSTANCE_SWAP controls which component };合法的componentPropertyReferences键键属性类型适用节点charactersTEXTTextNodevisibleBOOLEAN任意可切换可见性的节点mainComponentINSTANCE_SWAPInstanceNode这也是 Figma 四种属性模型的一部分——VARIANT画布上的排列组合、TEXT映射到文本子节点、BOOLEAN映射到可见性、INSTANCE_SWAP映射到实例子节点属性定义都存放在componentPropertyDefinitions上见 wwds-components.md。六、INSTANCE_SWAP用交换属性避免变体爆炸当一个组件可能承载大量子元素例如 30 个不同的图标时绝不要为每个子元素创建一个变体——那样会产生 30 倍组合爆炸。正确做法是定义一个 INSTANCE_SWAP 属性由用户在设计时从任意兼容组件中挑选// Create icon as its own ComponentNode const iconComp figma.createComponent(); iconComp.name Icon/Search; iconComp.resize(24, 24); const svgNode figma.createNodeFromSvg(svg.../svg); iconComp.appendChild(svgNode); // Use it as the default for INSTANCE_SWAP const iconSlotKey comp.addComponentProperty(Icon, INSTANCE_SWAP, iconComp.id); const instance iconComp.createInstance(); comp.appendChild(instance); instance.componentPropertyReferences { mainComponent: iconSlotKey };该模式适用于图标、头像、徽标或任何可交换的嵌套元素。设计系统语境下若代码里出现可选插槽这类属性通常也应该用INSTANCE_SWAP BOOLEAN 可见性的组合来表达而不是扩增变体轴见 wwds-components--creating.md。此外INSTANCE_SWAP 属性可通过editComponentProperty的preferredValues限定可选的候选组件见 plugin-api-standalone.d.ts。七、创建前先发现文件既有约定**在创建任何组件之前都要先检查文件。**不同文件有不同命名风格、结构与约定代码应匹配既有内容而非强加新约定。以下脚本均以figma.closePlugin()返回结果注意在use_figma运行时输出通道是return但参考文档中的独立脚本使用closePlugin二者等价地返回给调用方。列出所有页面上所有组件(async () { try { const results []; for (const page of figma.root.children) { await figma.setCurrentPageAsync(page); page.findAll(n { if (n.type COMPONENT) results.push([${page.name}] ${n.name} (COMPONENT) id${n.id}); if (n.type COMPONENT_SET) results.push([${page.name}] ${n.name} (COMPONENT_SET) id${n.id}); return false; }); } figma.closePlugin(results.join(\n)); } catch(e) { figma.closePluginWithFailure(e.toString()); } })()检查某个组件集的变体命名模式(async () { try { const cs await figma.getNodeByIdAsync(COMPONENT_SET_ID); const variantNames cs.children.map(c c.name); const propDefs cs.componentPropertyDefinitions; figma.closePlugin(JSON.stringify({ variantNames, propDefs })); } catch(e) { figma.closePluginWithFailure(e.toString()); } })()枚举文件中的组件(async () { try { const components []; for (const page of figma.root.children) { await figma.setCurrentPageAsync(page); page.findAll(n { if (n.type COMPONENT) { components.push({ name: n.name, id: n.id, page: page.name, w: n.width, h: n.height }); } return false; }); } figma.closePlugin(JSON.stringify(components)); } catch(e) { figma.closePluginWithFailure(e.toString()); } })()注意页面切换必须用await figma.setCurrentPageAsync(page)同步 setterfigma.currentPage page在use_figma运行时直接抛错见 gotchas.md 与 SKILL.md Page Rules。八、按 Key 导入组件团队库importComponentByKeyAsync和importComponentSetByKeyAsync用于从团队库不是当前所在文件导入组件。当前文件内的组件直接用figma.getNodeByIdAsync()或findOne()/findAll()定位即可。importComponentByKeyAsync(key): PromiseComponentNode与importComponentSetByKeyAsync(key): PromiseComponentSetNode的签名见 plugin-api-standalone.d.ts。// Import a component from a team library const comp await figma.importComponentByKeyAsync(COMPONENT_KEY); const instance comp.createInstance(); // Import a component set from a team library and pick a variant const set await figma.importComponentSetByKeyAsync(COMPONENT_SET_KEY); const variant set.children.find(c c.type COMPONENT c.name.includes(sizemd) ) || set.defaultVariant; const variantInstance variant.createInstance();设计系统语境下的选路原则优先按 Key 而非按名称查找因为组件名称并不唯一若只有名称则在页面内搜索或使用search_design_system见 wwds-components--using.md。另外只有已发布的组件才可被导入这一点在 d.ts 的key属性注释中有明确说明。九、操作 Instance找变体、设属性、改文本、detach 陷阱9.1 在组件集中找到正确的变体解析变体名称同时匹配多个属性const compSet await figma.importComponentSetByKeyAsync(KEY); const variant compSet.children.find(c { const props Object.fromEntries( c.name.split(, ).map(p p.split()) ); return props.variant primary props.size md; }) || compSet.defaultVariant; const instance variant.createInstance();9.2 用 setProperties 设置变体属性从组件集创建实例后可通过setProperties设置变体属性。签名是setProperties(properties: { [propertyName: string]: string | boolean | VariableAlias })见 plugin-api-standalone.d.tsconst instance defaultVariant.createInstance(); instance.setProperties({ variant: primary, size: medium });关键认知只有 VARIANT 属性是纯名称如SizeTEXT / BOOLEAN / INSTANCE_SWAP 属性都带#uid后缀如Label#1234。用错 Key 在setProperties中会静默无效见 wwds-components--using.md。9.3 覆盖实例文本先发现组件属性再 setProperties**在写文本覆盖之前永远先发现组件属性。**组件以 TEXT 类型组件属性暴露文本正确覆盖方式是setProperties()直接修改受属性管理的node.characters可能被组件属性系统在渲染时覆盖。Step 1检查示例实例的 componentPropertiesconst instance comp.createInstance(); const propDefs instance.componentProperties; // Returns e.g.: { Label#2:0: { type: TEXT, value: Button }, Has Icon#4:64: { type: BOOLEAN, value: true } } figma.closePlugin(JSON.stringify(propDefs));同时检查嵌套实例——父组件可能不直接暴露文本属性但它的嵌套子实例可能暴露const nestedInstances instance.findAll(n n.type INSTANCE); const nestedProps nestedInstances.map(ni ({ name: ni.name, id: ni.id, properties: ni.componentProperties }));Step 2对 TEXT 类型属性使用 setProperties()const instance comp.createInstance(); const propDefs instance.componentProperties; for (const [key, def] of Object.entries(propDefs)) { if (def.type TEXT) { instance.setProperties({ [key]: New text value }); } }嵌套实例若有自己的 TEXT 属性则在嵌套实例上调用setProperties()const nestedHeading instance.findOne(n n.type INSTANCE n.name Text Heading); if (nestedHeading) { nestedHeading.setProperties({ Text#2104:5: Actual heading text }); }Step 3仅对不受属性管理的文本回退到直接改 characters如果文本不受任何组件属性控制才直接查找文本节点。先加载该节点实际字体——实例文本节点继承自源组件不要假设是 Inter Regularconst textNodes instance.findAll(n n.type TEXT); for (const t of textNodes) { await figma.loadFontAsync(t.fontName); t.characters Updated text; }9.4 detachInstance() 会使祖先节点 ID 失效警告对库组件实例内的嵌套实例调用detachInstance()时父实例可能被隐式 detach从 INSTANCE 转为 FRAME 并获得新 ID。之后getNodeByIdAsync(旧父ID)会返回null// WRONG — cached parent ID becomes invalid after child detach const parentId parentInstance.id; nestedChild.detachInstance(); const parent await figma.getNodeByIdAsync(parentId); // null! // CORRECT — re-discover nodes by traversal from a stable (non-instance) parent const stableFrame await figma.getNodeByIdAsync(manualFrameId); // a frame YOU created nestedChild.detachInstance(); // Re-find the parent by traversing from the stable frame const parent stableFrame.findOne(n n.name ParentName);detachInstance()的返回类型是FrameNode见 plugin-api-standalone.d.ts。如果需要跨多个兄弟组件 detach 多个嵌套实例必须在一次use_figma调用内完成——在任何 detach 改变树结构之前先通过遍历发现所有目标节点。这一陷阱与 SKILL.md 错误恢复表中的The node with id X does not exist错误一一对应。十、深度遍历提取组件元数据与属性 schema以下辅助函数提取组件的完整属性 schema 与后代结构适合在创建实例或设置属性前理解复杂组件。它们覆盖了按 Key 导入先试 COMPONENT 再回退 COMPONENT_SET、定位持有componentPropertyDefinitions的顶层节点变体子组件的parent是 COMPONENT_SET、扁平化属性定义、递归收集 INSTANCE/TEXT 后代并处理变体命名空间与同名字节点的去重。/** * Imports a component or component set from a library by its published key. * Tries COMPONENT first, then falls back to COMPONENT_SET. * * param {string} componentKey - The published key of the component or component set. * returns {PromiseComponentNode|ComponentSetNode} */ async function importComponentByKey(componentKey) { try { return await figma.importComponentByKeyAsync(componentKey); } catch { try { return await figma.importComponentSetByKeyAsync(componentKey); } catch { throw new Error(No Component or Component Set available with key ${componentKey}); } } } /** * Given a main component node, returns the component set parent if one exists, * otherwise returns the component itself. Used to get the top-level node that * holds componentPropertyDefinitions. * * param {ComponentNode} mainComponent * returns {ComponentNode|ComponentSetNode} */ function getRelevantComponentNode(mainComponent) { return mainComponent.parent.type COMPONENT_SET ? mainComponent.parent : mainComponent; } /** * Extracts componentPropertyDefinitions from a component or component set node * into a flat map keyed by property key. * * param {ComponentNode|ComponentSetNode} node * returns {Recordstring, {name: string, type: string, key: string, variantOptions?: string[]}} */ function getComponentProps(node) { const result {}; for (let key in node.componentPropertyDefinitions) { const prop { name: key.replace(/#[^#]$/, ), type: node.componentPropertyDefinitions[key].type, key: key }; if (prop.type VARIANT) { prop.variantOptions node.componentPropertyDefinitions[key].variantOptions; } result[key] prop; } return result; } /** * Recursively walks a component tree and collects all INSTANCE and TEXT nodes * into result, keyed by TYPE[name]. Handles variant namespacing and * deduplicates nodes with identical names but differing property references. * * param {SceneNode} node - The node to traverse. * param {string[]} namespace - Accumulated variant names for the current path. * param {Recordstring, object} result - Accumulator object populated in place. */ function collectDescendants(node, namespace, result) { if (node.type INSTANCE || node.type TEXT) { const references node.componentPropertyReferences || {}; if (!node.visible !references.visible) return; const object { type: node.type, name: node.name, references }; let key ${node.type}[${node.name}]; if (result[key] JSON.stringify(references) ! JSON.stringify(result[key].references)) { key btoa(btoa(unescape(encodeURIComponent(JSON.stringify(references))))); } if (node.type INSTANCE) { const mainComponent getRelevantComponentNode(node.mainComponent); object.properties getComponentProps(mainComponent); object.descendants {}; object.mainComponentName mainComponent.name; collectDescendants(mainComponent, [], object.descendants); } const start namespace.length ? { variants: [] } : {}; result[key] Object.assign(object, result[key] || start); if (namespace.length) result[key].variants.push(namespace[namespace.length - 1]); } else if (children in node node.visible) { if (node.type COMPONENT node.parent.type COMPONENT_SET) namespace.push(node.name); node.children.forEach(child collectDescendants(child, namespace, result)); } } /** * Returns structured metadata for a component or component set defined in the current file. * * param {string} componentId - The node ID of a COMPONENT or COMPONENT_SET node. * returns {Promise{name: string, nodeId: string, properties: object, descendants: object}|undefined} */ async function getLocalComponentMetadata(componentId) { const node await figma.getNodeByIdAsync(componentId); if (node.type COMPONENT_SET || node.type COMPONENT) { const result { name: node.name, nodeId: node.id, properties: {}, descendants: {} }; result.properties getComponentProps(node); collectDescendants(node, [], result.descendants); return result; } else { throw new Error(Node is not a Component or Component Set); } } /** * Returns structured metadata for a published component or component set loaded by its key. * * param {string} componentKey - The published key of the component or component set. * returns {Promise{name: string, nodeId: string, properties: object, descendants: object}} */ async function getPublishedComponentMetadata(componentKey) { const node await importComponentByKey(componentKey); const result { name: node.name, nodeId: node.id, properties: {}, descendants: {} }; result.properties getComponentProps(node); collectDescendants(node, [], result.descendants); return result; }完整元数据提取脚本(async () { try { // For local components, use getLocalComponentMetadata: const result await getLocalComponentMetadata(COMPONENT_OR_SET_ID); figma.closePlugin(JSON.stringify(result)); // For published components, use getPublishedComponentMetadata: // const result await getPublishedComponentMetadata(COMPONENT_KEY); // figma.closePlugin(JSON.stringify(result)); } catch(e) { figma.closePluginWithFailure(e.toString()); } })()十一、实践建议把这些模式放进增量工作流根据 SKILL.md 的 Incremental Workflow 建议构建复杂组件集的推荐顺序是先勘察用第七节的发现脚本列出既有页面、组件、命名约定每个调用只做一件事建变体组件用一个调用加属性与链接用下一个调用布局画布再单独一个调用每次返回所有节点 IDreturn { createdNodeIds: [...], mutatedNodeIds: [...] }供后续调用引用每步验证用get_metadata检查结构子变体数量、变体名、属性定义、坐标用get_screenshot确认变体网格没有坍缩、文本没有被裁切或重叠错误即停止use_figma是原子的脚本报错不会执行、文件保持不变先读懂错误再修复重试绝不要盲目重试。这套创建组件 → 合并变体 → 手动布局 → 定义并链接属性 → 导入与实例化 → 深度遍历校验的模式构成了在 Figma 中程序化构建和维护设计系统组件库的完整闭环。相关可运行示例还可参考 common-patterns.md变体 组件属性完整示例、按 Key 导入、大型 ComponentSet 多步模式与 gotchas.md每个陷阱的 WRONG/CORRECT 对照。【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价