Overview
This skill will help you transformany React component or applicationinto a standard component that can be imported into the Axure prototyping platform. After the transformation is complete, the component will have:
- Configuration Panel: Provides visual configuration items (colors, text, numeric values, etc.) in the Axure editor
- Event System: Sends custom events to the Axure prototype
- Action System: Responds to external calls from the Axure prototype
- Variable Exposure: Exposes internal state for the Axure prototype to read
- Data Access: Receives external data sources from the Axure prototype
This skill produces two types of artifacts:
- Configuration Declarations: 5 groups of constants (EVENT_LIST / ACTION_LIST / VAR_LIST / CONFIG_LIST / DATA_LIST)
- Runtime Wrapper: Uses
forwardRef+useImperativeHandlewrapped component code
At the same time, Axure export artifacts intended for consumption by host tools are written by default to the general artifact directory:
.axhub/make/artifacts/axure/<resource-id>/
├── index-bundle.json
├── axure-json.json
├── cover.svg
└── manifest.json
If the target project uses .axhub/make/project.json to describe resources, these paths need to be registered in the corresponding resources.prototypes[].artifacts.axure so that make-server or other hosts can directly read, copy, and download them.
When to Use
- You have an existing React component and want to use it in an Axure prototype
- Your React application needs to interact bidirectionally with the Axure prototype system
- You need to generate a standardized configuration panel definition for a React component
Referenced Files
This skill comes with complete type definition files that can be copied directly into the target project for use:
references/axure-types.ts: Core interfaces (AxureProps, AxureHandle) and utility functionsreferences/config-panel-types.ts: Complete type definitions for configuration panel components
.axhub/make/project.json Example of resource metadata in :
{
"artifacts": {
"axure": {
"caseId": "home",
"manifestPath": ".axhub/make/artifacts/axure/home/manifest.json",
"indexBundlePath": ".axhub/make/artifacts/axure/home/index-bundle.json",
"axureJsonPath": ".axhub/make/artifacts/axure/home/axure-json.json",
"coverSvgPath": ".axhub/make/artifacts/axure/home/cover.svg"
}
}
}
Transformation Workflow
Step 1: Analyze the Source Component
Analyze the following aspects of the target React component:
- Props Interface: Which props does the component currently receive?
- Internal State: Which useState / useReducer states need to be exposed?
- Interaction Behavior: Which user interaction events are there (click, change, submit, etc.)?
- External Dependencies: Which third-party libraries does the component depend on?
- DOM Manipulation: Does it directly manipulate the DOM (such as chart libraries, map libraries)?
Output a transformation checklist listing the props → config, state → var, callback → event/action mappings that need to be made.
Step 2: Copy the Type Definitions
Copy references/axure-types.ts to a public directory of the target project (such as src/common/axure-types.ts). If the detailed types for the configuration panel are needed, also copy references/config-panel-types.ts。
Step 3: Define the Interface Layer
Based on the analysis results, generate the following 5 groups of constant definitions.
3.1 Event List EVENT_LIST
Define the events that the component can trigger externally:
import type { EventItem } from './path-to/axure-types';
const EVENT_LIST: EventItem[] = [
{ name: 'onClick', desc: '点击时触发' },
{ name: 'onChange', desc: '值改变时触发,payload 为新值的 JSON 字符串' }
];
Rules:
name: Event name, camelCase is recommendeddesc: Event description, explaining the trigger timingpayload: Optional, describes the payload format (payload must be of string type; complex data requiresJSON.stringify)
3.2 Action list ACTION_LIST
Defines the external calls that the component can respond to:
import type { Action } from './path-to/axure-types';
const ACTION_LIST: Action[] = [
{ name: 'reset', desc: '重置组件到初始状态' },
{ name: 'setValue', desc: '设置值,参数格式:JSON 字符串', params: 'JSON string' }
];
Rules:
paramsmust be of string type; complex parameters requireJSON.parseparsingdescshould describe the parameter format
3.3 Variable list VAR_LIST
Defines the internal state exposed by the component:
import type { KeyDesc } from './path-to/axure-types';
const VAR_LIST: KeyDesc[] = [
{ name: 'current_value', desc: '当前值' },
{ name: 'is_loading', desc: '是否加载中' }
];
Rules:
namemust use snake_case(lowercase letters + digits + underscores)- Only expose state that the outside needs to read; do not expose internal implementation details
3.4 Configuration item list CONFIG_LIST
Defines the configurable items in the configuration panel:
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' }
]
}
];
Available type(see detailed definitions in references/config-panel-types.ts):
| type | Purpose | initialValue type |
|---|---|---|
input |
Text input | string |
inputNumber |
Number input (supports min/max/step) | number |
checkbox |
Checkbox | boolean |
switch |
Switch | boolean |
slider |
Slider (requires min/max) | number |
select |
Dropdown select (requires options) | string | number |
colorPicker |
Color picker | string(HEX format) |
autoComplete |
Autocomplete input | string |
arrayData |
Array data editor | string[] |
table |
Table data editor | any[] |
map |
Key-value pair editor | Record<string, any> |
fontSetting |
Font settings (color/size/weight, etc.) | Object |
lineSetting |
Line settings | Object |
group |
Group container (contains children) | — |
collapse |
Collapsible panel container | — |
Mapping strategy (original Props → ConfigItem):
| Original Props type | Recommended ConfigItem type |
|---|---|
string |
input |
number |
inputNumber |
boolean |
switch or checkbox |
enum / union |
select(map enum values to options) |
| Color string | colorPicker |
string[] |
arrayData |
| Complex object/array | table or map |
3.5 Data item list DATA_LIST
Defines the external data structure received by the component:
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: '值' }
]
}
];
Applicable scenarios: Suitable for data-driven components such as lists and tables. Purely presentational or configuration-driven components can be set to an empty array []。
Step 4: Wrap the component
Wrap the original component with 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;
Step 5: Adapt the build output
The build artifact must meet the following requirements:
- Output format: IIFE (Immediately Invoked Function Expression), with the global variable name
UserComponent - Externalized dependencies: React and ReactDOM must be externalized and not bundled into the artifact
- Single-file output: The final artifact is a single JS file + an optional CSS file
Vite build configuration example:
// 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'
}
}
}
}
};
Step 6: Verification checklist
After the refactoring is complete, check item by item:
- The component uses
forwardRef<AxureHandle, AxureProps>wrapper -
useImperativeHandlecorrectly exposes all 5 lists (eventList, actionList, varList, configList, dataList) -
export defaultexports the wrapped component - All event payloads are string type
- All action params are string type
- The name in VAR_LIST uses snake_case
- Each item in CONFIG_LIST has
type、attributeId、displayName、initialValue - props destructuring uses defensive programming (
innerProps && innerProps.xxx), does not use ES6 destructuring - config value reading uses type checking, does not use the
||operator (avoids0/falsebeing misjudged) - The build output format is IIFE, the global variable name is
UserComponent - React/ReactDOM are externalized, not bundled again
Conversion Examples
Example 1: Simple display component
Before conversion(original React component):
function StatusCard({ title, status, color }) {
return (
<div style={{ borderLeft: `4px solid ${color}` }}>
<h3>{title}</h3>
<span>{status}</span>
</div>
);
}
After conversion(Axure-compatible component):
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;
Example 2: Interactive component (with events and actions)
Before conversion:
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>
);
}
After conversion:
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;
Example 3: Data-driven component
Before conversion:
function UserList({ users, onSelect }) {
return (
<ul>
{users.map(u => (
<li key={u.id} onClick={() => onSelect(u)}>
{u.name} - {u.email}
</li>
))}
</ul>
);
}
After conversion:
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;
Frequently Asked Questions
Q: What should I do if the component uses chart libraries (ECharts, D3) or other libraries that directly manipulate the DOM?
Use innerProps.container to get the DOM container, and initialize the chart engine in 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: The component's props are very complex (nested objects). How do I map them to CONFIG_LIST?
- Flatten: represent nested properties using
attributeIddot-separated paths, such as'style.fontSize' - Group: use
type: 'group'ortype: 'collapse'to organize related configuration items - Complex structures: use
tableormaptype editors
Q: The original component used props destructuring and arrow functions. Do they need to be rewritten?
It is recommended to rewrite them to improve compatibility:
- Use the
functionkeyword instead of arrow functions - Use
innerProps && innerProps.xxxinstead of destructuring (defensive programming) - Use
useStatearray index access for return values instead of destructuring
Q: How do I handle component styles?
- Put CSS in the
style.cssin the same directory andimport './style.css' - use class names with a component prefix to avoid global conflicts (such as
my-chart-container) - avoid global style pollution
Type Reference
For the complete type definitions, see this skill's references/ directory:
-
references/axure-types.ts: core interfaces and utility functionsAxureProps: component Props interfaces (data, config, onEvent, container)AxureHandle: component Handle interfaces (getVar, fireAction, 5 lists)EventItem: event definitions{ name, desc, payload? }Action: action definitions{ name, desc, params? }KeyDesc: variable/data field definitions{ name, desc }(name must be snake_case)DataDesc: data source definitions{ name, desc, keys: KeyDesc[] }ConfigItem: configuration panel item definitionssafeEmitEvent、createEventEmitter: event triggering utility functionsgetConfigValue、getDataValue: safe value access utility functions
-
references/config-panel-types.ts: detailed types for configuration panel componentsInputConfig、InputNumberConfig、CheckboxConfig、SliderConfigSelectConfig、AutoCompleteConfig、ColorPickerConfigArrayDataConfig、TableConfig、MapConfigFontSettingConfig、LineSettingConfig、PointSettingConfigGroupConfig: group layout