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

引言

上一篇文章咱们主要实现了 JSXWebGL 上的渲染与更新,对 虚拟DOMDiff 有了更深的了解,但相比于咱们使用的 React,还缺少了之中很重要的一环 --- 组件模式前端

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

在这篇文章中,咱们会在上一篇的实现基础上,加入 React 组件模式,并在实现的过程当中适时的去讲解一些原理和思惟,也有利于你们 由浅入深的理解编程思惟上的提高react

因为下篇是彻底以上篇做为基础的。因此若是你还没看过上篇,请优先猛戳:git

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

第七站: 破碎之墟 - Component

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

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

一般咱们须要引 JSCSS,再在 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。参数为 新旧虚拟节点(oldVNodenewVNode)。因此组件更新的原理也同样:

获取组件实例前后渲染的新旧 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 最主要的功能就是 指针映射,保存上一个更新的组件与下一步须要更新的组件,从而完成 可暂停可重启。计算进程的运行时间,利用浏览器的 requestIdleCallbackrequestAnimationFrame 接口,当有优先级更高的任务时,优先执行,暂停下一个组件的更新。待空闲时再重启更新。

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)

    • 在组件的模板渲染中,咱们一般使用的数据为 stateprops,而 props 由父级传入,组件自己并没有法直接修改,所以惟一的常见需求就是: 根据父级传入的 props 动态修改 state。该生命周期就是为此而生;
    • 你们可能会有疑问: 该方法为何为静态方法? 而不是常规的实例方法呢?

      • 先确定一点: 使用实例方法确定也是能知足需求的。但这个钩子比较特殊,它的执行时机是位于 新状态合并以后,重渲染以前,并且该方法会 侵入更新机制 中。若是在之中作例如修改状态之类的操做是十分不可控的。当设计为静态方法后,函数内部便没法访问组件实例,成为一个 纯函数,便能保证更新流程的安全与稳定。
  • render()

    • 根据 stateprops,生成 虚拟DOM
  • componentDidMount()

    • 组件被建立成真实元素并渲染后 被调用,此时可获取真实的元素状态,主要用于业务逻辑的执行,例如数据请求,事件绑定等;

更新阶段:

  • static getDerivedStateFromProps(nextProps, prevState)
  • shouldComponentUpdate(nextProps, nextState)

    • 上篇文章中的 diff 优化策略中有提到: 为了减小 无谓的更新消耗,赋予组件一个能够 主动中断更新流API。根据参数中的 更新属性更新状态,业务方自行判断是否须要继续往下执行 diff,从而能有效地提高 更新性能
    • 你们记得 React 中有种组件叫 纯组件(PureComponent) 吧,其实这个类继承于普通的 Component 上封装的,能够减小多余的 render,提高性能。

      • 默认使用 shouldComponentUpdate 函数设定更新条件: 仅当 propsstate 发生改变时,才会触发更新。这里使用了 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. willupdateupdate

更新以前更新以后,对应的即是咱们的 diff 函数。因为最终均需走到 diffVNode 中,所以只须要在 diffVNode 开头和末尾触发便可。

4. willremove

元素被卸载时,其实与 insert 相似,只须要关注 ApiremoveChild 的调用时机便可。在 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
    }
}

最后,你们可能发现咱们还有两个钩子没有实现: getDerivedStateFromPropsshouldComponentUpdate,这是由于这两个生命周期会影响到更新结果,所以须要 深刻到更新流程中,没法单纯的经过 元素节点 的生命周期来实现。

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

// 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. 双组件 更新生命周期 与预期一致;

<p style="text-align: center;font-weight: bold;">图1. 生命周期演示DEMO</p>

最后一站: 旅途之末

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

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

还有许多模块,如 ContextRefsFragment 和 一些全局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#]()

相关文章
相关标签/搜索