利用 React 编写 Markdown 编辑器(的时候遇到了一个问题)。
最近在写一个博客系统,其中后端部分用了 Koa2,目前基本完成。前端(管理)部分因为正在熟悉 React,采用了 React 编写。因为不需要考虑兼容性,Ajax 请求使用 Fetch API 实现。
整个编辑器本身作为一个组件或称视图,包含了两个子组件,基于 CodeMirror 代码编辑器组件和基于 Marked 渲染组件。
然后 React 的思想大致是数据仅由高到低单向流动,父组件可以传个方法给子组件,用于(让父组件自己)更新值。简单画个图的话大致长这个样子:

理论上这样没有问题,实际上写起来也是这样,不过在我给 React-Router 加入了嵌套路由之后,就发生了一些微妙的变化。
是这样的,我的编辑器在 /edit 这个路由的位置上,然后如果我想在点击某篇文章之后跳转编辑这篇文章,很自然的就想到了 /edit/:id 这个办法。但是实际情况下,我发现编辑器两边都没有值,但是编辑器本身的输入和渲染是 ok 的。感觉原因应该出在请求博客内容之后,更新父组件的 state 的时候。但是更新 state 和编辑器组件是调用的同一个 setMarkdown 方法,结果应该一致的。
难道没有请求到内容?于是在组件初始位置输出了 id。嗯??为什么会有两个 id 出现,一个 undefined,另一个有值。不只是这个,所有的东西都是两份,包括 state 也有不同的两版。
为什么一个组件会挂载两次?一组有id,一组没有id?嗯?id?好吧,原因跟 React-Router 的匹配机制有关。这货会自上而下的匹配,而我恰巧把 /edit/:id 的 Route 写在了 /edit 的下方导致实际挂载的是没有id的版本。于是我把两个 Route 调换过来,嗯,成功出现内容。
但是 CodeMirror 那边内容没法更新,需要调用 CodeMirror 预留的 setValue 方法。关于如何选择更新时机就又是一个问题了,props 每次变动都更新的话就不用编辑了,因为覆盖更新的话,每输入一次,光标每次都会跑到最前部。理论上能用增量更新,CodeMirror 有相关的方法好像。不过我选择了一个更简单的办法,加了一个 props.done 标记内容加载完成,当值变更时获取 props.raw 覆盖更新。
【接↑↑段】你以为是这个原因吗?那么如何解释挂载了两次呢?其实是 /edit/xxxx 会同时匹配 /edit/:id 和 /edit,但是 /edit 只会匹配 /edit,然后我把两个 Route 写在同一级了,导致带有 id 的情况下一次挂载了两个组件,而不带 id 的时候其实只会挂载一个组件。我是在写 Demo 验证的时候发现的,这个可用在浏览器中看到DOM变动。
具体细节感觉还需要了解,合理的解法感觉可以把 /edit/* 移动到组件内部去继续判断。话说居然已经 1:10 了,白天起床再研究好了。