引言
从React的渲染流程我们知道,JSX 会先转为一颗 Fiber Tree,然后通过 Renderer 渲染成页面。对于 Web 平台,这个 Renderer 就是 react-dom,对于 Native 平台,这个 Renderer 就是 react-native。当然,我们也可以创建我们自己的 Renderer,将 React 应用渲染到其他目标平台,比如本文中的 Canvas:


下面就来剖析下 Canvas Renderer 的实现方式。
Canvas Renderer
组件
如图,我们的 Canvas Renderer 包括 Stage,Rect,Circle,Text 这些组件,其中将他们一些公共的特征抽离成了一个父类 Layer。

不需要 React,现在的 Canvas Renderer 已经可以渲染出内容了,比如:
1 | const renderDom = document.getElementById('demo') |

Canvas Renderer 实现方式
我们通过引言中第一个 Demo 来分析 Canvas Renderer 的实现方式:
1 | // Demo1.jsx |
Demo1 是一个函数组件,返回了 text、rect、 circle 这些标签,这些标签需要我们 Canvas Renderer 来进行渲染,接下来看看 render 函数做了啥:
1 | const reconcilerInstance = Reconciler(HostConfig) |
该函数主要是创建了一个 Stage 对象作为 Reconciler 对象 reconcilerInstance 的 container,最后调用 reconcilerInstance.updateContainer() 将 Demo1 组件通过 Canvas Renderer 进行渲染。我们知道 Reconciler 在 React 渲染流程中充当着非常重要的作用,它会计算出哪些组件需要更新,并会将需要更新的信息提交给 Renderer 来处理,而将 Reconciler 和 Renderer 连接起来的秘诀就在 HostConfig 之中:
1 | const HostConfig = { |
HostConfig 中是我们的 Canvas Renderer 需要实现的一些接口,这里来说明一下:
supportsMutation
当前渲染器是否支持修改节点,毫无疑问这里必须是 true。
createInstance
该函数会在通过 FiberNode 创建宿主相关的元素时进行调用,返回的元素会保存在 FiberNode 的 stateNode 属性上,参考React的渲染流程。对于 Canvas Renderer 来说,这里会根据 type 值创建出不同的组件。
appendInitialChild、appendChild、appendChildToContainer、insertBefore
这几个接口都涉及到元素的插入操作,前三个是把元素插到最后面,其中 appendInitialChild 在首次渲染时调用,appendChild 在更新的时候调用,而 appendChildToContainer 则在把元素插入到 container 时使用,对于 Canvas Renderer 来说,这些接口中均调用 parent.appendChild(child) 即可:
1 | appendChild(child) { |
而 insertBefore 则是把元素插入到某个元素前面,同样,Canvas Renderer 也有对应的实现:
1 | insertBefore(child, beforeChild) { |
commitUpdate
当组件属性发生变化的时候会调用该函数,Canvas Renderer 对应的实现方法也比较简单,即更新 instance 的属性即可:
1 | update(props) { |
resetAfterCommit
在React 源码解读之一首次渲染流程这篇文章中已阐明 React 的每次更新过程包括 Render 和 Commit 两大阶段,其中 Render 阶段会计算出 Effect 链表供 Commit 阶段处理,而 resetAfterCommit 这个函数就是在 Commit 阶段执行完 commitMutationEffects 函数后进行调用,此时所有对元素的更新操作已处理完毕,所以这里是一个适合 Canvas Renderer 调用 container.render() 进行重新渲染的地方。该函数中首先清空了整个画布,然后依次调用子组件的 render 方法:
1 | // Stage.js |
值得一提的是,Remax 也是在这里触发了小程序的更新。
至此,我们的 Canvas Renderer 的核心实现原理就分析完了,更多内容及 Demo 详见源码。