React 实践揭秘之旅,中高级前端必备(下)

4,475 阅读21分钟

引言

上一篇文章我们主要实现了 JSX 在 WebGL 上的渲染与更新,对 虚拟DOM 和 Diff 有了更深的了解,但相比于我们使用的 React,还缺乏了之中很重要的一环 --- 组件模式。

想必大家能认同,React组件(Component)具有 强大的功能,高拓展性和高解耦性,在其基础上构建的各种 UI 组件框架完全改变了传统的 Web 开发模式,成为了Web 中的大型复杂应用提供了一种很好的构建模式和保障,也让我们的开发效率也有了质的变化。

在这篇文章中,我们会在上一篇的实现基础上,加入 React 组件模式,并在实现的过程中适时的去讲解一些原理和思维,也有利于大家 由浅入深的理解 和 编程思维上的提升。

由于下篇是完全以上篇作为基础的。所以如果你还没看过上篇,请优先猛戳:

React 实践揭秘之旅,中高级前端必备(上) ->

第七站: 破碎之墟 - Component

代码复用性,向来是编程领域一个核心的理念。我们最常使用 函数、类 方式进行代码的封装,但这里有个痛点:

Web 中 UI 与 逻辑 的分离的特性,导致较难优雅地整合封装。

通常我们需要引 JS、CSS,再在 HTML 中按规定书写结构,最后再初始化库。整个过程十分割裂,且不优雅。

而 React Component 帮我们解决了这个痛点,即保持了 动态UI 与 逻辑 的分离,又有了全新的整合方式。从而让 UI 组件变得非常的 高效易用,只需要在结构中以标签的形式引入即可。

我们就继续在上篇文章的基础上,加入 Component 的特性。基于 JSX 的变量传递,我们只需要实现一个 Component 类,并针对性地去完成组件的 渲染和更新 即可。

TIPs:

由于组件的加入,这时我们的 虚拟DOM(VNode) 就包含了两种类型: 组件节点(compVNode) 和 元素节点(elVNode)。后续都会以此区分,便于讲解。

// 组件基类
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 的方式继承出 自定义组件 进行渲染:

class App extends Component {
    constructor(props) {
        super(props)
        this.state = {
            content: 'ReactWebGL Component, Hello World',
        }
    }
    public render() {
        return (
            <Container name="parent">
                {this.state.content}
            </Container>
        )
    }
}

render(<App />, game.stage)

由于我们之前提过,JSX 是以 变量传递 的形式编译的。因此将 <App /> 转换成 VNode 后,其 vnode.type 的值便是 App 这个类,并不是类似 div 那样的 字符串标签名。所以接下来我们需要实现一个 组件初始化 的函数(Component)。这个函数的主要功能是:

实例化组件并获取组件 render 方法中返回的 元素节点。

// 渲染组件
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 的函数加入: 当传入的为 组件节点 时调用函数初始化生成 元素节点,后续只需要继续原有的逻辑继续创建,便能正确渲染组件。

function createElm(vnode) {
	// 当为组件时,初始化组件
	// 重新赋值成 元素节点
    if (typeof vnode.type === 'function') {
        vnode = renderComponent(vnode) 
    }
    
    // 维持原有逻辑
    ...
    
    return vnode.elm
}

保存执行,页面中已经能正确的渲染出 <App> 组件了。延续之前的逻辑,在完成初次渲染后,接下来就是组件的更新。这就是我们最常使用的 this.setState了。

第八站: 净化之匙 - setState

在上一篇文章中,我们实现了 虚拟DOM 更新函数 diff。参数为 新旧虚拟节点(oldVNode、newVNode)。所以组件更新的原理也一样:

获取组件实例先后渲染的新旧 VNode 再触发 diff 函数。

在刚才 Component的渲染中,我们已经把 render 生成的 VNode 保存在 this.__vnode 上,这便是初始化时生成的 旧虚拟节点(oldVNode)。所以我们要做的就是:

setState 中 更新状态,调用 render 生成 新虚拟节点(newVNode),触发 diff 更新。

因此我们在类上新增两个方法: setState 和 __update:

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 合并,多次连续调用会被最终合并成一次;

    • 同一组件 连续多次更新;

    • 父子级连续触发更新,由于 父级更新其实已经包含子级的更新,此时如果子级再自我更新一次,则就变成了一种无谓消耗;

为了这个优化,我们首先需要一个更新队列功能:

  • 可以异步化调用更新;

  • 为组件标注,保证一次循环中单个组件只会更新一次;

我们先来实现个 异步执行队列:

// 更新异步化,采用属于微任务的 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 函数:

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. 异步: 如我们上面所做的优化,这样能保证主进程的执行优先级,保证页面渲染或者更主要任务的优先执行,避免卡顿;
    1. 并行: 通过把某些高消耗的操作放到 非主进程 上执行,例如 worker 线程。不过由于 diff 本身就较为复杂,还要需要处理好主进程与线程之间的交互,会导致复杂度极高,但也并非不可行,后续也许是个优化方向。
    • 例如我就在思考在这里引入 wasm 的可能性,代价与收益比如何,有兴趣的童鞋可以一起探讨。

2. 任务分割

将原本会阻塞主进程的 大块逻辑执行进行拆解,分割成一个个小任务。从而可以在逻辑中找到合适的时机点 分段执行,即 不会阻塞主进程,又可以让代码快速高效的执行,最大化利用物理资源。

Facebook 的大神们选择了这条优化方向,这就是 React 16 新引入的 Fiber 理念的最主要目的。上面我们实现的 diff 中,有着一个很大的障碍:

一棵完整 虚拟DOM树 更新,必须一次性更新完成,中间无法被暂停,也无法被分割。

而 Fiber 最主要的功能就是 指针映射,保存上一个更新的组件与下一步需要更新的组件,从而完成 可暂停可重启。计算进程的运行时间,利用浏览器的 requestIdleCallback 与 requestAnimationFrame 接口,当有优先级更高的任务时,优先执行,暂停下一个组件的更新。待空闲时再重启更新。

Fiber 算是一种编程思想,在其它语言中也有许多应用(Ruby Fiber)。核心思想是:

任务拆分和协同,主动把执行权交给主线程,使主线程有时间空挡处理其他高优先级任务。

但实现复杂度较高,为了本文便于理解,暂时并没有引入。等以后有机会我们再来一起深挖 Fiber 的实现,也许能成为更多使用场景的性能优化手段。

子组件更新

在上篇文章中,我们优先实现了 diffVNode 方法用于更新 元素节点,但组件节点的更新与元素节点的更新是不同的。当出现组件嵌套的情况时,我们就需要一个新的方法(diffComponent)用于组件节点的更新。

与 元素节点 不同,组件节点之间的更新重要的是重渲染,类似于我们上面的 setState。

复用已创建好的组件实例,根据新的 状态(state)与 属性(props) 重新执行 render 生成 元素节点,再递归比对,

也就是说,我们需要在 diffVNode 外围再做一层判断处理:

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,用于 储存 对应的生命周期函数:

interface VNode {
	...
	
	hooks: {
        create?: (vnode) => void
        insert?: (vnode) => void
        willupdate?: (oldVNode, newVNode) => void
        update?: (oldVNode, newVNode) => void
        willremove?: (vnode) => void
    }
}

新增一个触发的方法(fireVNodeHook):

function fireVNodeHook(vnode, name, ...data) {
    // 根据 生命周期名称
    // 执行储存在 VNode 上的对应函数即可
    const { hooks: _hooks } = vnode
    if (_hooks) {
        hook = _hooks[name]
        hook && hook(...data)
    }
}

有了这层基础方法后,我们只需要分别在之前所写的 渲染与更新 流程中的各个函数适时地触发就行了。

1. create

该时机是在 元素被创建后,但还未被挂载之前。由于我们之前将逻辑统一收归为 createElm,因此只需要在该函数末尾统一加入触发即可。

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,实现 组件节点周期与元素节点周期的转换:

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 即可:

class Component {
	...
	
	public __createVNode() {
	    // ...
	    this.__vnode = this.render()
	    
	    // 赋值给对应的 元素节点,
	    // 实现该 元素节点 与 组件 之间生命周期的绑定
	    this.__vnode.hooks = this.__hooks
	    
	    return this.__vnode
	}
}

最后,大家可能发现我们还有两个钩子没有实现: getDerivedStateFromProps 和 shouldComponentUpdate,这是因为这两个生命周期会影响到更新结果,因此需要 深入到更新流程中,无法单纯的通过 元素节点 的生命周期来实现。

但其实也很简单,就是在 更新之前,需要根据这两个函数的返回结果,适当调整下更新逻辑即可:

// 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. <App>、<BBB txt={this.state.txt} /> 正确渲染;
    1. 双组件 渲染生命周期 与预期一致;
    1. 触发更新,调用 <App> setState,<BBB> 文字元素正确更新;
    1. 双组件 更新生命周期 与预期一致;

图1. 生命周期演示DEMO

最后一站: 旅途之末

在这系列文章中,我们实现了 React 最核心的部分: JSX、组件、渲染、更新。我们基于 动手实践 的方式,循序渐进地探讨了一些原理与策略,得出一些最佳实践。相信走完这遍旅程后,大家能对 React 有了更深层次的了解,能够给到各位小伙伴启发与帮助。其实我也一样,也是在这个旅程中跟大家一起共同学习,共同成长。

React 实践揭秘之旅,中高级前端必备(上) ->

还有许多模块,如 Context、Refs、Fragment 和 一些全局API,如 cloneElement 等,还有代码中一些更严谨的判断及边界情况的处理,并没有在文章中体现,主要是由于这些部分更多的是纯逻辑的扩展,同时也是为了便于理解。如果童鞋们有兴趣,可以到 github 中查看完整版代码:

react-webgl.js ->

另外我也想稍微唠嗑下关于 React-WebGL 这个想法。

近阶段我接触了一些 Web 游戏的开发,有了一些从前端开发者出发的思考与理解。在游戏开发领域,传统的游戏开发者有着一套与前端领域完全不同的思维编程模式。随着 Web 的发展,使他们需要拓展到 Js 的环境中。所以出现了一系列的游戏引擎库,本质上是从其它平台的库移植过来的。当我从一个前端开发者的角度在进行开发时,其实并不是说入门难,学习成本高,而是给我的感觉是: 类似用纯原生 js 在写页面,觉得效率低下。所以这也是 React-WebGL 的出发点,期望能将现在 Web 中更优秀的理念运用到游戏开发,甚至找到一种更高效的开发模式,提升效率,完善生态。

当然,这仅仅只是一个起点, 游戏开发 与 界面开发 确实有着许多异同点,如何找到一种更现代化更高效的 Web 游戏开发模式,这还需要很长的一段旅程。我也一直在思考,一直在摸索,相信能有一些好玩的东西。没有尝试,没有努力,就千万别在起点就放弃了。有什么问题,有什么想法,直接找我一起探讨哈。🙃~~

Tips:

看博主写得这么辛苦下,跪求点赞、关注、Star!更多文章猛戳 ->

邮箱: 159042708@qq.com 微信/QQ: 159042708

祝福#感恩#武汉加油##RIP KOBE#