BaseChart的实现
react-vchart中所有图表的封装都是通过高阶组件createChart来实现的,接下来我们以<VChart />的实现来详细解读一下实现原理
1.1
<VChart ``/``>的封装如下:
export const VChart = createChart<VChartProps>('VChart', {
vchartConstrouctor: VChartCore
});
这段代码比较简单,包含了如下内容:
-
使用
createChart工厂函数创建组件 -
指定组件名为
VChart -
注入
VChartCore构造器
1.2 基础图表实现 (BaseChart.tsx)
1.2.1 状态管理
const [updateId, setUpdateId] = useState<number>(0);
const chartContext = useRef<ChartContextType>({});
const [view, setView] = useState<IView>(null);
const isUnmount = useRef<boolean>(false);
关键状态:
-
updateId:用于控制子组件的更新。 -
chartContext:用于存储图表实例。 -
view:用于存储视图实例。 -
isUnmount:用于控制组件的卸载状态。
1.2.2 Spec 解析系统
const parseSpec = (props: Props) => { let spec: ISpec; // 1. 处理直接传入的 spec if (hasSpec && props.spec) { spec = props.spec; if (isValid(props.data)) { spec = { ...props.spec, data: props.data }; } } // 2. 处理从子组件收集的 spec else { spec = { ...prevSpec.current, ...specFromChildren.current }; } // 3. 处理 tooltip const tooltipSpec = initCustomTooltip(setTooltipNode, props, spec.tooltip); if (tooltipSpec) { spec.tooltip = tooltipSpec; } return spec; };
Spec 解析系统包括三个层次:
-
直接传入的 spec 配置。
-
子组件配置的聚合。
-
特殊组件(如 tooltip)的处理。
1.2.3 子组件解析系统
const parseSpecFromChildren = (props: Props) => { const specFromChildren: Omit<ISpec, 'type' | 'data' | 'width' | 'height'> = {}; toArray(props.children).map((child, index) => { const parseSpec = child?.type?.parseSpec; if (parseSpec && child.props) { // 处理子组件配置... const specResult = parseSpec(childProps); // 处理单例和数组配置 if (specResult.isSingle) { specFromChildren[specResult.specName] = specResult.spec; } else { if (!specFromChildren[specResult.specName]) { specFromChildren[specResult.specName] = []; } specFromChildren[specResult.specName].push(specResult.spec); } } }); return specFromChildren; };
该系统的职责包括:
-
收集所有子组件的配置。
-
区分单例和数组类型的配置。
-
生成最终的配置对象。
1.2.4 更新机制
useEffect(() => { // 1. 首次渲染 if (!chartContext.current?.chart) { createChart(props); renderChart(); return; } // 2. spec 更新 if (hasSpec) { if (!isEqual(eventsBinded.current.spec, props.spec)) { chartContext.current.chart.updateSpecSync(parseSpec(props)); handleChartRender(true); } // 3. 数据更新 else if (eventsBinded.current.data!== props.data) { chartContext.current.chart.updateFullDataSync(props.data); handleChartRender(true); } return; } // 4. 子组件更新 const newSpec = pickWithout(props, notSpecKeys); if (!isEqual(newSpec, prevSpec.current)) { // 更新处理... } }, [props]);
更新机制涵盖以下四种情况:
-
首次渲染
-
spec 配置更新
-
数据更新
-
子组件引起的更新
1.2.4 生命周期管理
useEffect(() => { return () => { if (chartContext.current?.chart) { chartContext.current.chart.release(); chartContext.current.chart = null; } eventsBinded.current = null; isUnmount.current = true; }; }, []);
组件销毁的时候,确保资源都能够释放,包括:
-
释放图表实例
-
清理事件绑定
-
更新组件状态
BaseComponent 的实现
2.1 核心实现机制
在 react-vchart 框架中,所有组件 的创建均依赖于 createComponent 这一工厂函数。该函数的定义如下:
const createComponent = <T extends ComponentProps>(
componentName: string, // 组件名
specName: string, // 规格名称
supportedEvents?: Record<string, string>, // 支持的事件
isSingle?: boolean, // 是否单例
registers?: (() => void)[] // 注册器
): any => {
// ...组件创建逻辑
};
这里通过泛型 <T extends ComponentProps> 来约束传入的组件属性类型。函数接收多个参数:
-
componentName:用于标识组件的名称,在整个应用中具有唯一性,方便开发者识别和管理组件。 -
specName:代表组件对应的规格名称,这对于配置收集和管理非常重要,不同的组件通过不同的规格名称来区分各自的配置。 -
supportedEvents:是一个可选的对象,用于定义组件支持的事件。对象的键值对形式表示事件类型和对应的事件处理逻辑。例如,一个组件可能支持'click'事件,并定义了相应的处理函数。 -
isSingle:布尔值,用于指示组件是否为单例模式。如果为true,则表示在整个应用中该组件只会有一个实例;如果为false,则可以创建多个实例。 -
registers:是一个函数数组,每个函数用于执行特定的注册操作。这些注册操作可能包括向框架中注册组件的特定功能或插件等。
为了更直观地理解,下面以轴组件和图例组件为例展示封装代码:
- 坐标轴
export const Axis = createComponent<AxisProps>('Axis', 'axes');