混合开发最小接口
2020 年目前在做的最复杂的项目是将基于 Draft.js
深度定制开发的 web 编辑器适配到移动端,因为数据上牵涉到很多转换过程 native 端要从头开发的话成本过高,并且发现 web 并不能很正常地识别虚拟键盘弹出与否,于是将下图所示的工具栏通过 native 端单独进行开发与 web 集成,以获得更好的使用体验。
简单介绍下 web – native 通讯方式,ark_editor_native
是 native webview 注入到 window 对象上的一系列方法简称 AEN
,ark_editor_web
则是 web 编辑器挂载成功后写入到 window 对象上供 native 调用的一系列方法简称 AEW
,每次 editor 有任何更新,通过 AEN.syncState
传递给 native 一个 JSON 字符串,客户端需要自行反序列化成 JSON 对象,称为 syncState
。
syncState
名称 | 值 |
---|---|
inline_styles | { "BOLD": false, \n"CODE": false, "ITALIC": false, "STRIKETHROUGH": false }, |
block_type | “unstyled” |
alignment | “left” |
disabled_buttons | [ “BOLD”, “unstyled”, “header-one”, …] |
undo_disabled | true |
redo_disabled | false |
操作栏
行为 | value | disabled | toggle | insert | update | remove | native 帮帮我 | 交给你了 web |
---|---|---|---|---|---|---|---|---|
加粗 | ‘BOLD’ | ‘BOLD’ in syncState.disabled_buttons | AEW.toggleInlineStyle(‘BOLD’) | |||||
楷体 | ‘ITALIC’ | ‘ITALIC’ in syncState.disabled_buttons | AEW.toggleInlineStyle(‘ITALIC’) | |||||
删除线 | ‘STRIKETHROUGH’ | ‘STRIKETHROUGH’ in syncState.disabled_buttons | AEW.toggleInlineStyle(‘STRIKETHROUGH’) | |||||
行内代码 | ‘CODE’ | ‘CODE’ in syncState.disabled_buttons | AEW.toggleInlineStyle(‘CODE’) | |||||
链接 | ‘LINK’ | ‘LINK’ in syncState.disabled_buttons | AEW.toggleLink() | AEW.insertLink(url) | AEW.updateLink(entityKey, url) | AEW.removeLink() | AEN.showLinkEditor(entityKey, url) | |
注释 | ‘FOOTNOTE’ | ‘FOOTNOTE’ in syncState.disabled_buttons | AEW.insertFootnote(content) | AEW.updateFootnote(entityKey, content) | AEN.showFootnoteEditor(entityKey, content) | |||
标题( 总称,无实际意义) | ‘headlines’ | ‘headlines’ in syncState.disabled_buttons | ||||||
一级标题 | ‘header-one’ | ‘header-one’ in syncState.disabled_buttons | AEW.toggleBlockType(‘header-one’) | |||||
二级标题 | ‘header-two’ | ‘header-two’ in syncState.disabled_buttons | AEW.toggleBlockType(‘header-two’) | |||||
三级标题 | ‘header-three’ | ‘header-three’ in syncState.disabled_buttons | AEW.toggleBlockType(‘header-three’) | |||||
四级标题 | ‘header-four’ | ‘header-four’ in syncState.disabled_buttons | AEW.toggleBlockType(‘header-four’) | |||||
五级标题 | ‘header-five’ | ‘header-five’ in syncState.disabled_buttons | AEW.toggleBlockType(‘header-five’) | |||||
六级标题 | ‘header-six’ | ‘header-six’ in syncState.disabled_buttons | AEW.toggleBlockType(‘header-six’) | |||||
默认文字块 | ‘unstyled’ | ‘unstyled’ in syncState.disabled_buttons | AEW.toggleBlockType(‘unstyled’) | |||||
对齐 | ‘alignments’ | ‘alignments’ in syncState.disabled_buttons | AEW.toggleAlignment(‘left’|’center’|’right’) | |||||
有序列表 | ‘ordered-list-item’ | ‘ordered-list-item’ in syncState.disabled_buttons | AEW.toggleBlockType(‘ordered-list-item’) | |||||
无序列表 | ‘unordered-list-item’ | ‘unordered-list-item’ in syncState.disabled_buttons | AEW.toggleBlockType(‘unordered-list-item’) | |||||
引用 | ‘blockquote’ | ‘blockquote’ in syncState.disabled_buttons | AEW.toggleBlockType(‘blockquote’) | |||||
图片 | ‘FIGURE’ | ‘atomic’ in syncState.disabled_buttons | AEW.selectImage() | |||||
代码块 | ‘code-block’ | ‘code-block’ in syncState.disabled_buttons | AEW.insertCodeBlock() | |||||
分割线 | ‘PAGEBREAK’ | ‘ atomic’ in syncState.disabled_buttons | AEW.insertPagebreak() | |||||
分行 | ‘ atomic’ in syncState.disabled_buttons | AEW.insertSoftNewLine() | ||||||
撤销 | syncState.undo_disabled | AEW.undo() | ||||||
重做 | syncState.redo_disabled | AEW.redo() | ||||||
清除行内样式 | AEW.removeFormat() |
优先级原则:
- 先判断是不是
disabled
- toggle > native 帮我 > 交给你了 web > insert/update/remove
上面就是编辑器跨端通讯的基本形式了,简单来说 web 端每次 React componentDidUpdate
会把状态同步给 native 端,保证工具栏的及时性。当用户点击工具栏中可用按钮时,首先判断是不是需要和 web 进行状态判断,毕竟前一步只是判断能不能触发,触发以后的行为需要结合更具体的数据状态。然后是如果发现需要更多用户输入(比如弹窗输入框或勾选项等等)就需要唤起 native 的方法(native 帮帮我),如果没有这一步就可能是 web 专享操作,无关更多状态,比如上传图片或者撤销重做等等,这种情况就全权交给 web 了。最后则是 native 端二次调用 web 的方法,通常是关掉弹窗以后传递数据去改变 web 的状态。
体会:
- 原本通过 React props 保证的数据实时性需要手动同步给 native 端
- native 和 web 互相暴露方法需要考虑实现的最小集
- 需要找到优雅的 debug 方式,出现过直接传 JS Object,native 端得到
undefined
- 异步操作可能需要加锁
当然现在开发还在对接联调期,包括保存等功能还没有加上,与从零开始的搭建也不可同日而语。编辑器和阅读器都是电子阅读产品的重头戏,之前则是看了这篇《Visual Studio Code有哪些工程方面的亮点》,对编辑器的开发充满了崇敬的心情。VS Code 也是我们现在开发的主力工具,包括其丰富的插件体系、LSP 和宛如神器的 Remote 模式,都使我惊叹于其良好的实现。不说了,去下单《设计模式》了。