准备(使用kityformula-editor 必要的文件)
kityformula-editor
官网:fex-team.github.io/kityformula... (没什么参考价值,可以参考这个)
仓库地址:github.com/fex-team/kf...
- s: 文件 /lib 中的 所有文件;
注意:需要额外添加 jquery.js 
- 静态资源:css、img等

demo 放在下面了,可以参考
字体文件:
在 react 中引入 kityformula
在项目的根目录下,创建public/kityformula ,将上述的资源文件放进去


使用
动态引入资源 js 文件,注意引入顺序
- 注意,一定引入 jQuery 库
kitygraph -> kity-formula-render -> kity-formula-parser -> kityformula-editor
创建工具函数,用于按照顺序动态引入 文件
挂载编辑器 将 创建 编辑器所需的方法 挂载与 window中
如: kity、kf
javascript
// 等待条件成立的工具函数
const waitFor = (conditionFn, interval = 50) =>
new Promise((resolve) => {
const timer = setInterval(() => {
if (conditionFn()) {
clearInterval(timer);
resolve();
}
}, interval);
});
export default async () => {
// 动态加载脚本的工具函数
const loadScript = (src) =>
new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = src;
script.async = false;
script.defer = false;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
try {
// 1. 加载 jQuery
if (!window.jQuery) {
await loadScript("/kityformula/js/jquery.min.js");
console.log("jQuery 加载完毕");
}
// 2. 加载 kitygraph
await loadScript("/kityformula/js/kitygraph.all.js");
console.log("kitygraph 加载完毕");
await waitFor(() => window.kity);
// 3. 加载 render 和 parser
await loadScript("/kityformula/js/kity-formula-render.all.js");
await loadScript("/kityformula/js/kity-formula-parser.all.min.js");
console.log("render 和 parser 加载完毕");
// 4. 加载 editor
await loadScript("/kityformula/js/kityformula-editor.all.min.js");
console.log("editor 加载完毕");
// 5. 动态引入样式
const styles = [
"/kityformula/assets/styles/base.css",
"/kityformula/assets/styles/page.css",
"/kityformula/assets/styles/ui.css",
"/kityformula/assets/styles/dialogbase.css",
];
styles.forEach((href) => {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = href;
document.head.appendChild(link);
});
await waitFor(() => window.kf && window.kf.EditorFactory);
// ✅ 修复: 防止空公式状态下 objTree 为 null 报错
if (window.kf?.SyntaxComponenet?.prototype?.hasRootplaceholder) {
const original = window.kf.SyntaxComponenet.prototype.hasRootplaceholder;
window.kf.SyntaxComponenet.prototype.hasRootplaceholder = function (
...args
) {
if (!this.objTree || !this.objTree.mapping) {
return false; // 跳过
}
return original.apply(this, args);
};
console.log("✅ KityFormula Patch: hasRootplaceholder 防空指针修复");
}
} catch (err) {
console.error("初始化失败", err);
}
};
创建 KityFormula 组件
- 创建 kityformula-editor 编辑器 初始化 方法 (用户 给编辑器 赋 初始值 以及 点击 进入编辑状态)
javascript
// editorRef 为 kityformula-editor 编辑器 的实例 , 见下文
const init_editor = () => {
// 延迟尝试创建编辑器,确保 DOM 和 kf 已就绪
const tryCreateEditor = () => {
if (
!editorRef.current &&
window.kf?.EditorFactory &&
containerRef.current
) {
editorRef.current = window.kf.EditorFactory.create(
containerRef.current,
{
render: {
fontsize: 24,
},
resource: {
path: "/kityformula/resource/", // 加载字体资源
},
}
);
setTimeout(() => {
const editor = editorRef.current?.editor; // KFEditor 实例
if (editor && editor.execCommand) {
// 公式回填 value 为传入 该组件的 公式: string 用于公式回填,编辑公式
editor.execCommand("render", value ? value : "\placeholder");
editor.execCommand("focus");
console.log("✅ 插入占位公式并聚焦");
} else {
console.warn("⚠️ KFEditor 未准备好");
}
}, 200);
} else if (!editorRef.current) {
setTimeout(tryCreateEditor, 50);
}
};
tryCreateEditor();
};
组件初始化时 调用工具函数,引入 kityformula-editor。
javascript
//三方
import { useEffect } from 'React'
// utils
import init_kityformula_editor from './utils'
const KityFormula = props => {
const containerRef = useRef(null); // 渲染 编辑器 的DOM,编辑器创建好之后,或将其渲染到该 DOM 中
const editorRef = useRef(null); // 用于绑定 编辑器 的实例,编辑器的 各种方法属性 都在该实例中
useEffect(() => {
// 延时调用, 确保 DOM 已经挂载
const timer = setTimeout(() => {
// 挂载 编辑器
init_kityformula();
// 初始化 编辑器
init_editor();
}, 100);
// 组件卸载 卸载实例
return () => {
clearTimeout(timer);
if (editorRef.current?.dispose) editorRef.current.dispose();
};
}, [])
return (
<div style={{ padding: 20 }}>
<div
ref={containerRef}
className="my-kf-editor"
/>
</div>
)
}
最重要的一点,向外暴漏(创建)导出图片,公式 string 的方法
kityformula-editor 中所有的(大部分这个逻辑)方法调用都是这个逻辑
kityformula-editor 暴露的方法 中的 execFn为一个 对象方法, 其本身是一个 方法, 同时还包括一个属性方法 apply(正式使用时调用的方法)
其参数 thisArg 为一个必填的,kityformula-editor方法对象中 (cmd)中的 executor
typescript
// 从实例对象中找到所需的方法
// 此处 editorRef.current 为实例对象
const cmds =
editorRef.current.commands ||
editorRef.current.editor?.commands ||
null;
// cmd 中的 execFn为一个 对象方法, 其本身是一个 方法, 同时还包括一个属性方法,apply
type TCmd<TArgs = any, TResult = any> = {
execFn: {
(...args: TArgs[]): TResult
apply(thisArg: any, args: TArgs[]): TResult
}
executor: object // 调用 apply 串的第一个参数
}
const cmd: TCmd= cmds['具体方法名']
这里我使用的是 react 中父组件调用 子组件的方法 所以用到了 useImperativeHandle、forwardRef
javascript
import { useImperativeHandle, forwardRef } from "react";
const KityFormula = forwardRef((props, ref) => {
/**
* kityformula 创建的公式 最终形式 是一个 字符串,kity 会将这个字符串转为 公式,
* 所以 公式的保存形式为 字符串;显示形式为 图片。
*/
// 向父组件暴露方法(替换原有 useImperativeHandle 块)
useImperativeHandle(ref, () => ({
// 返回 公式 字符串
getFormulaString: () => {
if (!editorRef.current) return "";
// cmds 中存储的 是具体的导出方案,但是因为方法入参需要传入一个 对象,所以现将其提取出来
// editorRef.current 可能是 KFEditor,也可能是一个 wrapper(带 .editor)
const cmds =
editorRef.current.commands ||
editorRef.current.editor?.commands ||
null;
// 下面为逻辑判断,避免报错
if (!cmds) {
console.warn("找不到 commands:", editorRef.current);
return "";
}
// 导出字符串方法 get.source
const cmd = cmds["get.source"] || cmds["getSource"] || null;
if (!cmd) {
console.warn("命令 get.source 不存在,available:", Object.keys(cmds));
return "";
}
// 最终导出 公式字符串
return cmd.execFn.apply(cmd.executor);
},
// 返回 Promise,resolve 为 base64 图片字符串或空串
getFormulaImage: () => {
if (!editorRef.current) return "";
//
const strFoormula = ref.current.getFormulaString()
if (!strFoormula || strFoormula === "placeholder" || strFoormula === "\placeholder ") {
console.warn('未输入公式!');
return
}
// editorRef.current 可能是 KFEditor,也可能是一个 wrapper(带 .editor)
const cmds =
editorRef.current.commands ||
editorRef.current.editor?.commands ||
null;
if (!cmds) {
console.warn("找不到 commands:", editorRef.current);
return "";
}
const cmd = cmds["get.image.data"] || cmds["getImageData"] || null;
if (!cmd) {
console.warn("命令 get.image.data 不存在,available:", Object.keys(cmds));
return "";
}
try {
return new Promise((resolve) => {
// 1) 先尝试以 callback 形式(大多数实现会走 callback)
let finished = false;
try {
const maybeReturn = cmd.execFn.apply(cmd.executor, [
(data) => {
finished = true;
console.log("get.image.data callback ->", data);
resolve(data?.img || "");
},
]);
// 2) 如果 execFn 同步返回了对象(某些实现),则直接用返回值
if (!finished && maybeReturn && typeof maybeReturn === "object") {
finished = true;
console.log("get.image.data returned sync ->", maybeReturn);
resolve(maybeReturn.img || "");
}
// 3) 兜底:如果既没回调也没返回(极少见),等待短时间后返回空串
setTimeout(() => {
if (!finished) {
console.warn("get.image.data 未在回调或返回中给出结果,返回空字符串。");
resolve("");
}
}, 200); // 200ms 兜底
} catch (e) {
console.error("调用 get.image.data 出错:", e);
// resolve("");
}
});
} catch (e) {
console.error("获取公式图片失败:", e);
return "";
}
},
}));
return (
<div style={{ padding: 20 }}>
<div
ref={containerRef}
className="my-kf-editor"
/>
</div>
);
});
export default KityFormula;
完整组件
javascript
import { useEffect, useRef, useImperativeHandle, forwardRef } from "react";
import "./index.less";
import { Button } from 'antd'
import init_kityformula from './utils/kityformula.js'
const KityFormula = forwardRef((props, ref) => {
const { data: value = null } = props
const containerRef = useRef(null);
const editorRef = useRef(null);
const init_editor = () => {
// 延迟尝试创建编辑器,确保 DOM 和 kf 已就绪
const tryCreateEditor = () => {
if (
!editorRef.current &&
window.kf?.EditorFactory &&
containerRef.current
) {
editorRef.current = window.kf.EditorFactory.create(
containerRef.current,
{
render: {
fontsize: 24,
},
resource: {
path: "/kityformula/assets/",
},
}
);
setTimeout(() => {
const editor = editorRef.current.editor; // KFEditor 实例
if (editor && editor.execCommand) {
// 公式回填
editor.execCommand("render", value ? value : "\placeholder");
editor.execCommand("focus");
console.log("✅ 插入占位公式并聚焦");
} else {
console.warn("⚠️ KFEditor 未准备好");
}
}, 200);
} else if (!editorRef.current) {
setTimeout(tryCreateEditor, 50);
}
};
tryCreateEditor();
};
// 向父组件暴露方法(替换原有 useImperativeHandle 块)
useImperativeHandle(ref, () => ({
// 返回 Promise,resolve 为 base64 图片字符串或空串
getFormulaImage: () => {
if (!editorRef.current) return "";
const strFoormula = ref.current.getFormulaString()
if (!strFoormula || strFoormula === "placeholder" || strFoormula === "\placeholder ") {
console.warn('未输入公式!');
return
}
// editorRef.current 可能是 KFEditor,也可能是一个 wrapper(带 .editor)
const cmds =
editorRef.current.commands ||
editorRef.current.editor?.commands ||
null;
if (!cmds) {
console.warn("找不到 commands:", editorRef.current);
return "";
}
const cmd = cmds["get.image.data"] || cmds["getImageData"] || null;
if (!cmd) {
console.warn("命令 get.image.data 不存在,available:", Object.keys(cmds));
return "";
}
try {
return new Promise((resolve) => {
// 1) 先尝试以 callback 形式(大多数实现会走 callback)
let finished = false;
try {
const maybeReturn = cmd.execFn.apply(cmd.executor, [
(data) => {
finished = true;
console.log("get.image.data callback ->", data);
resolve(data?.img || "");
},
]);
// 2) 如果 execFn 同步返回了对象(某些实现),则直接用返回值
if (!finished && maybeReturn && typeof maybeReturn === "object") {
finished = true;
console.log("get.image.data returned sync ->", maybeReturn);
resolve(maybeReturn.img || "");
}
// 3) 兜底:如果既没回调也没返回(极少见),等待短时间后返回空串
setTimeout(() => {
if (!finished) {
console.warn("get.image.data 未在回调或返回中给出结果,返回空字符串。");
resolve("");
}
}, 200); // 200ms 兜底
} catch (e) {
console.error("调用 get.image.data 出错:", e);
// resolve("");
}
});
} catch (e) {
console.error("获取公式图片失败:", e);
return "";
}
},
getFormulaString: () => {
if (!editorRef.current) return "";
// editorRef.current 可能是 KFEditor,也可能是一个 wrapper(带 .editor)
const cmds =
editorRef.current.commands ||
editorRef.current.editor?.commands ||
null;
if (!cmds) {
console.warn("找不到 commands:", editorRef.current);
return "";
}
const cmd = cmds["get.source"] || cmds["getSource"] || null;
if (!cmd) {
console.warn("命令 get.source 不存在,available:", Object.keys(cmds));
return "";
}
return cmd.execFn.apply(cmd.executor);
},
}));
useEffect(() => {
const timer = setTimeout(() => {
init_kityformula();
init_editor();
}, 100);
return () => {
clearTimeout(timer);
if (editorRef.current?.dispose) editorRef.current.dispose();
};
}, []);
return (
<div style={{ padding: 20 }}>
<div
ref={containerRef}
className="my-kf-editor"
/>
</div>
);
});
export default KityFormula;
总结
大概就是这样,这东西也没有一个文档,只能找看源码,看别人的 demo 打断点,看传参,结合 AI 辅助,也是看了 两天。