# React 实践揭秘之旅,中高级前端必备(下) ## 引言 上一篇文章我们主要实现了 **JSX** 在 `WebGL` 上的渲染与更新,对 **虚拟DOM** 和 **Diff** 有了更深的了解,但相比于我们使用的 `React`,还缺乏了之中很重要的一环 --- **组件模式**。 想必大家能认同,React组件(`Component`)具有 **强大的功能,高拓展性和高解耦性**,在其基础上构建的各种 `UI` 组件框架完全改变了传统的 Web 开发模式,成为了Web 中的大型复杂应用提供了一种很好的构建模式和保障,也让我们的开发效率也有了质的变化。 在这篇文章中,我们会在上一篇的实现基础上,加入 `React` 组件模式,并在实现的过程中适时的去讲解一些原理和思维,也有利于大家 **由浅入深的理解** 和 **编程思维上的提升**。 由于下篇是完全以上篇作为基础的。所以如果你还没看过上篇,请优先猛戳: **[React 实践揭秘之旅,中高级前端必备(上) ->](https://github.com/xd-tayde/blog/blob/master/ReactGL-1.md)** ## 第七站: 破碎之墟 - Component **代码复用性**,向来是编程领域一个核心的理念。我们最常使用 **函数、类** 方式进行代码的封装,但这里有个痛点: **Web 中 UI 与 逻辑 的分离的特性,导致较难优雅地整合封装。** 通常我们需要引 `JS`、`CSS`,再在 `HTML` 中按规定书写结构,最后再初始化库。整个过程十分割裂,且不优雅。 而 `React Component` 帮我们解决了这个痛点,即保持了 **动态UI** 与 **逻辑** 的分离,又有了全新的整合方式。从而让 UI 组件变得非常的 **高效易用**,只需要在结构中以标签的形式引入即可。 我们就继续在上篇文章的基础上,加入 `Component` 的特性。基于 `JSX` 的变量传递,我们只需要实现一个 `Component` 类,并针对性地去完成组件的 **渲染和更新** 即可。 > TIPs: > > 由于组件的加入,这时我们的 **虚拟DOM(VNode)** 就包含了两种类型: **组件节点(compVNode)** 和 **元素节点(elVNode)**。后续都会以此区分,便于讲解。 ```js // 组件基类 class Component { // 通过保持三份不同的时期的快照 // 有利于对组件的状态及属性的管理和追踪 // 属性 public __prevProps public props = {} public __nextProps // 状态 public __prevState public state = {} public __nextState // 储存当前组件渲染出的 VNode public __vnode // 储存 组件节点 public __component constructor(props) { // 初始化参数 this.props = props } public render(): any { return null } public __createVNode() { // 该方法用于封装重新执行 render 的逻辑 // state 与 props 状态变更的逻辑 this.__prevProps = this.props this.__prevState = this.state this.props = this.__nextProps this.state = this.__nextState this.__nextState = this.__nextProps = undefined // 重新执行 render 生成 VNode this.__vnode = this.render() return this.__vnode } } ``` 有了这个类后,我们就可以使用 `React` 的方式继承出 **自定义组件** 进行渲染: ```js class App extends Component { constructor(props) { super(props) this.state = { content: 'ReactWebGL Component, Hello World', } } public render() { return ( {this.state.content} ) } } render(, game.stage) ``` 由于我们之前提过,`JSX` 是以 **变量传递** 的形式编译的。因此将 `` 转换成 `VNode` 后,其 **`vnode.type` 的值便是 `App` 这个类**,并不是类似 `div` 那样的 **字符串标签名**。所以接下来我们需要实现一个 **组件初始化** 的函数(`Component`)。这个函数的主要功能是: **实例化组件并获取组件 `render` 方法中返回的 元素节点。** ```js // 渲染组件 function renderComponent(compVNode) { const { type: Comp, props } = compVNode let instance = compVNode.instance // 当 instance 已经存在时,则不需要重新创建实例 if (!instance) { // 传入 props,初始化组件实例 // 支持 类组件 或者 函数组件 if (Comp.prototype && Comp.prototype.render) { instance = new Comp(props) } else { instance = new Component(props) instance.constructor = Comp instance.render = () => instance.constructor(props) } // 初次渲染时 // 将来的属性与状态其实便是与当前值一致 instance.__nextProps = props instance.__nextState = instance.state } // 调用 render 获取 VNode const vnode = instance.__createVNode() // 组件、元素、实例之间保持相互引用,有利于双向连接整棵虚拟树 instance.__component = compVNode compVNode.instance = instance compVNode.vnode = vnode vnode.component = compVNode return vnode } ``` 接下来我们只需要在 `createElm` 的函数加入: 当传入的为 **组件节点** 时调用函数初始化生成 **元素节点**,后续只需要继续原有的逻辑继续创建,便能正确渲染组件。 ```js function createElm(vnode) { // 当为组件时,初始化组件 // 重新赋值成 元素节点 if (typeof vnode.type === 'function') { vnode = renderComponent(vnode) } // 维持原有逻辑 ... return vnode.elm } ``` 保存执行,页面中已经能正确的渲染出 `` 组件了。延续之前的逻辑,在完成初次渲染后,接下来就是组件的更新。这就是我们最常使用的 `this.setState`了。 ## 第八站: 净化之匙 - setState 在上一篇文章中,我们实现了 **虚拟DOM** 更新函数 `diff`。参数为 **新旧虚拟节点(`oldVNode`、`newVNode`)**。所以组件更新的原理也一样: **获取组件实例先后渲染的新旧 `VNode` 再触发 `diff` 函数。** 在刚才 `Component `的渲染中,我们已经把 `render` 生成的 `VNode` 保存在 `this.__vnode` 上,这便是初始化时生成的 **旧虚拟节点(`oldVNode`)**。所以我们要做的就是: **`setState` 中 更新状态,调用 `render` 生成 新虚拟节点(`newVNode`),触发 `diff` 更新。** 因此我们在类上新增两个方法: `setState` 和 `__update`: ```js class Component { // 其余方法 ... // 更新函数 public __update = () => { // 临时存储 旧虚拟节点 (oldVNode) const oldVNode = this.__vnode this.__nextProps = this.props if (!this.__nextState) this.__nextState = this.state // 重新生成 新虚拟节点(newVNode) this.__vnode = this.__createVNode() // 调用 diff,更新 VNode diff(oldVNode, this.__vnode) } // 更新状态 public setState(partialState, callback?) { // 合并状态, 暂存于 即将更新态 中 if (typeof partialState === 'function') { partialState = partialState(this.state) } this.__nextState = { ...this.state, ...partialState, } // 调用更新,并执行回调 this.__update() callback && callback() } } ``` ### 更新优化 到这里我们可以看出: `setState` 封装了 `diff` 方法。但由于 `diff` 的复杂度,性能的优化会是一个我们需要着重考虑的点。每执行一次 `setState`,就需要重新生成 `newVNode` 进行 `diff`。因此,当组件非常复杂时或者连续更新时,可能会导致 **主进程的阻塞,造成页面的卡死。** 这里我们需要有两个优化: - **`setState` 异步化,避免阻塞主进程;** - **`setState` 合并,多次连续调用会被最终合并成一次;** - 同一组件 **连续多次更新**; - 父子级连续触发更新,由于 **父级更新其实已经包含子级的更新**,此时如果子级再自我更新一次,则就变成了一种无谓消耗; 为了这个优化,我们首先需要一个更新队列功能: - **可以异步化调用更新**; - **为组件标注,保证一次循环中单个组件只会更新一次**; 我们先来实现个 异步执行队列: ```js // 更新异步化,采用属于微任务的 Promise,兼容性使用 setTimeout // 这里使用微任务,可以保证宏任务的优先执行 // 保证例如 UI渲染 等更为重要的任务,避免页面卡顿; const defer = typeof Promise === 'function' ? Promise.prototype.then.bind(Promise.resolve()) : setTimeout // 更新队列 const updateQueue: any[] = [] // 队列更新 API export function enqueueRender(updater) { // 将所有 updater 同步推入更新队列中 // 为实例添加一个属性 __dirty,标识是否处于待更新状态 // 初始 和 更新完毕,该值会被置为 false // 推入队列时,标记为 true if ( !updater.__dirty && (updater.__dirty = true) && updateQueue.push(updater) === 1 ) { // 异步化冲洗队列 // 最终只执行一次冲洗 defer(flushRenderQueue) } } // 合并一次循环中多次 updater function flushRenderQueue() { if (updateQueue.length) { // 排序更新队列 updateQueue.sort() // 循环队列出栈 let curUpdater = updateQueue.pop() while (curUpdater) { // 当组件处于 待更新态 时,触发组件更新 // 如果该组件已经被更新完毕,则该状态为 false // 则后续的更新均不再执行 if (curUpdater.__dirty) { // 调用组件自身的更新函数 curUpdater.__update() // 执行 callback flushCallback(curUpdater) } curUpdater = updateQueue.pop() } } } // 执行缓存在 updater.__setStateCallbacks 上的回调 function flushCallback(updater) { const callbacks = updater.__setStateCallbacks let cbk if (callbacks && callbacks.length) { while (cbk = callbacks.shift()) cbk.call(updater) } } ``` 完成这个方法后,我们来修改下上面的 `setState` 函数: ```js class Component { // 其余方法 ... public setState(partialState = {}, callback?) { // 合并状态, 暂存于 即将更新态 中 // 处理参数为函数的形式 if (typeof partialState === 'function') { partialState = partialState(this.state, this.props) } this.__nextState = { ...this.state, ...partialState, } // 缓存回调 callback && this.__setStateCallbacks.push(callback) // 把组件自身先推入更新队列 enqueueUpdate(this) } } ``` 此时,由于 **更新队列** 为异步的,因此当多次连续调用 `setState` 时,组件的状态会被 **同步合并**,待全部完成后,才会进入更新队列的冲洗并最终只执行一次组件更新。 React 中组件还有个更新的方法: `forceUpdate(callback)`,该方法的功能其实是与 `this.setState({}, callback)` 相同的,唯一有个需要注意的点就是: **触发的更新不需要经过 `shouldComponentUpdate` 的判断**,实现只需要加个标识位即可。 ### 优化策略 回到性能优化这个点,从这里的简单实现我们可以看出: 虽然异步化了更新流程,但本质上仍然没有解决 复杂的组件 `diff` 带来长时间执行阻塞主进程。我记得以前文章有说过: **最有效的性能优化方式就是 异步、任务分割 和 缓存策略。** #### 1. 异步化: 通过把同步的代码执行变成异步,把串行变成并行,可以有效提高 **执行的时间利用率** 和 **保证代码优先级**。从这里可以延伸出两种优化方向: - 1. **异步**: 如我们上面所做的优化,这样能保证主进程的执行优先级,保证页面渲染或者更主要任务的优先执行,避免卡顿; - 2. **并行**: 通过把某些高消耗的操作放到 **非主进程** 上执行,例如 worker 线程。不过由于 `diff` 本身就较为复杂,还要需要处理好主进程与线程之间的交互,会导致复杂度极高,但也并非不可行,后续也许是个优化方向。 - 例如我就在思考在这里引入 wasm 的可能性,代价与收益比如何,有兴趣的童鞋可以一起探讨。 #### 2. 任务分割 将原本会阻塞主进程的 **大块逻辑执行进行拆解,分割成一个个小任务**。从而可以在逻辑中找到合适的时机点 **分段执行**,即 **不会阻塞主进程,又可以让代码快速高效的执行,最大化利用物理资源。** Facebook 的大神们选择了这条优化方向,这就是 React 16 新引入的 `Fiber` 理念的最主要目的。上面我们实现的 `diff` 中,有着一个很大的障碍: **一棵完整 虚拟DOM树 更新,必须一次性更新完成,中间无法被暂停,也无法被分割。** 而 `Fiber` 最主要的功能就是 **指针映射,保存上一个更新的组件与下一步需要更新的组件**,从而完成 **可暂停可重启**。计算进程的运行时间,利用浏览器的 `requestIdleCallback` 与 `requestAnimationFrame` 接口,当有优先级更高的任务时,优先执行,暂停下一个组件的更新。待空闲时再重启更新。 `Fiber` 算是一种编程思想,在其它语言中也有许多应用(`Ruby Fiber`)。核心思想是: **任务拆分和协同,主动把执行权交给主线程,使主线程有时间空挡处理其他高优先级任务。** 但实现复杂度较高,为了本文便于理解,暂时并没有引入。等以后有机会我们再来一起深挖 `Fiber` 的实现,也许能成为更多使用场景的性能优化手段。 ### 子组件更新 在上篇文章中,我们优先实现了 `diffVNode` 方法用于更新 **元素节点**,但组件节点的更新与元素节点的更新是不同的。当出现组件嵌套的情况时,我们就需要一个新的方法(`diffComponent`)用于组件节点的更新。 与 **元素节点** 不同,组件节点之间的更新重要的是重渲染,类似于我们上面的 `setState`。 **复用已创建好的组件实例,根据新的 状态(`state`)与 属性(`props`) 重新执行 `render` 生成 元素节点,再递归比对**, 也就是说,我们需要在 `diffVNode` 外围再做一层判断处理: ```js function diff(oldVNode, newVNode) { if (isSameVNode(oldVNode, newVNode)) { if (typeof oldVNode.type === 'function') { // 组件节点 diffComponent(oldVNode, newVNode) } else { // 元素节点, // 直接执行比对 diffVNode(oldVNode, newVNode) } } else { // 新节点替换旧节点 ... } } // 组件比对 function diffComponent(oldCompVNode, newCompVNode) { const { instance, vnode: oldVNode, elm } = oldCompVNode const { props: nextProps } = newCompVNode if (instance && oldVNode) { instance.__dirty = false // 更新状态和属性 instance.__nextProps = nextProps if (!instance.__nextState) instance.__nextState = instance.state // 复用旧组件实例和元素 newCompVNode.instance = instance newCompVNode.elm = elm // 使用新属性、新状态,旧组件实例 // 重新生成 新虚拟DOM const newVNode = initComponent(newCompVNode) // 递归触发 diff diff(oldVNode, newVNode) } } ``` ## 第九站: 生命之泉 - Lifecycle 组件有一个相当重要的特征,便是具有 **生命周期**。不同的函数钩子对应了组件 **从初始化到销毁** 的各个关键时间点。主要是为了让业务方有能力 **插入组件的渲染工作流** 中,编写业务逻辑。我们先来简单梳理下最新 React 组件的生命周期: ### **首次渲染**: - **`constructor`** - 即组件的 **实例化时机**,通常可以用来设置初始化 `state`; - **`static getDerivedStateFromProps(nextProps, prevState)`** - 在组件的模板渲染中,我们通常使用的数据为 `state` 和 `props`,而 `props` 由父级传入,组件本身并无法直接修改,因此唯一的常见需求就是: **根据父级传入的 `props` 动态修改 `state`**。该生命周期就是为此而生; - 大家可能会有疑问: **该方法为什么为静态方法? 而不是常规的实例方法呢?** - 先肯定一点: **使用实例方法肯定也是能满足需求的**。但这个钩子比较特殊,它的执行时机是位于 **新状态合并之后,重渲染之前**,而且该方法会 **侵入更新机制** 中。如果在之中做例如修改状态之类的操作是十分不可控的。当设计为静态方法后,函数内部便无法访问组件实例,成为一个 **纯函数**,便能保证更新流程的安全与稳定。 - **`render()`** - 根据 `state` 和 `props`,生成 **虚拟DOM**; - **`componentDidMount()`** - **组件被创建成真实元素并渲染后** 被调用,此时可获取真实的元素状态,主要用于业务逻辑的执行,例如数据请求,事件绑定等; ### **更新阶段**: - **`static getDerivedStateFromProps(nextProps, prevState)`** - **`shouldComponentUpdate(nextProps, nextState)`** - 上篇文章中的 `diff` 优化策略中有提到: 为了减少 **无谓的更新消耗**,赋予组件一个可以 **主动中断更新流** 的 `API`。根据参数中的 **更新属性** 和 **更新状态**,业务方自行判断是否需要继续往下执行 `diff`,从而能有效地提升 **更新性能**; - 大家记得 React 中有种组件叫 **纯组件(`PureComponent`)** 吧,其实这个类继承于普通的 `Component` 上封装的,可以减少多余的 `render`,提升性能。 - 默认使用 `shouldComponentUpdate` 函数设定更新条件: **仅当 `props` 和 `state` 发生改变时,才会触发更新**。这里使用了 `Object` 浅层比对,也就是仅做第一层比对,即 **1. key 是否完全匹配;2. value 是否全等;** 所以如果需要超过一层的数据变动,纯组件即无法正确更新了; - 这也是为什么 React 提倡使用 **不变数据** 的原理,能有效地使用浅层比对; - **不变数据**: 提倡数据不可变,任何修改都需要返回一个新的对象,不能直接修改原对象,这样能有效提高比对效率,减少无谓性能损耗。 - **`render()`** - **`getSnapshotBeforeUpdate(prevProps, prevState)`** - 替换旧版的 `componentWillUpdate`,触发时机点: 在数据状态已更新,最新 `VNode` 已生成,但 **真实元素还未被更新**; - 可以用来在 **更新之前** 从真实元素或状态中获取计算一些信息,便于在更新后使用; - **`componentDidUpdate(prevProps, prevState, snapshot)`** - 组件更新完成后调用; - 可以用于 **监听数据变化**,使用 `setState` 时必须加条件,避免无限循环; ### **卸载阶段**: - **`componentWillUnmount()`** - 组件即将被销毁。通常可以用于 **解绑事件**、**清除数据**、**释放内存** 等功能; 我们也按这样的目标来在我们的 `Component` 上实现这些生命周期,那如何更好的组织生命周期呢?这里我考虑到的是: **组件作为元素的容器,生命周期的本质其实是 其渲染出的元素节点的生命周期**。 也就是说,关键点还是在于 **元素在视图中的工作流,何时被挂载 - 更新 - 卸载**。所以为了更好的维护性和拓展性,更理想的方式应该是 **为元素节点统一添加生命周期**,而不是单独为组件,这样可大大降低复杂度,增加可拓展性。 ### 节点生命周期 那我们第一步先根据上面需要的生命周期来理一下需要哪些时机: - **创建后(`create`)**; - **挂载后(`insert`)**; - **更新前(`willupdate`)**; - **更新后(`update`)**; - **删除前(`willremove`)**; 原理就很简单了,只需要在 `VNode` 工作流中的对应时期调用相应的生命周期函数即可。那我们现在 `VNode` 上新增一个属性 `hooks`,用于 **储存** 对应的生命周期函数: ```js interface VNode { ... hooks: { create?: (vnode) => void insert?: (vnode) => void willupdate?: (oldVNode, newVNode) => void update?: (oldVNode, newVNode) => void willremove?: (vnode) => void } } ``` 新增一个触发的方法(`fireVNodeHook`): ```js function fireVNodeHook(vnode, name, ...data) { // 根据 生命周期名称 // 执行储存在 VNode 上的对应函数即可 const { hooks: _hooks } = vnode if (_hooks) { hook = _hooks[name] hook && hook(...data) } } ``` 有了这层基础方法后,我们只需要分别在之前所写的 **渲染与更新** 流程中的各个函数适时地触发就行了。 #### 1. `create` 该时机是在 **元素被创建后,但还未被挂载之前**。由于我们之前将逻辑统一收归为 `createElm`,因此只需要在该函数末尾统一加入触发即可。 ```js function createElm(vnode) { // 创建元素逻辑 ... // 触发 虚拟DOM 上储存的 钩子函数 fireVNodeHook(vnode, 'create', vnode) return vnode.elm } ``` #### 2. `insert` **元素被挂载到视图** 上的时机。从元素的角度来看,就是被 `append` 到父级中的时机点。这个时机点比较分散,但也比较好加入,找到我们使用 `Api` 中的 `append` 方法加入,总共有三个地方: - `render` 函数中加入对 **根节点** 的触发; - `createElm` 函数中加入对 **所有子级** 的触发; - `diffChildren` 列表比对中 **新增列表项** 的触发; #### 3. `willupdate` 与 `update` **更新之前** 与 **更新之后**,对应的便是我们的 `diff` 函数。由于最终均需走到 `diffVNode` 中,因此只需要在 `diffVNode` 开头和末尾触发即可。 #### 4. `willremove` **元素被卸载时**,其实与 `insert` 类似,只需要关注 `Api` 中 `removeChild` 的调用时机即可。在 `diff` 列表比对期间,当新列表中不存在时,我们需要删除旧列表中的元素,也就是之前写的业务函数 `removeVNodes`。 ### 组件生命周期 由于 **元素节点** 才是贯穿整棵 **虚拟DOM** 渲染与更新的关键,因此我们上面先实现的是对 **元素节点** 的生命周期触发。但是我们最终需要是 **组件节点** 的生命周期。由于 **组件节点** 与 **元素节点** 为一一对应的 **上下层级关系**,因此这里我们还需要做一层转接: **把组件节点的生命周期赋值给其生成的元素节点**。 首先我们先来为组件定义上生命周期,并定义一个中转对象 `__hooks`,实现 **组件节点周期与元素节点周期的转换**: ```js class Component { public __hooks = { // 元素节点 插入时机,触发 didMount insert: () => this.componentDidMount(), // 元素节点 更新之前,触发 getSnapshotBeforeUpdate willupdate: (vnode) => { this.__snapshot = this.getSnapshotBeforeUpdate(this.__prevProps, this.__prevState) }, // 元素节点 更新之后, 触发 didUpdate update: (oldVNode, vnode) => { this.componentDidUpdate(this.__prevProps, this.__prevState, this.__snapshot) this.__snapshot = undefined }, // 元素节点 卸载之前, 触发 willUnmount willremove: (vnode) => this.componentWillUnmount(), } // 默认生命周期函数 // getDerivedStateFromProps(nextProps, state) public getSnapshotBeforeUpdate(prevProps, prevState) { return undefined } public shouldComponentUpdate(nextProps, nextState) { return true } public componentDidMount() { } public componentDidUpdate(prevProps, prevState, snapshot) { } public componentWillUnmount() { } } ``` 然后我们只需要在 `__createVNode` 方法中将 `this.__hooks` 赋值给生成出的 `VNode` 即可: ```js class Component { ... public __createVNode() { // ... this.__vnode = this.render() // 赋值给对应的 元素节点, // 实现该 元素节点 与 组件 之间生命周期的绑定 this.__vnode.hooks = this.__hooks return this.__vnode } } ``` 最后,大家可能发现我们还有两个钩子没有实现: `getDerivedStateFromProps` 和 `shouldComponentUpdate`,这是因为这两个生命周期会影响到更新结果,因此需要 **深入到更新流程中**,无法单纯的通过 **元素节点** 的生命周期来实现。 但其实也很简单,就是在 **更新之前**,需要根据这两个函数的返回结果,适当调整下更新逻辑即可: ```js // Componet 中的 __update 方法 class Component { // ... public __update = () => { // 临时存储 旧虚拟节点 (oldVNode) const oldVNode = this.__vnode this.__nextProps = this.props if (!this.__nextState) this.__nextState = this.state // 执行 getDerivedStateFromProps // 更新 state 状态 const cls = this.constructor if (cls.getDerivedStateFromProps) { const state = cls.getDerivedStateFromProps(this.__nextProps, this.state) if (state) { this.__nextState = Object.assign(this.__nextState, state) } } // 在 diff 之前调用 shouldComponentUpdate 进行判断 // true: 生成新 VNode,继续 diff // false: 清空状态 if (this.shouldComponentUpdate(this.props, this.__nextState)) { // 重新生成 新虚拟节点(newVNode) this.__vnode = this.__createVNode() // 调用 diff,更新 VNode diff(oldVNode, this.__vnode) } else { // 清空状态更新 this.__nextProps = this.__nextState = undefined } // 刚才 异步更新队列 中标识的组件 待更新状态 // 在更新完后置为 false this.__dirty = false } } ``` 组件更新还有另外一个地方,即 `diffComponent`,也需要加入上述类似的执行和判断。完成这部分代码后,我们来简单测试个 DEMO: - 1. ``、`` **正确渲染**; - 2. 双组件 **渲染生命周期** 与预期一致; - 3. **触发更新**,调用 ` setState`,`` 文字元素正确更新; - 4. 双组件 **更新生命周期** 与预期一致; ![](https://user-gold-cdn.xitu.io/2020/3/24/1710a381f500d6d3?w=550&h=417&f=gif&s=4875150)

图1. 生命周期演示DEMO

## 最后一站: 旅途之末 在这系列文章中,我们实现了 React 最核心的部分: **JSX、组件、渲染、更新**。我们基于 **动手实践** 的方式,循序渐进地探讨了一些原理与策略,得出一些最佳实践。相信走完这遍旅程后,大家能对 React 有了更深层次的了解,能够给到各位小伙伴启发与帮助。其实我也一样,也是在这个旅程中跟大家一起共同学习,共同成长。 **[React 实践揭秘之旅,中高级前端必备(上) ->](https://github.com/xd-tayde/blog/blob/master/ReactGL-1.md)** 还有许多模块,如 `Context`、`Refs`、`Fragment` 和 一些全局API,如 `cloneElement` 等,还有代码中一些更严谨的判断及边界情况的处理,并没有在文章中体现,主要是由于这些部分更多的是纯逻辑的扩展,同时也是为了便于理解。如果童鞋们有兴趣,可以到 github 中查看完整版代码: **[react-webgl.js ->](https://github.com/xd-tayde/react-webgl.js)** 另外我也想稍微唠嗑下关于 React-WebGL 这个想法。 近阶段我接触了一些 Web 游戏的开发,有了一些从前端开发者出发的思考与理解。在游戏开发领域,传统的游戏开发者有着一套与前端领域完全不同的思维编程模式。随着 Web 的发展,使他们需要拓展到 Js 的环境中。所以出现了一系列的游戏引擎库,本质上是从其它平台的库移植过来的。当我从一个前端开发者的角度在进行开发时,其实并不是说入门难,学习成本高,而是给我的感觉是: 类似用纯原生 js 在写页面,觉得效率低下。所以这也是 React-WebGL 的出发点,期望能将现在 Web 中更优秀的理念运用到游戏开发,甚至找到一种更高效的开发模式,提升效率,完善生态。 当然,这仅仅只是一个起点, 游戏开发 与 界面开发 确实有着许多异同点,**如何找到一种更现代化更高效的 Web 游戏开发模式**,这还需要很长的一段旅程。我也一直在思考,一直在摸索,相信能有一些好玩的东西。**没有尝试,没有努力,就千万别在起点就放弃了**。有什么问题,有什么想法,直接找我一起探讨哈。🙃~~ > Tips: > > 看博主写得这么辛苦下,跪求点赞、关注、Star![更多文章猛戳 ->](https://github.com/xd-tayde/blog) > > 邮箱: 159042708@qq.com 微信/QQ: 159042708 **[祝福#感恩#武汉加油##RIP KOBE#]()**