⚠️温馨提示: 文档中包含【2个】暂不支持的区域,请通过搜索关键字【暂不支持的文档区域】进行后续处理

feishu-editor-notes.zip

很多同学可能第一次来,我分享了很多干货公开课,希望对你的技术成长有帮助!免费分享都在这里,需要 文档和配套视频找咨询老师 领取~

【飞书文档】备战金九银十,进阶大前端,涨薪全突破! 跳槽涨薪需求同学必看

【飞书文档】项目没有难点亮点?字节大佬带你前端项目弯道超车

【飞书文档】字节大佬带你弯道超车,深入剖析大厂面试真题~

【飞书文档】面试被算法干吐了?字节大佬带你突破极限,冲击 30K+ 必掌握算法与 WebAssembly 技术 冲刺年包 50W+ 同学必看


【飞书文档】高级前端专家如何做性能优化?特邀字节大佬细数飞书应用优化细节【超详细内培版】

【飞书文档】高级前端专家如何做项目架构与工程化设计? 特邀字节大佬细数字节开源项目架构细节【超详细内培版】

【飞书文档】小小微前端,轻松拿捏,特邀字节大佬开讲微前端架构与源码剖析【内部培训版】 35K+ 同学必看

【飞书文档】高级前端专家如何做性能优化?特邀字节大佬细数飞书应用优化细节(二)【内部培训版】


【飞书文档】前端破局 AI 应用开发,特邀字节大佬分享字节系 AI 场景落地应用与 AI 引擎编排流程 探索前端新方向同学福音

【飞书文档】React18 源码深度剖析,字节面试官教你轻松拿捏高级前端专家面试框架原理题

【飞书文档】Vue3 源码深度剖析,字节面试官教你轻松拿捏高级前端专家面试框架原理题

【飞书文档】Vite 构建过程与源码深度剖析,你怎么也想不到一线大厂工程化构建面试会问这么深!

【飞书文档】脚手架/CLI 工具原理与开发实践,特邀字节前端专家带你体悟大厂团队基建与研发工作流 团队基建必看

【飞书文档】Webpack 原理深度解读与面试专项突击,字节面试官带你手撕难缠打包构建面试原理题 工程化与构建原理深入


【飞书文档】冲击中大厂筹备与涨薪突击最优方案,特邀字节面试官带你体验大厂面试全流程

【飞书文档】字节面试专场——中厂在职学员冲击大厂 30K+,看看面试官如何评价 大厂模拟面试,问得很深慎看

【飞书文档】前端性能与异常监控平台全链路设计与实践,字节架构师:掌握这整套拿个 40K+ 不在话下吧

  1. 编辑器底层基础处理
  2. 光标、框选、缓冲区等
  3. 编辑器编译与转换器——json2html、html2json 或 md2html、html2md
  4. 协同、冲突解决、undo/redo
  • 初中级 】有没有做过富文本编辑器开发?怎么实现的?
  • 中高级 】从零到一自研一个富文本编辑器,需要考虑哪些问题,怎么实现?
  • 专家级 】架构设计一个类似飞书文档、notion 的产品,并详细说明具体细节?

⚠️ 暂不支持的文档区域,【文档小组件】

  • Github copilot
  • Gemini
  • cursor

初中级 】有没有做过富文本编辑器开发?怎么实现的?

竞品介绍

  • 飞书文档Notion

    • 提供丰富的文本格式支持、多媒体嵌入、实时协作等强大功能。
    • 用户可以通过快捷命令(如 /@ )快速插入不同类型的块(Block),提高操作效率。
  • 其他常见的富文本编辑器

    • Google DocsMicrosoft Word Online :支持复杂的文档编辑功能,如文本格式化、插入图片、撤销重做、表格和绘图等。
    • QuillDraft.js :开源富文本编辑器,允许开发者在其基础上构建自定义的编辑器解决方案,支持插件扩展和丰富的 API。

富文本开发核心

  • 富文本编辑器的开发核心在于支持多样化的文本操作和功能,包括:

    • 文本格式化 :加粗、斜体、下划线、颜色、背景色等多种文本样式。
    • 多媒体插入 :图片、视频、文件、链接等。
    • 撤销重做 :用户在编辑过程中可以随时撤销和重做操作。
    • 跨浏览器兼容性 :在不同浏览器和平台上行为一致。
    • 协作功能 :多用户实时协作,显示协同光标和实时文档同步。
  • 实现这些功能的基础是使用 contenteditable 属性,使元素变为可编辑状态,并结合 JavaScript 监听用户操作。

contenteditable 基础

  • contenteditable 属性

    • 是 HTML 属性,可以使任何元素变为可编辑状态。通过设置 contenteditable="true" ,用户可以直接在元素内输入或删除文本。
    • 常用于构建富文本编辑器的编辑区域,简单高效。
  • 示例 :创建一个可编辑的 div 元素。

<div class="editor-content" contenteditable="true">
  <p>这是一个简单的富文本编辑器。</p>
</div>

-

  • 在 JavaScript 中,结合事件监听,实现输入事件的捕获和处理:
document.querySelector('.editor-content').addEventListener('input', function (e) {
  console.log('用户输入了内容:', e.target.innerHTML);
});

浏览器 execCommand (编辑器小白级别)

document.execCommand 是一种浏览器 API,用于在 HTML 文档中执行与文档内容相关的命令。该方法最初是为实现富文本编辑功能而设计的,可以让开发者轻松实现一些常见的编辑操作,如加粗文本、插入链接、剪切、复制、粘贴等。尽管这种方法在过去非常流行,但随着 Web 技术的发展,它的使用逐渐减少,并在一些现代浏览器中被标记为过时或即将废弃。

基本语法

document.execCommand(command, showUI, value);
  • command (字符串): 要执行的命令名称,如 'bold''italic''copy' 等。
  • showUI (布尔值): 指示是否显示默认的用户界面,通常传入 false ,因为许多命令都不支持或忽略这个参数。
  • value (字符串): 与某些命令一起使用的值,如在插入链接时的 URL。对于不需要值的命令,该参数可以省略或传入 null

常见的命令

以下是一些常见的命令及其用途:

  1. 'bold' : 使选中的文本加粗。
document.execCommand('bold');
  1. 'italic' : 使选中的文本倾斜。
document.execCommand('italic');
  1. 'underline' : 为选中的文本添加下划线。
document.execCommand('underline');
  1. 'copy' : 复制当前选中的内容到剪贴板。
document.execCommand('copy');
  1. 'cut' : 剪切当前选中的内容,并将其放入剪贴板。
document.execCommand('cut');
  1. 'paste' : 从剪贴板粘贴内容到光标所在的位置。
document.execCommand('paste');
  1. 'createLink' : 为选中的文本创建超链接。
document.execCommand('createLink', false, 'https://example.com');
  1. 'unlink' : 移除选中文本中的链接。
document.execCommand('unlink');

示例

下面是一个简单的示例,用于实现富文本编辑功能:

<div contenteditable="true" id="editor">
    这是一个可编辑的区域。
</div>
<button onclick="document.execCommand('bold')">加粗</button>
<button onclick="document.execCommand('italic')">倾斜</button>
<button onclick="document.execCommand('underline')">下划线</button>

重要提示 :尽管 document.execCommand 目前在许多浏览器中仍然可用,但建议开发者尽量避免在新项目中使用它,并逐步迁移到更现代、更可靠的替代方案。

  • document.execCommand API

    • 是一个用于执行编辑命令的旧版浏览器 API,例如加粗、斜体、删除等。它可以简化一些常见的编辑操作。

    • 常用命令包括:

      • document.execCommand('bold') :将选中的文本加粗。
      • document.execCommand('italic') :将选中的文本变为斜体。
      • document.execCommand('underline') :为选中的文本添加下划线。
  • 局限性

    • 兼容性问题 :该 API 在现代浏览器中的支持逐渐减少,部分功能在某些浏览器中可能表现不一致。
    • 灵活性不足 :无法实现复杂的格式和定制化需求。
  • 替代方案 :手动操作 DOM(使用 SelectionRange )来实现自定义的文本编辑功能。

SelectionRange

SelectionRange 是 Web API 中的两个重要接口,它们用于处理用户在网页中的文本或元素选择。这两个接口提供了对文档中选定内容的精确控制,可以用来创建富文本编辑器、处理用户选择、实现自定义的文本操作等。

Selection 接口

Selection 对象表示用户或脚本在文档中选中的文本范围。它通常与光标或鼠标选择操作相关联。

常用属性

  • anchorNode : 返回 Selection 的起始节点(锚点)。
  • anchorOffset : 返回 Selection 的起始节点内的偏移量。
  • focusNode : 返回 Selection 的结束节点(焦点)。
  • focusOffset : 返回 Selection 的结束节点内的偏移量。
  • isCollapsed : 如果选区的起始点和结束点在同一位置,则返回 true ,否则返回 false 。这意味着没有实际的文本被选中。
  • rangeCount : 返回当前选区中的 Range 对象的数量。通常是 1,但在某些浏览器中可以选择多个不连续的区域。

常用方法

  • getRangeAt(index) : 返回选区中的指定 Range 对象。通常使用 getRangeAt(0) 来获取当前选区。
  • addRange(range) : 向选区添加一个 Range 对象。如果当前已经存在选区,新的范围会与之合并。
  • removeAllRanges() : 移除所有选区。
  • collapse(node, offset) : 将选区折叠到文档中的一个特定点,即将选区的起点和终点设置为相同的值。
  • extend(node, offset) : 将当前选区的终点扩展到指定的节点和偏移量。
  • collapseToStart() : 将选区折叠到起始点。
  • collapseToEnd() : 将选区折叠到结束点。

示例

const selection = window.getSelection();
if (selection.rangeCount > 0) {
    const range = selection.getRangeAt(0);
    console.log(range.toString()); // 输出当前选中的文本
}

Range 接口

Range 对象表示文档中连续的一段内容。它可以包含部分或整个节点。 Range 允许开发者在文档中创建、修改、删除特定的选区,而不仅仅局限于用户选择的内容。

常用属性

  • startContainer : 返回 Range 起点所在的节点。
  • startOffset : 返回 Range 起点在 startContainer 内的偏移量。
  • endContainer : 返回 Range 终点所在的节点。
  • endOffset : 返回 Range 终点在 endContainer 内的偏移量。
  • collapsed : 如果 Range 的起点和终点在同一位置,则返回 true ,否则返回 false

常用方法

  • setStart(node, offset) : 设置 Range 的起点为指定节点的特定偏移量。
  • setEnd(node, offset) : 设置 Range 的终点为指定节点的特定偏移量。
  • selectNode(node) : 将 Range 设置为包含整个指定节点。
  • selectNodeContents(node) : 将 Range 设置为包含指定节点的所有内容。
  • collapse(toStart) : 将 Range 折叠到起点或终点。 toStarttrue 时折叠到起点,反之折叠到终点。
  • cloneRange() : 创建并返回一个与当前 Range 完全相同的副本。
  • deleteContents() : 删除 Range 包含的文档内容。
  • extractContents() : 删除 Range 的内容并返回一个 DocumentFragment ,其中包含了被删除的内容。
  • insertNode(node) : 将一个节点插入到 Range 的起点位置。
  • surroundContents(newParent) : 将 Range 的内容放入到一个新创建的父节点 newParent 中。

示例

const range = document.createRange();
const textNode = document.getElementById('someTextNode');
range.selectNodeContents(textNode);

// 获取选中的内容
const selectedContent = range.cloneContents();
console.log(selectedContent.textContent);

// 删除选中的内容
range.deleteContents();

// 插入新内容
const newNode = document.createTextNode('This is new content.');
range.insertNode(newNode);

结合使用 SelectionRange

SelectionRange 通常会一起使用。例如,使用 Selection 对象获取当前用户选中的内容,然后使用 Range 对象来精确操作选区中的内容。

示例:加粗用户选中的文本

const selection = window.getSelection();
if (selection.rangeCount > 0) {
    const range = selection.getRangeAt(0);
    
    // 创建一个新的 <strong> 元素
    const strongNode = document.createElement('strong');
    
    // 将选中的文本放入 <strong> 元素中
    strongNode.appendChild(range.extractContents());
    
    // 将 <strong> 元素插入到文档中
    range.insertNode(strongNode);
}

实际应用

  • 富文本编辑器 : 使用 SelectionRange 接口可以轻松实现文本的加粗、斜体、链接插入等功能。
  • 文本高亮 : 通过 Range 可以实现自定义的文本高亮效果。
  • 自定义剪切板操作 : 使用 Selection 可以捕获用户的选择,并执行定制化的复制或剪切操作。

示例:使用 contenteditableSelectionRange 实现富文本编辑器功能

  1. 基本编辑功能 :加粗选中的文本。
<div class="editor-content" contenteditable="true">
  <p>这是一个简单的富文本编辑器。</p>
</div>
<button onclick="applyBold()">加粗</button>

<script>
function applyBold() {
  const selection = window.getSelection();
  if (!selection.rangeCount) return;

  const range = selection.getRangeAt(0);

  // 创建一个 <strong> 标签包裹选中的文本
  const strong = document.createElement('strong');
  strong.appendChild(range.extractContents());
  range.insertNode(strong);

  // 清除选区,并将光标设置在新的 <strong> 标签之后
  selection.removeAllRanges();
  const newRange = document.createRange();
  newRange.setStartAfter(strong);
  newRange.collapse(true);
  selection.addRange(newRange);
}
</script>
  1. 添加和移除文本样式 :支持加粗、斜体等多种样式。
function exec(command) {
  const selection = window.getSelection();
  if (!selection.rangeCount) return;

  const range = selection.getRangeAt(0);
  let wrapper;

  // 根据命令类型创建相应的标签
  switch (command) {
    case 'bold':
      wrapper = document.createElement('strong');
      break;
    case 'italic':
      wrapper = document.createElement('em');
      break;
    default:
      return;
  }

  wrapper.appendChild(range.extractContents());
  range.insertNode(wrapper);

  // 清除选区并更新光标位置
  selection.removeAllRanges();
  const newRange = document.createRange();
  newRange.setStartAfter(wrapper);
  selection.addRange(newRange);
}
  1. 选择文本并将光标移到特定位置
document.querySelector('.editor-content').addEventListener('mouseup', () => {
  const selection = window.getSelection();
  if (selection.rangeCount > 0) {
    const range = selection.getRangeAt(0);
    console.log('当前选中的文本:', range.toString());
  }
});

额外功能

  • 添加加粗样式

    • 选中一段文本,调用 exec('bold') 方法,将选中的文本包裹在 <strong> 标签中。
  • 插入文本或节点

    • 选中一个位置或范围,使用 range.insertNode(newNode) 将自定义节点插入到指定位置。
  • 撤销和重做

    • 结合 SelectionRange 对象操作,实现对用户操作的记录和回退功能。

中高级 】从零到一自研一个富文本编辑器,需要考虑哪些问题,怎么实现?

文本格式化和处理

  • 问题 :如何支持加粗、斜体、下划线、颜色、背景色等多种格式?

  • 实现方法

    • 使用 JavaScript 手动操作 DOM,为选中的文本节点添加或移除样式标签(如 <strong><em> )。
    • 通过 isWrapped 方法判断选区是否已应用某种格式,通过 exec 方法对选区应用或移除格式。
  • 代码示例

    • isWrapped 方法 :检查当前选区是否已被包装指定格式。
function isWrapped(range, command) {
      let container = range.commonAncestorContainer;
      if (container.nodeType === Node.TEXT_NODE) {
        container = container.parentNode;
      }

      // 遍历父节点,检查是否有指定格式的节点
      while (container && container !== document) {
        switch (command) {
          case "bold":
            if (container.nodeName === "B" || container.nodeName === "STRONG")
              return true;
            break;
          case "italic":
            if (container.nodeName === "I" || container.nodeName === "EM")
              return true;
            break;
          case "underline":
            if (container.nodeName === "U") return true;
            break;
          case "color":
            if (container.nodeName === "SPAN" && container.style.color)
              return true;
            break;
          default:
            return false;
        }
        container = container.parentNode;
      }
      return false;
    }
  • exec 方法 :应用或移除文本样式。
    function exec(command, extra) {
      if (!selection) return;

      const range = selection.getRangeAt(0);
      const isContentWrapped = isWrapped(range, command);

      if (isContentWrapped) {
        unwrap(range, command); // 解除格式
      } else {
        // 应用新格式
        let wrapper;
        switch (command) {
          case "bold":
            wrapper = document.createElement("strong");
            break;
          case "italic":
            wrapper = document.createElement("em");
            break;
          case "underline":
            wrapper = document.createElement("u");
            break;
          case "color":
            wrapper = document.createElement("span");
            wrapper.style.color = extra.color;
            break;
          default:
            break;
        }
        if (!wrapper) return;

        const selectedText = range.extractContents();
        wrapper.appendChild(selectedText);
        range.insertNode(wrapper);

        // 合并相邻的相同格式标签
        mergeAdjacentNodes(wrapper);
      }
      updateSelectionAfterCommand(range);
    }
  • unwrap 方法 :解除选中的文本格式。
    function unwrap(range, command) {
      let container = range.commonAncestorContainer;
      if (container.nodeType === Node.TEXT_NODE) {
        container = container.parentNode;
      }

      function unwrapNode(node) {
        if (!node) return;

        const parentNode = node.parentNode;
        if (!parentNode) return;

        if (
          (command === "bold" &&
            (node.nodeName === "B" || node.nodeName === "STRONG")) ||
          (command === "italic" &&
            (node.nodeName === "I" || node.nodeName === "EM")) ||
          (command === "underline" && node.nodeName === "U") ||
          (command === "color" && node.nodeName === "SPAN" && node.style.color)
        ) {
          const beforeRange = document.createRange();
          beforeRange.setStartBefore(node);
          beforeRange.setEnd(range.startContainer, range.startOffset);

          const afterRange = document.createRange();
          afterRange.setStart(range.endContainer, range.endOffset);
          afterRange.setEndAfter(node);

          if (!beforeRange.collapsed) {
            const beforeNode = document.createDocumentFragment();
            beforeNode.appendChild(beforeRange.extractContents());
            parentNode.insertBefore(beforeNode, node);
          }

          if (!afterRange.collapsed) {
            const afterNode = document.createDocumentFragment();
            afterNode.appendChild(afterRange.extractContents());
            parentNode.insertBefore(afterNode, node.nextSibling);
          }

          const selectedContents = range.extractContents();
          parentNode.insertBefore(selectedContents, node);
          parentNode.removeChild(node);
        } else {
          unwrapNode(node.parentNode);
        }
      }

      unwrapNode(container);
    }
  • 核心逻辑说明

    • 应用格式 :使用 exec 方法,通过判断选区是否已经被包装来决定是应用还是移除某种格式。
    • 解除格式 :使用 unwrap 方法递归遍历父节点,解除指定的格式化标签。

光标与选区管理

  • 问题 :如何正确管理光标和文本选区的位置?

  • 实现方法

    • 使用 selectionchange 事件监听用户的选区变化。
    • 创建自定义光标元素,在用户编辑内容或移动光标时,更新光标位置。
  • 代码示例

onMounted(() => {
  const customCursor = document.createElement("div");
  customCursor.classList.add("custom-cursor");
  document.body.appendChild(customCursor);
  cursorRef.value = customCursor;

  document.addEventListener("selectionchange", () => {
    const innerSelection = window.getSelection();
    const isInEditor =
      innerSelection?.focusNode &&
      document.querySelector(".editor-content")?.contains(innerSelection?.focusNode);

    if (!isInEditor) {
      customCursor.style.display = "none";
      return;
    }

    selection = innerSelection;
    if (selection.rangeCount > 0 && !selection.isCollapsed) {
      customCursor.style.display = "none"; // 隐藏光标
    } else {
      customCursor.style.display = "block"; // 显示光标
      updateCursorPosition();
    }
  });
});

function updateCursorPosition() {
  if (!selection || !cursorRef.value) return;
  const range = selection.getRangeAt(0);
  const rect = range.getBoundingClientRect();

  const cursor = cursorRef.value;
  cursor.style.top = `${rect.top}px`;
  cursor.style.left = `${rect.left - 1}px`;
}
  • 核心逻辑说明

    • 自定义光标 :通过 selectionchange 事件实时监听用户的选区变化,判断光标位置并根据情况显示或隐藏自定义光标。
    • 光标位置更新 :在用户光标位置变化时,使用 updateCursorPosition 函数动态更新自定义光标的位置。

性能优化

  • 问题 :在处理大量文本和复杂内容时,如何保证编辑器的响应速度?

  • 实现方法

    • 使用虚拟 DOM 技术和延迟渲染优化大量 DOM 操作。
    • 在高频事件(如键盘输入、鼠标移动)中使用防抖和节流技术减少不必要的渲染。
  • 性能优化措施

    • 防抖(Debounce) :对于频繁的输入事件,延迟函数的执行,减少不必要的重绘。
    • 节流(Throttle) :限制高频事件的触发次数,例如在鼠标拖动或滚动时限制每秒触发的次数。

跨浏览器兼容性

  • 问题 :在不同的浏览器上,如何确保编辑器行为一致?

  • 实现方法

    • 使用标准化的 Web API,如 SelectionRange ,避免依赖特定浏览器行为。
    • 对常见的浏览器行为差异进行检测和兼容性处理。
  • 兼容性措施

    • 使用 Polyfill :为不支持的浏览器提供兼容代码。
    • 检测浏览器行为差异 :如在操作节点时,使用统一的方法避免不同浏览器的行为差异。

数据序列化与反序列化(编译器)

大部分编辑器都会提供编译器,例如 markdown、mdx 会有 hyper,tiptap 类会有基于 JSONContent 的结构,通常来说:

  1. 基于 markdown 会有更高性格,更大约束
  2. 基于 JSONContent 会有更灵活特性,对于自定义节点等极其便捷,可以 tiptap custom node 为例
  • 问题 :如何将编辑器内容转换为可存储的格式(如 JSON)并支持从这种格式解析回来?

  • 实现方法

    • 实现 JSON 和 HTML 的双向转换函数:

      • html2Json :将 HTML 解析为结构化的 JSON 数据。
      • json2Html :从 JSON 数据生成 HTML 字符串。
  • 代码示例

  • HTML 转换为 JSON

export function html2Json(element) {
  if (!element) return null;

  switch (element.nodeType) {
    case Node.ELEMENT_NODE: {
      const tagName = element.tagName.toLowerCase();
      if (tagName === "p") {
        return {
          type: "paragraph",
          content: Array.from(element.childNodes)
            .map(html2Json)
            .filter(Boolean),
        };
      } else if (tagName === "strong") {
        return {
          type: "text",
          marks: [{ type: "bold" }],
          text: element.textContent,
        };
      } else if (tagName === "em") {
        return {
          type: "text",
          marks: [{ type: "italic" }],
          text: element.textContent,
        };
      }
      break;
    }
    case Node.TEXT_NODE: {
      return {
        type: "text",
        text: element.textContent,
      };
    }
    default:
      return null;
  }
}
  • JSON 转换为 HTML
export function json2Html(json) {
  if (!json || !json.type) return "";

  switch (json.type) {
    case "doc":
      return json.content.map(json2Html).join("");

    case "paragraph":
      return <p>${json.content.map(json2Html).join("")}</p>;

    case "text": {
      let text = json.text;
      if (json.marks) {
        json.marks.forEach((mark) => {
          switch (mark.type) {
            case "bold":
              text = <strong>${text}</strong>;
              break;
            case "italic":
              text = <em>${text}</em>;
              break;
            default:
              break;
          }
        });
      }
      return text;
    }
    default:
      return "";
  }
}
  • 核心逻辑说明

    • HTML 转 JSON :通过递归遍历 DOM 元素,构建结构化的 JSON 数据。
    • JSON 转 HTML :从 JSON 数据生成对应的 HTML 字符串,支持富文本格式。

以 Quill 为例分析【重点】

Quill 的基本架构

  1. 内容模型 :

Quill 采用了一个名为“Delta”的数据模型来表示文档的内容。Delta 是一种 JSON 格式的文档结构,它可以表示文本、格式和嵌入对象(如图像或视频)。这种数据结构与 HTML DOM 无关,使得文档的操作更加灵活和高效。

  1. 渲染引擎 :

Quill 的渲染引擎将 Delta 数据模型转换为 HTML,显示在编辑器中。每次用户进行编辑操作时,Quill 更新 Delta 并重新渲染编辑器内容。

  1. 事件处理与模块 :

Quill 使用模块化设计,通过各种插件和模块来处理不同的编辑操作。这些模块处理用户输入、光标移动、文本格式化等任务。Quill 还支持自定义模块,让开发者可以扩展编辑器的功能。

Quill 数据协议

Quill 采用了一个名为“Delta”的数据模型来表示文档的内容。Delta 是一种 JSON 格式的文档结构,它可以表示文本、格式和嵌入对象(如图像或视频)。这种数据结构与 HTML DOM 无关,使得文档的操作更加灵活和高效。

Quill 使用 SelectionRange

它利用了 SelectionRange 接口来处理用户的光标位置、文本选择和内容操作。

  • Selection : Quill 使用 Selection API 来确定用户当前选择的文本范围,并将其与 Delta 模型进行同步。这对于处理用户的选择、移动光标、以及执行特定的文本操作(如加粗或插入链接)是至关重要的。

  • Range : 在需要精确处理选区时,Quill 使用 Range API 来获取或设置用户选择的具体范围。例如,当用户选择一段文本并应用某种格式时,Quill 会使用 Range 来确定这段文本的开始和结束位置,然后应用相应的格式。

Quill 的操作流程示例

  1. 用户输入或编辑操作 :

    • 用户通过键盘输入文本,或者通过鼠标选择一段文本。
    • Quill 捕获用户的输入事件,并根据当前的光标位置或选区来修改 Delta 数据结构。
  2. Delta 数据更新 :

    • Quill 更新 Delta 模型以反映用户的操作,例如插入新文本或应用文本格式。
    • Delta 是一种不可变的数据结构,因此每次更新都会创建一个新的 Delta。
  3. 渲染和同步 :

    • Quill 根据更新后的 Delta 模型重新渲染编辑器内容。
    • 使用 SelectionRange API,Quill 同步光标位置或选区,使用户界面与内部数据保持一致。
  4. 事件触发 :

    • Quill 触发相应的事件,通知应用程序其他部分(如工具栏)有内容更新。
    • 开发者可以通过事件监听器来扩展 Quill 的功能或与其他部分进行交互。

专家级 】架构设计一个类似飞书文档、notion 的产品,并详细说明具体细节

详细分析飞书文档与 Notion 产品功能

  • 核心功能

    • 实时协作 :支持多用户同时编辑,同步光标位置和编辑内容变化,提供实时反馈。
    • 丰富的文本和块样式支持 :支持多种文本格式(如标题、粗体、斜体、下划线)、块元素(如列表、表格、代码块)和多媒体嵌入(如图片、视频)。
    • 历史版本管理 :记录每次编辑的历史版本,支持查看、回滚和恢复。
    • 撤销重做支持 :提供撤销和重做功能,允许用户快速回退和恢复操作。
    • 快捷命令(如 / ** 命令)和智能推荐功能**:提供快速插入块元素的方式,用户输入 / 后弹出操作选项,如插入图片、表格、代码块等; @ 功能用于提及用户或文档链接,方便协作和文档互联。

基于 Block 设计的物料

  • Block 设计

    • 文档内容被视为多个块(Block)的组合,每个块都是一个独立的元素,可以单独存在、编辑、删除或移动。
    • 块的类型可以多样化,例如文本段落、标题、列表、表格、代码块、图片等。
  • 实现方法

    • 每个块作为一个独立的组件 :在前端架构中,每个块都是一个 React 组件。每个组件根据其类型和内容进行独立渲染和管理。
    • 数据结构 :使用 JSON 来描述文档的结构,每个块对应一个 JSON 对象,包含类型、内容、样式等信息。
    • 渲染逻辑 :通过遍历 JSON 数据结构,使用 json2Html 方法将每个块渲染为相应的 HTML 元素。
    • 支持嵌套块和拖拽排序 :允许块嵌套,例如列表中的项目可以是另一个列表;支持用户拖拽排序,改变块的位置。
  • 代码示例

// 基于 JSON 描述的 Block 数据结构
const blocks = [
  {
    type: "heading",
    level: 1,
    content: "Welcome to Feishu Docs",
  },
  {
    type: "paragraph",
    content: "This is a collaborative document editor.",
  },
  {
    type: "list",
    items: [
      { type: "text", content: "Rich text support" },
      { type: "text", content: "Real-time collaboration" },
    ],
  },
];

// 渲染函数,将 JSON 数据转换为 HTML
function renderBlocks(blocks) {
  return blocks.map((block) => {
    switch (block.type) {
      case "heading":
        return `<h${block.level}>${block.content}</h${block.level}>`;
      case "paragraph":
        return `<p>${block.content}</p>`;
      case "list":
        return `<ul>${block.items.map(item => `<li>${item.content}</li>`).join('')}</ul>`;
      default:
        return "";
    }
  }).join('');
}

const htmlOutput = renderBlocks(blocks);
document.getElementById("editor").innerHTML = htmlOutput;

数据协议【重点】

Quill

Quill 是一个流行的开源富文本编辑器,以其灵活性和可扩展性而著称。它采用了名为“Delta”的数据模型来表示文档的内容。

Delta 数据模型

  • Delta 是一种 JSON 格式的文档结构,用于表示文本、格式和嵌入对象(如图像或视频)。这种数据结构独立于 HTML DOM,专注于内容的逻辑表示,而不是具体的呈现方式。
  • 文本操作 在 Delta 中以“操作”(operations)的形式记录,包括插入、删除和保留(retain)。这使得文档内容的变更能够精确地描述,并支持撤销/重做、协作编辑等高级功能。
  • 格式化与嵌入 :Delta 可以精确地描述文本的格式(如加粗、斜体、链接等)和嵌入对象的位置与属性。这种模型的灵活性允许对内容进行高效的操作,而不必依赖 DOM 操作。
Delta 的基本结构

Delta 是一个由多个操作(operations)组成的数组。每个操作可以是以下三种类型之一:

  1. Insert (插入)
  2. Delete (删除)
  3. Retain (保留)
Insert 操作

用于在文档中的某个位置插入文本或嵌入对象。Insert 操作可以附带格式信息。

{
  "insert": "Hello, World!"
}

插入操作也可以包含格式信息:

{
  "insert": "Hello, World!",
  "attributes": {
    "bold": true,
    "italic": true
  }
}

除了插入文本,还可以插入嵌入对象,例如图片、视频等:

{
  "insert": {
    "image": "https://example.com/image.png"
  }
}
Delete 操作

用于删除指定数量的字符。Delete 操作只需要指定要删除的字符数。

{
  "delete": 5
}

这将删除文档中的 5 个字符。

Retain 操作

用于保留指定数量的字符,通常用于在文档中移动光标,或者应用格式而不改变内容。Retain 操作也可以附带格式信息。

{
  "retain": 7
}

这将保留文档中的 7 个字符。

Retain 操作也可以应用格式:

{
  "retain": 7,
  "attributes": {
    "bold": true
  }
}

这将使保留的 7 个字符变为加粗。

完整的 Delta 示例

以下是一个包含插入、删除和保留操作的完整 Delta 示例,展示了如何表示复杂的编辑操作:

[
  { "insert": "Hello " },
  { "attributes": { "bold": true }, "insert": "World" },
  { "insert": "!" },
  { "retain": 1, "attributes": { "italic": true } },
  { "delete": 5 }
]

这个 Delta 描述了以下操作:

  1. 插入文本 "Hello "。
  2. 插入加粗的文本 "World"。
  3. 插入标点 "!"。
  4. 将光标向后移动 1 个字符,并将其格式设置为斜体(假设之前的 "!" 已被选中)。
  5. 删除 5 个字符(假设这些字符位于 "World" 之后,Delta 的实际应用中,这可能用于撤销前面的某些操作)。
复杂场景中的 Delta

Delta 的设计使得它可以描述复杂的文档编辑场景,包括协作编辑、撤销重做和内容同步。通过将多个 Delta 合并,应用程序可以实现多用户协作的功能,并确保不同用户的编辑操作不会相互冲突。

特点

  • 与 HTML DOM 无关 :Delta 数据模型与 HTML DOM 的独立性意味着它可以轻松在不同的呈现环境中使用,包括服务端渲染、跨平台应用等。
  • 模块化与扩展性 :Quill 通过模块系统提供可扩展的功能,可以自定义工具栏、格式和事件处理器。开发者可以编写自己的模块来扩展编辑器的功能。

Tiptap

Tiptap 是一个基于 ProseMirror 构建的富文本编辑器框架,旨在提供比传统富文本编辑器更高的灵活性和可定制性。Tiptap 的设计初衷是为现代 Web 应用提供一个高度可定制的编辑解决方案。

ProseMirror 数据模型

  • 基于 Schema 的模型 :Tiptap 使用 ProseMirror 的 Schema 来定义文档的结构。Schema 定义了文档中允许的节点类型、嵌套规则和属性,使得编辑器可以支持复杂的文档结构,如表格、列表、嵌套的引用等。
  • 可扩展的节点与标记 :Tiptap 允许开发者定义自定义的节点(nodes)和标记(marks),这些都是 ProseMirror Schema 的一部分。这使得开发者可以轻松实现特定的文档结构和格式需求。
  • 命令与插件系统 :Tiptap 提供了一个强大的命令和插件系统,允许开发者通过命令(commands)来实现复杂的用户交互和文本操作。

特点

  • 高度可定制化 :通过插件和 Schema 定义,Tiptap 提供了极高的定制性,适合复杂的编辑需求,如编写代码编辑器、学术文章编辑器等。
  • 基于 Vue.js :Tiptap 的核心基于 Vue.js,虽然也支持 React 等框架,但其与 Vue.js 的集成尤为紧密,适合使用 Vue 的开发者。

/ 命令与 @ 等功能

  • / 命令

    • 提供快捷操作入口,如插入不同类型的块(图片、表格、代码块等)。
    • 实现方法 :捕获用户输入的 / 符号,弹出下拉菜单显示可选项(如插入标题、列表、表格等)。
  • @ 功能

    • 支持用户提及(mention)功能和文档链接功能。用户输入 @ 后,可以选择用户进行提及或链接到其他文档。
    • 实现方法 :捕获用户输入的 @ 符号,根据输入内容动态显示推荐选项(用户列表或文档列表)。
  • 代码示例

// 捕获输入事件
document.querySelector('.editor-content').addEventListener('input', function (e) {
  const inputText = e.target.innerText;
  if (inputText.endsWith('/')) {
    showCommandMenu(e.target); // 显示命令菜单
  }
  if (inputText.endsWith('@')) {
    showMentionMenu(e.target); // 显示提及菜单
  }
});

function showCommandMenu(target) {
  // 显示插入命令的选项菜单
  console.log("显示插入命令菜单");
}

function showMentionMenu(target) {
  // 显示用户提及的选项菜单
  console.log("显示提及选项菜单");
}

历史记录栈与 Undo/Redo

  • 问题 :如何实现撤销和重做功能?

  • 实现方法

    • 使用 命令模式历史记录栈 来管理用户操作。每次用户执行操作时,将该操作的命令对象推入栈中。
    • 当用户点击撤销时,从栈中弹出最后一个命令对象并调用其 undo 方法;当用户点击重做时,调用 redo 方法。
  • 代码示例

class Command {
  constructor(execute, undo, value) {
    this.execute = execute;
    this.undo = undo;
    this.value = value;
  }
}

const historyStack = [];
const undoStack = [];

function executeCommand(command) {
  command.execute(command.value);
  historyStack.push(command);
}

function undoCommand() {
  const command = historyStack.pop();
  if (!command) return;
  command.undo(command.value);
  undoStack.push(command);
}

function redoCommand() {
  const command = undoStack.pop();
  if (!command) return;
  command.execute(command.value);
  historyStack.push(command);
}

// 示例:加粗命令
const boldCommand = new Command(
  (value) => document.execCommand('bold', false, value),
  (value) => document.execCommand('undo', false, value)
);

// 执行加粗命令
executeCommand(boldCommand);
// 撤销加粗
undoCommand();

协同光标实现与协同编辑

  • 问题 :如何实现多用户实时协同编辑,显示协同光标?

  • 实现方法

    • 前端使用 WebSocket 进行实时通信 :每个客户端(用户)连接到 WebSocket 服务器,通过发送和接收光标位置和编辑内容,保持实时同步。
    • 后端 WebSocket 服务器 :接收来自客户端的消息,并将消息广播给所有其他客户端,确保所有用户的编辑状态一致。
    • 其他方案还有:yjs 等
  • 前端实现:

const socket = new WebSocket('ws://localhost:8080');

// 发送光标位置和编辑内容到服务器
function sendCursorAndContent(cursorPosition, content) {
  const message = JSON.stringify({
    type: 'update',
    cursor: cursorPosition,
    content: content,
  });
  socket.send(message);
}

socket.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.type === 'update') {
    updateRemoteCursor(data.cursor);
    updateRemoteContent(data.content);
  }
};

function updateRemoteCursor(cursor) {
  // 更新其他用户的光标位置
}

function updateRemoteContent(content) {
  // 更新其他用户的文档内容
}
  • 服务端实现:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
  ws.on('message', (message) => {
    wss.clients.forEach((client) => {
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    });
  });
});

console.log('WebSocket server is running on ws://localhost:8080');
  • 核心逻辑说明

    • 实时通信 :每个客户端通过 WebSocket 与服务器保持连接,当用户操作光标或编辑文档内容时,客户端发送消息到服务器。
    • 消息广播 :服务器将接收到的消息广播给所有其他客户端,所有用户的文档内容和光标状态保持同步。

冲突解决

  • 问题 :如何解决多用户协同编辑时产生的冲突?

  • 实现方法

    • 乐观锁机制 :在客户端进行本地操作的同时,将编辑操作发送到服务器,假设不会发生冲突。服务器收到操作后检查冲突,如果没有冲突,将更新广播给其他客户端;如果有冲突,通知冲突的客户端进行回滚或合并。

    • 基于 OT(Operational Transformation)或 CRDT(Conflict-free Replicated Data Type)算法

      • OT 算法 :通过操作变换来处理冲突,确保多个用户的编辑操作按照相同的顺序合并,从而达成一致。
      • CRDT 算法 :通过状态同步的方式,确保所有客户端可以并行编辑数据并最终收敛到同一个状态,避免冲突。
  • 核心逻辑说明

    • 冲突检测和解决 :实时协作编辑时,使用乐观锁或算法(如 OT 或 CRDT)来检测和解决冲突,确保所有客户端的文档内容一致。
    • 确保最终一致性 :通过变换操作或合并状态,所有用户的编辑内容最终一致。

代码示例

使用乐观锁机制

socket.onmessage = (event) => {
  const data = JSON.parse(event.data);

  if (data.type === 'update') {
    if (isConflict(data)) {
      resolveConflict(data);
    } else {
      updateRemoteCursor(data.cursor);
      updateRemoteContent(data.content);
    }
  }
};

function isConflict(data) {
  // 简单示例:判断操作时间戳是否冲突
  return data.timestamp < lastOperationTimestamp;
}

function resolveConflict(data) {
  console.log("冲突检测到,正在解决...");
  // 合并冲突操作或回滚
}

使用 OT 算法

// OT 算法的基本操作变换逻辑
function transformOperation(localOp, remoteOp) {
  // 根据操作类型和位置进行变换
  if (localOp.position < remoteOp.position) {
    return localOp;
  } else {
    localOp.position += remoteOp.length;
    return localOp;
  }
}

socket.onmessage = (event) => {
  const remoteOp = JSON.parse(event.data);
  const localOp = getLocalOperation(); // 本地未同步的操作const transformedOp = transformOperation(localOp, remoteOp);
  applyOperation(transformedOp);
};

简历优化(30K+ 简历样板)

技能描述

  • 熟练掌握 HTML、CSS、JavaScript、Typescript 以及 OOP、FP、 AOP 等设计思想

  • 掌握样式体系构建与落地,对 css 预编译、css in js、module css 以及 utility-first CSS 有深入研究,并从零改良过样式体系以支持 SSR、SSG

  • 熟悉 React、Vue 相关技术栈,熟悉 React、Vue 及相关技术框架的实现原理

  • 掌握构建工具 Webpack、Vite 等,掌握编译工具 Babel,并深入理解其原理,并参与 Rspack 构建

  • 丰富的数据可视化经验,熟悉 Canvas、svg 开发范式,理解 Echarts、Antv 原理,能根据业务需求基于 d3、zrender 开发自定义渲染引擎

  • 丰富的跨端开发经验,熟练使用 Taro、Flutter、React-Native 开发跨端应用,对构建 hybird App 有丰富经验,深入理解跨端开发编译原理

  • 基于 Node.js 开发脚手架、打包构建优化工具及中间件服务

  • 掌握常用设计模式、算法与安全知识,追求开发高质量、高可维护性代码,追求极致产品体验

  • 团队管理经验,并在项目架构设计与性能优化方面具有丰富经验

  • 算法与编程技术

    • 精通各种算法题的分类及解决方法,包括排序与查找、数据结构、动态规划、贪心算法、回溯算法、分治算法、图论算法、数学算法等。
    • 重点掌握动态规划的基本概念、解题步骤和经典题目。
  • 3D 数字孪生平台开发经验

    • 熟练使用 WebGL 和 WebAssembly 技术,开发高效的 3D 渲染引擎。
    • 精通正射影像和倾斜摄影技术,具备 Tile 和模型(包括白膜和精模)的处理经验,能够使用 Blender 进行模型制作。
    • 熟悉材质、光效和粒子系统的实现与优化。
  • 全面性能优化能力

    • 具备打包构建优化经验,熟练使用 Webpack 进行模块打包,掌握 chunk、treeshaking、happypack、cache-loader 等优化技巧,并使用 Webpack Module Federation 进行模块联邦管理。
    • 精通资源优化,能够有效进行图片、字体压缩,管理请求队列,并通过 OSS 和 CDN 提升资源加载速度。
    • 具有应用性能优化经验,包括数据结构优化和应用模块更新。
    • 深入了解缓存机制,熟悉强缓存(Expiration、Cache-Control)、协商缓存(Etag)和策略缓存(Service-Worker)的配置与管理。

项目描述

这个环节至关重要,很多同学不重视,简历随便写一写就开始投递了,结果投出去几百份可能一家公司面试都没有,结果就在怀疑前端行情出问题了

STAR 法则:

  • 遇到了什么问题 question,需求
  • 怎么评估解决方案,方案对比,方案落地 react 状态管理(redux、mobx、jotai、recoil)Vue3 -> Pinia
  • 具体方案落地
  • 结果反思,细节优化思考

大家先看这段描述:

技术栈:Java、Vue2、echarts、WEui、BaiduMap、JavaScript 、HTTP数据库:MySQL管理工具:SVN

责任描述:

1)产品前端研发负责人,主要负责整体样式沟通,样式调配实现;门户、管理平台、移动端、智端、可视化等前端内容实现;

2)实现组织管理、人员管理、党员关系转接、待办通知、绩效考核(复杂功能算法实现)、发展党员(25个流程)、可视化等主体功能,兼容性优化、适配1920*1080屏幕以及响应式布局实现;

3)相关功能开发,包含前后端、数据库;

4)门户框架搭建、门户整体设计、后端接口、门户前端 UI 实现等;

5)微信小程序框架搭建、小程序页面设计及开发、知识图谱技术预演;

6 )项目经理工作辅助,包含需求沟通、UI设计沟通、交付材料项目经历整理、前端代码质量管理、部分功能设计。

深度优化一下

工作内容和成果

  • 架构设计 】参与智慧管理平台整体架构设计、技术选型与方案评审,担任全栈开发,完成相关核心模块
  • 企微开发 】对接企业微信生态,基于企微 SDK 完成平台支付、消息推送、机器人等功能开发
  • 可视化 】主导完成平台可视化渲染引擎(可视化图表的组件,数据协议)设计与开发,基于 echarts (svgRenderer、canvasRenderer 一千万行数据的表格渲染【不能使用 虚拟滚动 】 canvas table,chunk)封装业务图表库,服务于平台可视化场景
  • 地图开发 】使用百度地图 SDK,封装业务地图渲染器(MapRenderer),包含:地图撒点、地区数据下钻等功能
  • 小程序与App 】基于 uniapp 实现智慧党建用户端多端开发落地,产物编译为 H5、微信小程序两端应用
  • 团队基建 】推进团队业务组件库、图表库与基础库沉淀,完成 10+ 个业务组件沉淀,以此提升了团队协同开发效率
  • 优化 】设计产品响应式系统,基于 media query 设计响应式端点规则,适配不同端应用的展示
  • 自研 OA 打通 】...

STAR

【技术栈】

  • Java、Vue2、ECharts、WEui、BaiduMap、JavaScript、HTTP
  • 数据库 :MySQL
  • 管理工具 :SVN

【产品前端研发负责人】

  • 情境 (Situation) :担任产品前端研发负责人,负责门户、管理平台、移动端、智端、可视化等前端内容的实现。
  • 任务 (Task) :主要任务是与设计团队沟通,调配和实现整体样式,确保产品界面的一致性和用户体验。
  • 行动 (Action) :我协调设计与开发团队,定期召开样式沟通会,亲自进行样式的调配与实现,并负责不同平台和设备的前端内容开发。
  • 结果 (Result) :成功实现了多个平台的前端开发工作,提升了产品的用户体验和一致性,获得了团队和用户的高度评价。

【实现复杂功能及优化】

  • 情境 (Situation) :项目需要实现复杂功能算法和大规模功能模块,包括组织管理、人员管理、党员关系转接、待办通知、绩效考核、发展党员(25个流程)和可视化功能。
  • 任务 (Task) :负责上述复杂功能的实现,并优化其兼容性和响应式布局。
  • 行动 (Action) :通过设计和实现复杂算法,确保各模块的功能性;进行兼容性优化,使系统适配1920*1080屏幕和响应式布局。
  • 结果 (Result) :成功实现并优化了所有复杂功能,系统在不同设备和分辨率下均表现良好,提高了用户操作的流畅度和满意度。

【全面功能开发】

  • 情境 (Situation) :需要进行前后端和数据库的全面开发,确保系统各功能模块的无缝集成。
  • 任务 (Task) :开发和实现相关功能,包括前端UI、后端接口和数据库交互。
  • 行动 (Action) :采用Java、Vue2等技术,开发并调试各功能模块,与后端团队密切合作,确保接口的准确性和数据的一致性。
  • 结果 (Result) :成功完成了所有功能模块的开发和集成,系统运行稳定,性能优异,受到了客户的好评。

【微信小程序开发与知识图谱预演】

  • 情境 (Situation) :项目需要开发微信小程序,并进行知识图谱技术的预演。
  • 任务 (Task) :负责小程序框架的搭建、页面设计及开发,同时进行知识图谱的技术预演。
  • 行动 (Action) :使用WEui、JavaScript等技术,设计并开发小程序页面,进行知识图谱的技术预演和验证。
  • 结果 (Result) :成功搭建了微信小程序框架,完成了页面设计和开发工作,知识图谱技术预演顺利,通过了技术验证。

【项目经理工作辅助】

  • 情境 (Situation) :在项目中辅助项目经理,确保项目需求沟通顺畅,UI设计协调到位,交付材料齐全,前端代码质量高。
  • 任务 (Task) :辅助项目经理进行需求沟通、UI设计沟通、交付材料整理、前端代码质量管理以及部分功能设计。
  • 行动 (Action) :积极参与需求和UI设计的沟通,整理和管理项目交付材料,进行代码审查和质量管理,并参与功能设计。
  • 结果 (Result) :成功辅助项目经理完成了项目的各项工作,提高了项目的开发效率和交付质量,确保了项目的顺利进行。

其实还不够,这些项目才是求职香饽饽

你可能不具备这些项目的实战经验,很多同学写了很多年管理系统,简单增删改查项目,如果不跳出这个圈子,很难在薪资上有非常大的突破!

  • 大厂 UI 组件库(Vue3)整体设计与开发实践(monorepo 架构)

  • 大厂业务 Hooks 库(React 18)整体设计与开发实践(从零到一的架构、规范流程)

  • 企业级脚手架工具开发实践

  • 企业级文档编辑器飞书文档开发实践

  • 前端性能、异常与行为监控

  • 3D 可视化数字孪生低代码实战 💥

    • 基于 cesium(arcGis、超图) 方案的 WebGIS 开发实践
    • 基于 openlayer、mapbox 开发
    • 基于 WebGL 3D 可视化开发实践

年包 50W+ 薪资长线规划

核心要素

  1. 全面的技术储备

    1. 框架基础

      • Vue 和 React 经验:包括 Vue3 + Typescript 和 React18(Hooks、Concurrent)
      • 掌握框架原理:React 生态库(React-Router、Redux)和 Vue 生态库原理
    2. 工程化能力

      • 构建工具:Webpack、Vite、Rspack、ESBuild、swc
      • CI/CD 自动化:自动化构建和自动化部署
    3. 基建能力

      • Node.js、命令行工具开发(Cli)
      • UI 库、图表库、工具库开发
    4. 业务领域经验

      • 管理系统和图表类应用
      • 可视化、编辑器、云表格、低代码平台、SaaS 产品、数字孪生、三维可视化
  2. 项目经验

    • 参与过至少两个大型项目,并主导过一个复杂项目
    1. 管理系统:包括项目搭建、技术方案选择、技术栈构建、CI/CD 流程
    2. 其他领域项目:如可视化、编辑器、云表格、低代码平台、SaaS 产品、数字孪生、三维可视化
  3. 面试表现

    1. 个人介绍

      • 准备个人介绍草稿,涵盖基本信息、技术栈和项目重难点
    2. STAR 法则

      • 问题描述:阐述遇到的问题或需求
      • 解决方案评估:方案对比与选择(如 React 状态管理:redux、mobx、jotai、recoil,Vue3 使用 Pinia)
      • 方案实施:具体的实施步骤
      • 反思与优化:项目反思及优化建议
    3. 面试准备

      • 重点复习知识点:如 v8 内存管理、Promise A+ 规范、事件循环、this、面向对象编程原型
      • 技术储备:结合项目经验展示技术在项目中的应用
  4. 学历提升

    1. 现有学历:大专
    2. 未来计划:尽快取得本科证书(不需要注明具体年限)
    3. 学历背景:民办学校
    4. 内推建议:应对学历和工作经历问题,通过内推提升机会

补足短板

  • 项目简单,管理后台一做就是大半年,天天 CRUD

    • 看开源项目(react-hook-form ts 类型、hook 处理、状态管理、架构 Provider,keyPath)
    • 找一些不错的项目练手
    • 拿好的项目,学习完放在简历里, 可视化、编辑器、云表格、低代码、SaaS 产品、数字孪生、三维可视化
    • 1.自研:15K, 2.外包:20K ,这个火坑, 灰(供需决定)
  • 技术栈掌握不深不广,只会用 Vue2 (Vue3 + Typescript),React18(React stack reconciler)

  • 架构、方案设计没碰过

    • React,create-react-app、umi,没有真正从零到一去设计初始化过一个项目
    • Vue,Vue CLI,Vite/Webpack。Vue2 CLI 创出来项目 1. webpack、2.vite
  • 没有专精的技能或业务

    • 自驱 (假定自己是 Leader),项目的赢利点、商业价值【 可视化、编辑器、白板、团队基建、AI产品
    • 与生俱来有些东西,好奇心、自驱力、清晰规划
    • 我:管理平台,HPE(官网、后台管理),云表格(维格表、飞书云表格)、云编辑器(CKEditor 老【html string】)(语雀、【石墨文档】)

妙码学院——全程陪跑、督学、内推

⚠️ 暂不支持的文档区域,【文档小组件】

  • 跨端开发 Taro(Taro 编译器 compiler、运行时 runtime)、uniapp
  • 协同编辑器(文档类、画板类)wangEditor、CKEditor
  • 团队基建工程(UI 库、图表库、Cli -> 产生产物 npm 包)
  • 3D 可视化数字孪生 bigdata
  • 低代码平台

课程体系 2.0 升级,除了基础知识夯实,融入了更多项目实战内容,包含:

  • 企业级脚手架工具开发实践

  • 企业级文档编辑器飞书文档开发实践

  • 大厂 UI 组件库(Vue3)整体设计与开发实践

  • 大厂业务 Hooks 库(React 18)整体设计与开发实践

  • 埋点与数据监控平台实战

  • 3D 可视化数字孪生实战 💥

    • 基于 cesium 方案的 WebGIS 开发实践
    • 基于 WebGL 3D 可视化开发实践

妙码学院全网独家项目实战矩阵

所有项目实战,均完全 从零到一手写纯原创 ,项目架构与编码规范真一线大厂级。

  • 微前端在分拆原子应用场景下的落地与实践

  • 协同编辑器从零到一架构实现

  • 推动团队基建落地

    • React UI 库
    • Vue Composition API 库
    • 企业级脚手架
  • 数字孪生平台整体架构设计

  • 企业级无代码可视化平台实践(Vue3)

  • 低代码平台设计与实现

    • 编排引擎
    • 流程引擎
    • 编辑器
    • 代码执行器与 JavaScript 沙箱
  • 用户行为分析 SDK 及监控可视化整体实现

  • 可视化渲染引擎设计与实现

  • 基于 RAG / Agent 前端 AI 应用流程编排平台

不同阶段对前端人的硬性要求

以下是对您提供的内容进行权威和专业化改写的建议:

1~3年

在此阶段,重点在于评估个人的基础知识和热情。对前端基础、计算机原理、网络通信和算法等领域的要求较高。由于在此阶段难以评估业务深度,因此更多关注基础知识的掌握程度。

  • 关键在于通过学术教育或网络资源加强基础知识;
  • 在简历中以多种方式展示对前端的热情,展现个人潜力;
  • 积极探索前沿技术,关注国内外技术动态;
  • 尝试开发小型项目或参与社区开源项目;
  • 建立技术博客,以输出促进知识吸收。

3~5年

此阶段通常是向成为独立工程师发展的关键时期,避免重复使用有限的经验。

  • 关注社区中关于进阶的资料和路线,强化基础知识;

  • 深入掌握常用框架的高级用法,探索其原理;

  • 在业务开发中不仅完成功能,还需考虑项目结构设计、封装基础工具、设计和开发基础组件;

  • 思考提高团队效率的方法,例如:

    • 集成代码检验和风格统一插件(如 eslint、stylelint、prettier、spellcheck);
    • 从工程化角度提高本地开发效率,优化webpack构建,探索esbuild、vite等工具;
    • 对于多项目开发,整理差异和统一部分,建立内部脚手架以减少重复工作;
    • 尝试搭建CI/CD平台,维护公司内部的通用npm包;
    • 培养软技能,如沟通协作,协调各角色共同推进目标。

5年以上

进入此阶段,可能朝技术专家或管理方向发展。期望您能够独立负责高复杂度项目,突破关键技术难题。

  • 负责技术调研,关注行业趋势,选择最优技术方案,具备决策能力;
  • 拥有丰富的技术经验和技术储备,能够解决遇到的困难,并有自己的方法论;
  • 协助或主导业务目标制定,合理推动项目达成预期效果;
  • 是否具有团队领导经验,能够协调跨团队项目,处理团队成员情绪问题,解决技能分布不平衡等问题;
  • 打造技术氛围,促进团队共同成长。

职业规划指导

评论区选取三位同学互动,其他同学也可以联系咨询老师,文字方式提供辅导解答。

【未知组件reminder】