在 react 中单独使用 kityformula-editor

准备(使用kityformula-editor 必要的文件)

kityformula-editor

官网:fex-team.github.io/kityformula... (没什么参考价值,可以参考这个

仓库地址:github.com/fex-team/kf...

  1. s: 文件 /lib 中的 所有文件;

注意:需要额外添加 jquery.js

  1. 静态资源:css、img等

demo 放在下面了,可以参考

字体文件:

resource.zip

KityFormula-react-demo.zip

在 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 组件

  1. 创建 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 辅助,也是看了 两天。

相关推荐
好奇的候选人面向对象3 小时前
基于 Element Plus 的 TableColumnGroup 组件使用说明
开发语言·前端·javascript
送鱼的老默3 小时前
学习笔记-JavaScript的原型和原型链
javascript
小纯洁w3 小时前
vue3.0 使用el-tree节点添加自定义图标造成加载缓慢的多种解决办法
前端·javascript·vue.js
老前端的功夫4 小时前
ES6 模块 vs CommonJS:从历史背景到引擎实现的深度解析
前端·javascript
colorFocus4 小时前
大数据量计算时的延迟统一处理
前端·javascript
San304 小时前
在浏览器中运行AI模型:用Brain.js实现前端智能分类
前端·javascript·机器学习
小高0074 小时前
从npm run build到线上部署:前端人必会的CI/CD套路
前端·javascript·面试
古一|4 小时前
ES6(ECMAScript 2015)完全指南:从基础特性到异步解决方案(附实战)
javascript·es6
trsoliu4 小时前
React 19正式发布:引入React Compiler与全新并发特性
前端·react.js