react-to-axure
React 导出到 Axure
概述
本技能将帮助你把任意 React 组件或应用改造为可导入 Axure 原型平台的标准组件。改造完成后,组件将具备:
- 配置面板:在 Axure 编辑器中提供可视化配置项(颜色、文本、数值等)
- 事件系统:向 Axure 原型发送自定义事件
- 动作系统:响应来自 Axure 原型的外部调用
- 变量暴露:将内部状态暴露给 Axure 原型读取
- 数据接入:接收来自 Axure 原型的外部数据源
本技能产出两类产物:
- 配置声明:5 组常量(EVENT_LIST / ACTION_LIST / VAR_LIST / CONFIG_LIST / DATA_LIST)
- 运行时封装:使用
forwardRef+useImperativeHandle包装的组件代码
何时使用
- 你有一个现成的 React 组件,希望在 Axure 原型中使用
- 你的 React 应用需要与 Axure 原型系统进行双向交互
- 你需要为 React 组件生成标准化的配置面板定义
引用文件
本技能附带了完整的类型定义文件,可直接复制到目标项目中使用:
references/axure-types.ts:核心接口(AxureProps、AxureHandle)和工具函数references/config-panel-types.ts:配置面板组件的完整类型定义
改造工作流程
第 1 步:分析源组件
分析目标 React 组件的以下方面:
- Props 接口:组件当前接收哪些 props?
- 内部状态:有哪些 useState / useReducer 状态需要暴露?
- 交互行为:有哪些用户交互事件(click、change、submit 等)?
- 外部依赖:组件依赖哪些第三方库?
- DOM 操作:是否直接操作 DOM(如图表库、地图库)?
输出一份改造清单,列出需要映射的 props → config、state → var、callback → event/action。
第 2 步:复制类型定义
将 references/axure-types.ts 复制到目标项目的公共目录中(如 src/common/axure-types.ts)。如果需要配置面板的详细类型,也复制 references/config-panel-types.ts。
第 3 步:定义接口层
根据分析结果,生成以下 5 组常量定义。
3.1 事件列表 EVENT_LIST
定义组件可以向外触发的事件:
import type { EventItem } from './path-to/axure-types';
const EVENT_LIST: EventItem[] = [
{ name: 'onClick', desc: '点击时触发' },
{ name: 'onChange', desc: '值改变时触发,payload 为新值的 JSON 字符串' }
];
规则:
name:事件名称,建议使用 camelCasedesc:事件描述,说明触发时机payload:可选,描述 payload 的格式(payload 必须是字符串类型,复杂数据需要JSON.stringify)
3.2 动作列表 ACTION_LIST
定义组件可以响应的外部调用:
import type { Action } from './path-to/axure-types';
const ACTION_LIST: Action[] = [
{ name: 'reset', desc: '重置组件到初始状态' },
{ name: 'setValue', desc: '设置值,参数格式:JSON 字符串', params: 'JSON string' }
];
规则:
params必须是字符串类型,复杂参数需要JSON.parse解析desc应说明参数格式
3.3 变量列表 VAR_LIST
定义组件暴露的内部状态:
import type { KeyDesc } from './path-to/axure-types';
const VAR_LIST: KeyDesc[] = [
{ name: 'current_value', desc: '当前值' },
{ name: 'is_loading', desc: '是否加载中' }
];
规则:
name必须使用 snake_case(小写字母 + 数字 + 下划线)- 只暴露外部需要读取的状态,不要暴露内部实现细节
3.4 配置项列表 CONFIG_LIST
定义配置面板中的可配置项:
import type { ConfigItem } from './path-to/axure-types';
const CONFIG_LIST: ConfigItem[] = [
{
type: 'input',
attributeId: 'title',
displayName: '标题',
info: '组件顶部显示的标题文本',
initialValue: '默认标题'
},
{
type: 'colorPicker',
attributeId: 'primaryColor',
displayName: '主色调',
info: '组件的主题颜色',
initialValue: '#1890ff'
},
{
type: 'inputNumber',
attributeId: 'maxCount',
displayName: '最大数量',
info: '允许的最大数量',
initialValue: 10,
min: 1,
max: 100
},
{
type: 'switch',
attributeId: 'showBorder',
displayName: '显示边框',
info: '是否显示组件边框',
initialValue: true
},
{
type: 'select',
attributeId: 'size',
displayName: '尺寸',
info: '组件尺寸',
initialValue: 'medium',
options: [
{ label: '小', value: 'small' },
{ label: '中', value: 'medium' },
{ label: '大', value: 'large' }
]
}
];
可用 type(详细定义见 references/config-panel-types.ts):
| type | 用途 | initialValue 类型 |
|---|---|---|
input |
文本输入 | string |
inputNumber |
数字输入(支持 min/max/step) | number |
checkbox |
勾选框 | boolean |
switch |
开关 | boolean |
slider |
滑块(需要 min/max) | number |
select |
下拉选择(需要 options) | string | number |
colorPicker |
颜色选择器 | string(HEX 格式) |
autoComplete |
自动完成输入 | string |
arrayData |
数组数据编辑器 | string[] |
table |
表格数据编辑器 | any[] |
map |
键值对编辑器 | Record<string, any> |
fontSetting |
字体设置(颜色/大小/粗细等) | 对象 |
lineSetting |
线条设置 | 对象 |
group |
分组容器(包含 children) | — |
collapse |
折叠面板容器 | — |
映射策略(原始 Props → ConfigItem):
| 原始 Props 类型 | 推荐 ConfigItem type |
|---|---|
string |
input |
number |
inputNumber |
boolean |
switch 或 checkbox |
enum / union |
select(将枚举值映射为 options) |
| 颜色字符串 | colorPicker |
string[] |
arrayData |
| 复杂对象/数组 | table 或 map |
3.5 数据项列表 DATA_LIST
定义组件接收的外部数据结构:
import type { DataDesc } from './path-to/axure-types';
const DATA_LIST: DataDesc[] = [
{
name: 'items',
desc: '列表数据',
keys: [
{ name: 'id', desc: '唯一标识' },
{ name: 'title', desc: '标题' },
{ name: 'value', desc: '值' }
]
}
];
适用场景:适用于列表、表格等数据驱动型组件。纯展示或配置驱动的组件可以设为空数组 []。
第 4 步:封装组件
将原始组件用 forwardRef + useImperativeHandle 包装:
import React, { forwardRef, useImperativeHandle, useState, useCallback } from 'react';
import type { AxureProps, AxureHandle } from './path-to/axure-types';
const WrappedComponent = forwardRef<AxureHandle, AxureProps>(
function WrappedComponent(innerProps, ref) {
// ① 安全解构 props(防御性编程)
const dataSource = innerProps && innerProps.data ? innerProps.data : {};
const configSource = innerProps && innerProps.config ? innerProps.config : {};
const onEventHandler = typeof innerProps.onEvent === 'function'
? innerProps.onEvent
: function () { return undefined; };
const container = innerProps && innerProps.container ? innerProps.container : null;
// ② 从 config 读取配置值(使用类型检查,避免 || 运算符)
const title = typeof configSource.title === 'string' && configSource.title
? configSource.title
: '默认标题';
const maxCount = typeof configSource.maxCount === 'number'
? configSource.maxCount
: 10;
// ③ 组件内部状态
const countState = useState(0);
const count = countState[0];
const setCount = countState[1];
// ④ 事件触发(payload 必须是字符串)
const emitEvent = useCallback(function (eventName: string, payload?: string) {
try {
onEventHandler(eventName, payload);
} catch (error) {
console.warn('事件触发失败:', eventName, error);
}
}, [onEventHandler]);
// ⑤ 动作处理(params 必须是字符串)
const handleAction = useCallback(function (name: string, params?: string) {
switch (name) {
case 'reset':
setCount(0);
break;
case 'setValue':
if (params) {
try {
const parsed = JSON.parse(params);
if (typeof parsed.count === 'number') {
setCount(parsed.count);
}
} catch (e) {
console.warn('参数解析失败:', e);
}
}
break;
}
}, []);
// ⑥ 暴露 Handle 接口
useImperativeHandle(ref, function () {
return {
getVar: function (name: string) {
const vars: Record<string, any> = {
current_value: count,
};
return vars[name];
},
fireAction: handleAction,
eventList: EVENT_LIST,
actionList: ACTION_LIST,
varList: VAR_LIST,
configList: CONFIG_LIST,
dataList: DATA_LIST
};
}, [count, handleAction]);
// ⑦ 渲染原始组件(透传改造后的 props)
return <OriginalComponent title={title} maxCount={maxCount} />;
}
);
export default WrappedComponent;
第 5 步:适配构建输出
构建产物必须符合以下要求:
- 输出格式:IIFE(立即执行函数),全局变量名为
UserComponent - 外部化依赖:React 和 ReactDOM 必须外部化,不打包进产物
- 单文件输出:最终产物为单个 JS 文件 + 可选的 CSS 文件
Vite 构建配置示例:
// vite.config.js
export default {
build: {
lib: {
entry: 'src/index.tsx',
name: 'UserComponent',
formats: ['iife'],
fileName: () => 'index.js'
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM'
}
}
}
}
};
第 6 步:验证清单
改造完成后,逐项检查:
- 组件使用
forwardRef<AxureHandle, AxureProps>包装 -
useImperativeHandle正确暴露了所有 5 个列表(eventList, actionList, varList, configList, dataList) -
export default导出封装后的组件 - 所有 event payload 都是 字符串类型
- 所有 action params 都是 字符串类型
- VAR_LIST 中的 name 使用 snake_case
- CONFIG_LIST 中的每个项都有
type、attributeId、displayName、initialValue - props 解构使用防御性编程(
innerProps && innerProps.xxx),不使用 ES6 解构 - config 值读取使用类型检查,不使用
||运算符(避免0/false被误判) - 构建产物格式为 IIFE,全局变量名为
UserComponent - React/ReactDOM 已外部化,不重复打包
改造示例
示例 1:简单展示组件
改造前(原始 React 组件):
function StatusCard({ title, status, color }) {
return (
<div style={{ borderLeft: `4px solid ${color}` }}>
<h3>{title}</h3>
<span>{status}</span>
</div>
);
}
改造后(Axure 兼容组件):
import React, { forwardRef, useImperativeHandle } from 'react';
import type { AxureProps, AxureHandle, EventItem, Action, KeyDesc, ConfigItem, DataDesc } from './path-to/axure-types';
const EVENT_LIST: EventItem[] = [];
const ACTION_LIST: Action[] = [];
const VAR_LIST: KeyDesc[] = [
{ name: 'current_status', desc: '当前状态文本' }
];
const CONFIG_LIST: ConfigItem[] = [
{ type: 'input', attributeId: 'title', displayName: '标题', initialValue: '状态卡片' },
{ type: 'input', attributeId: 'status', displayName: '状态', initialValue: '正常' },
{ type: 'colorPicker', attributeId: 'color', displayName: '状态颜色', initialValue: '#52c41a' }
];
const DATA_LIST: DataDesc[] = [];
const Component = forwardRef<AxureHandle, AxureProps>(function StatusCard(innerProps, ref) {
const configSource = innerProps && innerProps.config ? innerProps.config : {};
const title = typeof configSource.title === 'string' && configSource.title ? configSource.title : '状态卡片';
const status = typeof configSource.status === 'string' && configSource.status ? configSource.status : '正常';
const color = typeof configSource.color === 'string' && configSource.color ? configSource.color : '#52c41a';
useImperativeHandle(ref, function () {
return {
getVar: function (name: string) { return name === 'current_status' ? status : undefined; },
fireAction: function () {},
eventList: EVENT_LIST,
actionList: ACTION_LIST,
varList: VAR_LIST,
configList: CONFIG_LIST,
dataList: DATA_LIST
};
}, [status]);
return (
<div style={{ borderLeft: '4px solid ' + color, padding: '12px' }}>
<h3>{title}</h3>
<span>{status}</span>
</div>
);
});
export default Component;
示例 2:交互型组件(带事件和动作)
改造前:
function Counter({ initial = 0, onCountChange }) {
const [count, setCount] = useState(initial);
return (
<div>
<span>{count}</span>
<button onClick={() => { setCount(c => c + 1); onCountChange?.(count + 1); }}>+1</button>
<button onClick={() => setCount(0)}>重置</button>
</div>
);
}
改造后:
import React, { forwardRef, useImperativeHandle, useState, useCallback } from 'react';
import type { AxureProps, AxureHandle, EventItem, Action, KeyDesc, ConfigItem, DataDesc } from './path-to/axure-types';
const EVENT_LIST: EventItem[] = [{ name: 'onCountChange', desc: '计数改变时触发' }];
const ACTION_LIST: Action[] = [
{ name: 'increment', desc: '计数 +1' },
{ name: 'reset', desc: '重置计数' },
{ name: 'setCount', desc: '设置计数值,参数:数字字符串', params: 'number string' }
];
const VAR_LIST: KeyDesc[] = [{ name: 'count', desc: '当前计数值' }];
const CONFIG_LIST: ConfigItem[] = [
{ type: 'inputNumber', attributeId: 'initial', displayName: '初始值', initialValue: 0, min: 0 }
];
const DATA_LIST: DataDesc[] = [];
const Component = forwardRef<AxureHandle, AxureProps>(function Counter(innerProps, ref) {
const configSource = innerProps && innerProps.config ? innerProps.config : {};
const onEventHandler = typeof innerProps.onEvent === 'function'
? innerProps.onEvent : function () {};
const initial = typeof configSource.initial === 'number' ? configSource.initial : 0;
const countState = useState(initial);
const count = countState[0];
const setCount = countState[1];
const emitEvent = useCallback(function (eventName: string, payload?: string) {
try { onEventHandler(eventName, payload); } catch (e) { console.warn(e); }
}, [onEventHandler]);
const increment = useCallback(function () {
setCount(function (prev) {
var next = prev + 1;
emitEvent('onCountChange', String(next));
return next;
});
}, [emitEvent]);
const reset = useCallback(function () {
setCount(initial);
emitEvent('onCountChange', String(initial));
}, [initial, emitEvent]);
const handleAction = useCallback(function (name: string, params?: string) {
switch (name) {
case 'increment': increment(); break;
case 'reset': reset(); break;
case 'setCount':
if (params) {
var n = parseInt(params, 10);
if (!isNaN(n)) { setCount(n); emitEvent('onCountChange', String(n)); }
}
break;
}
}, [increment, reset, emitEvent]);
useImperativeHandle(ref, function () {
return {
getVar: function (name: string) { return name === 'count' ? count : undefined; },
fireAction: handleAction,
eventList: EVENT_LIST,
actionList: ACTION_LIST,
varList: VAR_LIST,
configList: CONFIG_LIST,
dataList: DATA_LIST
};
}, [count, handleAction]);
return (
<div>
<span>{count}</span>
<button onClick={increment}>+1</button>
<button onClick={reset}>重置</button>
</div>
);
});
export default Component;
示例 3:数据驱动型组件
改造前:
function UserList({ users, onSelect }) {
return (
<ul>
{users.map(u => (
<li key={u.id} onClick={() => onSelect(u)}>
{u.name} - {u.email}
</li>
))}
</ul>
);
}
改造后:
import React, { forwardRef, useImperativeHandle, useState, useCallback } from 'react';
import type { AxureProps, AxureHandle, EventItem, Action, KeyDesc, ConfigItem, DataDesc } from './path-to/axure-types';
const EVENT_LIST: EventItem[] = [{ name: 'onSelect', desc: '选中用户时触发,payload 为用户 JSON' }];
const ACTION_LIST: Action[] = [{ name: 'clearSelection', desc: '清除选中' }];
const VAR_LIST: KeyDesc[] = [{ name: 'selected_user_id', desc: '当前选中的用户 ID' }];
const CONFIG_LIST: ConfigItem[] = [
{ type: 'input', attributeId: 'emptyText', displayName: '空状态文本', initialValue: '暂无数据' }
];
const DATA_LIST: DataDesc[] = [
{
name: 'users',
desc: '用户列表数据',
keys: [
{ name: 'id', desc: '用户 ID' },
{ name: 'name', desc: '用户姓名' },
{ name: 'email', desc: '用户邮箱' }
]
}
];
const Component = forwardRef<AxureHandle, AxureProps>(function UserList(innerProps, ref) {
const dataSource = innerProps && innerProps.data ? innerProps.data : {};
const configSource = innerProps && innerProps.config ? innerProps.config : {};
const onEventHandler = typeof innerProps.onEvent === 'function'
? innerProps.onEvent : function () {};
const emptyText = typeof configSource.emptyText === 'string' && configSource.emptyText
? configSource.emptyText : '暂无数据';
var users = Array.isArray(dataSource.users) ? dataSource.users : [];
const selectedState = useState<string | null>(null);
const selectedId = selectedState[0];
const setSelectedId = selectedState[1];
const emitEvent = useCallback(function (eventName: string, payload?: string) {
try { onEventHandler(eventName, payload); } catch (e) { console.warn(e); }
}, [onEventHandler]);
const handleAction = useCallback(function (name: string) {
if (name === 'clearSelection') setSelectedId(null);
}, []);
useImperativeHandle(ref, function () {
return {
getVar: function (name: string) { return name === 'selected_user_id' ? selectedId : undefined; },
fireAction: handleAction,
eventList: EVENT_LIST,
actionList: ACTION_LIST,
varList: VAR_LIST,
configList: CONFIG_LIST,
dataList: DATA_LIST
};
}, [selectedId, handleAction]);
if (users.length === 0) return <div>{emptyText}</div>;
return (
<ul>
{users.map(function (u: any) {
return (
<li key={u.id} onClick={function () {
setSelectedId(u.id);
emitEvent('onSelect', JSON.stringify(u));
}}>
{u.name} - {u.email}
</li>
);
})}
</ul>
);
});
export default Component;
常见问题
Q: 组件使用了图表库(ECharts、D3)等直接操作 DOM 的库怎么办?
使用 innerProps.container 获取 DOM 容器,在 useEffect 中初始化图表引擎:
const container = innerProps && innerProps.container ? innerProps.container : null;
useEffect(function () {
if (!container) return;
const chart = echarts.init(container);
chart.setOption({ /* ... */ });
return function () { chart.dispose(); };
}, [container]);
return null; // 直接使用 container 渲染时返回 null
Q: 组件的 props 很复杂(嵌套对象),如何映射到 CONFIG_LIST?
- 扁平化:将嵌套属性用
attributeId的点分隔路径表示,如'style.fontSize' - 分组:使用
type: 'group'或type: 'collapse'组织相关配置项 - 复杂结构:使用
table或map类型编辑器
Q: 原始组件用了 props 解构、箭头函数,需要改写吗?
建议改写以提升兼容性:
- 使用
function关键字替代箭头函数 - 使用
innerProps && innerProps.xxx替代解构(防御性编程) - 使用
useState返回值的数组索引访问而非解构
Q: 如何处理组件的样式?
- 将 CSS 放在同目录的
style.css中并import './style.css' - 使用带组件前缀的类名避免全局冲突(如
my-chart-container) - 避免全局样式污染
类型参考
完整的类型定义见本技能的 references/ 目录:
-
references/axure-types.ts:核心接口和工具函数AxureProps:组件 Props 接口(data、config、onEvent、container)AxureHandle:组件 Handle 接口(getVar、fireAction、5 个列表)EventItem:事件定义{ name, desc, payload? }Action:动作定义{ name, desc, params? }KeyDesc:变量/数据字段定义{ name, desc }(name 必须 snake_case)DataDesc:数据源定义{ name, desc, keys: KeyDesc[] }ConfigItem:配置面板项定义safeEmitEvent、createEventEmitter:事件触发工具函数getConfigValue、getDataValue:安全取值工具函数
-
references/config-panel-types.ts:配置面板组件详细类型InputConfig、InputNumberConfig、CheckboxConfig、SliderConfigSelectConfig、AutoCompleteConfig、ColorPickerConfigArrayDataConfig、TableConfig、MapConfigFontSettingConfig、LineSettingConfig、PointSettingConfigGroupConfig:分组布局
More from lintendo/axhub-skills
extract-axure-data
Extract structured data from Axure prototypes using Playwright — screenshots, design tokens, interaction maps, annotations, and page text. Use this skill whenever the user wants to extract data from an Axure prototype, reconstruct or clone an Axure page, analyze Axure design tokens, export Axure content, or work with Axure prototype URLs (even if they don't say "Axure" explicitly — look for AxShare links, /start.html#p= URLs, or mentions of wireframes/prototypes hosted online).
41clone-page
>
36generate-theme
>
35genie-editor-workflow
通过 Axhub AI Extension(Chrome 扩展)配合 @axhub/genie CLI 实现网页编辑器待办的完整闭环处理流程。引导用户安装扩展和检测连接状态、发现在线编辑器客户端、读取用户标注和待办节点、导出上下文截图和图片、设置编辑状态(editing/idle/completed/error 四状态生命周期)、完成代码修改并回写终态。支持 CLI 自动重试、指数退避重连、标签页可见性感知、任务状态持久化等通信稳定性保障。当用户通过 Genie Editor 标注了网页修改待办并希望 AI 代为处理时使用。
33mcp-installer
Install and configure MCP servers across desktop and CLI clients (Claude, Cline, Windsurf, Cursor, VSCode, Gemini CLI, Codex, Trae, Antigravity, etc.) on macOS/Windows/Linux, preferring @smithery/cli when supported and otherwise performing manual JSON config updates and path discovery.
32react-to-figma-make
将任意 React 组件或应用转换为可在 Figma Make 中编辑和导出的标准项目结构。适用于已有 React 项目需要导入 Figma Make 进行可视化编辑、团队协作或产出 .fig 文件的场景。当用户提到将 React 转为 Figma、导出 Figma Make、生成 .fig 文件、或希望在 Figma Make 中编辑现有 React 代码时使用。
32