摘要:如果列表是空的,则存入组件后将异步刷新任务加入到事件循环当中。四总结本文基于上一个版本的代码,加入了事件处理功能,同时通过异步刷新的方法提高了渲染效率。
欢迎关注我的公众号睿Talk,获取我最新的文章:
目前最流行的两大前端框架,React和Vue,都不约而同的借助Virtual DOM技术提高页面的渲染效率。那么,什么是Virtual DOM?它是通过什么方式去提升页面渲染效率的呢?本系列文章会详细讲解Virtual DOM的创建过程,并实现一个简单的Diff算法来更新页面。本文的内容脱离于任何的前端框架,只讲最纯粹的Virtual DOM。敲单词太累了,下文Virtual DOM一律用VD表示。
这是VD系列文章的第六篇,以下是本系列其它文章的传送门:
你不知道的Virtual DOM(一):Virtual Dom介绍
你不知道的Virtual DOM(二):Virtual Dom的更新
你不知道的Virtual DOM(三):Virtual Dom更新优化
你不知道的Virtual DOM(四):key的作用
你不知道的Virtual DOM(五):自定义组件
你不知道的Virtual DOM(六):事件处理&异步更新
今天,我们继续在之前项目的基础上扩展功能。在上一篇文章中,介绍了自定义组件的渲染和更新的实现方法。为了验证setState是否生效,还定义了一个setTimeout方法,5秒后更新state。在现实的项目中,state的改变往往是通过事件触发的,如点击事件、键盘事件和滚动事件等。下面,我们就将事件处理加入到项目当中。
二、实现事件处理事件的绑定一般是定义在元素或者组件的属性当中,之前对属性的初始化和更新没有考虑支持事件,只是简单的赋值操作。
// 属性赋值 function setProps(element, props) { // 属性赋值 element[ATTR_KEY] = props; for (let key in props) { element.setAttribute(key, props[key]); } } // 比较props的变化 function diffProps(newVDom, element) { let newProps = {...element[ATTR_KEY]}; const allProps = {...newProps, ...newVDom.props}; // 获取新旧所有属性名后,再逐一判断新旧属性值 Object.keys(allProps).forEach((key) => { const oldValue = newProps[key]; const newValue = newVDom.props[key]; // 删除属性 if (newValue == undefined) { element.removeAttribute(key); delete newProps[key]; } // 更新属性 else if (oldValue == undefined || oldValue !== newValue) { element.setAttribute(key, newValue); newProps[key] = newValue; } } ) // 属性重新赋值 element[ATTR_KEY] = newProps; }
setProps是在创建元素的时候调用的,而diffProps则是在diff过程中调用的。如果需要支持事件绑定,我们需要多做一个判断。如果属性名称是on开头的话,比如onClick,我们就要在当前元素上注册或删除一个事件处理。
// 属性赋值 function setProps(element, props) { // 属性赋值 element[ATTR_KEY] = props; for (let key in props) { // on开头的属性当作事件处理 if (key.substring(0, 2) == "on") { const evtName = key.substring(2).toLowerCase(); element.addEventListener(evtName, evtProxy); (element._evtListeners || (element._evtListeners = {}))[evtName] = props[key]; } else { element.setAttribute(key, props[key]); } } } function evtProxy(evt) { this._evtListeners[evt.type](evt); } // 比较props的变化 function diffProps(newVDom, element) { let newProps = {...element[ATTR_KEY]}; const allProps = {...newProps, ...newVDom.props}; // 获取新旧所有属性名后,再逐一判断新旧属性值 Object.keys(allProps).forEach((key) => { const oldValue = newProps[key]; const newValue = newVDom.props[key]; // on开头的属性当作事件处理 if (key.substring(0, 2) == "on") { const evtName = key.substring(2).toLowerCase(); if (newValue) { element.addEventListener(evtName, evtProxy); } else { element.removeEventListener(evtName, evtProxy); } (element._evtListeners || (element._evtListeners = {}))[evtName] = newValue; } else { // 删除属性 if (newValue == undefined) { element.removeAttribute(key); delete newProps[key]; } // 更新属性 else if (oldValue == undefined || oldValue !== newValue) { element.setAttribute(key, newValue); newProps[key] = newValue; } } } ) // 属性重新赋值 element[ATTR_KEY] = newProps; }
所有的事件处理函数都存到dom元素的_evtListeners当中,当事件触发的时候,将事件传给里面对应的方法处理。这样做的好处是如果以后要对浏览器传入的事件evt做进一步的封装,就可以在evtProxy函数里面处理。
接下来,我们在自定义组件里面新增一个onClick事件,在点击的时候改变state里面的值。
class MyComp extends Component { constructor(props) { super(props); this.state = { name: "Tina", count: 1 } } elmClick() { this.setState({name: `Jack${this.state.count}`, count: this.state.count + 1 }); } render() { return() } }This is My Component! {this.props.count}name: {this.state.name}
项目运行的效果是每当我点一下MyComp组件的区域,里面的name就会随之马上更新。
三、setState异步更新用过React的朋友都知道,为了减少不必要的渲染,提高性能,React并不是在我们每次setState的时候都进行渲染,而是将一个同步操作里面的多个setState进行合并后再渲染,给人异步渲染的感觉。看过源码的都应该知道,React是通过事务的方式来合并多个setState操作的,本质来说还是同步的。如果想对其作更深入的学习,推荐看这篇文章。
为了达到合并操作,减少渲染的效果,最简单的方式就是异步渲染,下面我们来看看如何实现。在上一个版本里,setState是这么定义的:
class Component { ... setState(newState) { this.state = {...this.state, ...newState}; const vdom = this.render(); diff(this.dom, vdom, this.parent); } ... };
state更新后直接就进行diff操作,进而更新页面。如果我们onClick里面的代码改成这样:
elmClick() { this.setState({name: `Jack${this.state.count}`, count: this.state.count + 1 }); this.setState({name: `Jack${this.state.count}`, count: this.state.count + 1 }); }
页面会渲染2次。如果我们把它改造成下面的样子:
// 等待渲染的组件数组 let pendingRenderComponents = []; class Component { ... setState(newState) { this.state = {...this.state, ...newState}; enqueueRender(this); } ... }; function enqueueRender(component) { // 如果push后数组长度为1,则将异步刷新任务加入到事件循环当中 if (pendingRenderComponents.push(component) == 1) { if (typeof Promise=="function") { Promise.resolve().then(renderComponent); } else { setTimeout(renderComponent, 0); } } } function renderComponent() { // 组件去重 const uniquePendingRenderComponents = [...new Set(pendingRenderComponents)]; // 渲染组件 uniquePendingRenderComponents.forEach(component => { const vdom = component.render(); diff(component.dom, vdom, component.parent); }); // 清空待渲染列表 pendingRenderComponents = []; }
当第一次setState成功后,并不会马上进行渲染,而是将组件存入待渲染组件列表当中。如果列表是空的,则存入组件后将异步刷新任务加入到事件循环当中。当运行环境支持Promise时,通过微任务运行,否则通过宏任务运行。微任务的运行时间是当前事件循环的末尾,而宏任务的运行时间是下一个事件循环。所以优先使用微任务。
紧接着进行第二次setState操作,同样的,将组件存入待渲染组件列表当中。此时,主线程的任务执行完了,开始执行异步任务。
当异步刷新任务启动时,将待渲染列表去重后对里面的组件进行渲染。等渲染完成后再清空待渲染列表。此时,渲染出来的是2次setState合并后的结果,并且只会进行一次diff操作,渲染一次。
四、总结本文基于上一个版本的代码,加入了事件处理功能,同时通过异步刷新的方法提高了渲染效率。
这是VD系列的最后一篇文章。本系列从什么是Virtual Dom这个问题出发,讲解了VD的数据结构、比较方式和更新流程,并在此基础上进行功能扩展和性能优化,支持key元素复用、自定义组件,dom事件绑定和setState异步更新。总共三百多行代码,实现了mvvm库的核心功能。
有关VD,如果还有什么想了解的,欢迎留言,有问必答。
P.S.: 想看完整代码见这里,如果有必要建一个仓库的话请留言给我:代码
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/97573.html
摘要:现在流行的前端框架都支持自定义组件,组件化开发已经成为提高前端开发效率的银弹。二对自定义组件的支持要想正确的渲染组件,第一步就是要告诉某个标签是自定义组件。下面的例子里,就是一个自定义组件。解决了识别自定义标签的问题,下一步就是定义标签了。 欢迎关注我的公众号睿Talk,获取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、...
摘要:最后里面没有第四个元素了,才会把苹果从移除。四总结本文基于上一个版本的代码,加入了对唯一标识的支持,很好的提高了更新数组元素的效率。 欢迎关注我的公众号睿Talk,获取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的两大前端框架,React和Vue,都不约而同的借助Virtual DOM技术提高页面的渲染...
摘要:不同的框架对这三个属性的命名会有点差别,但表达的意思是一致的。它们分别是标签名属性和子元素对象。我们先来看下页面的更新一般会经过几个阶段。元素有可能是数组的形式,需要将数组解构一层。 欢迎关注我的公众号睿Talk,获取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的两大前端框架,React和Vue,都不约...
摘要:变化的只有种更新和删除。页面的元素的数量随着而变。四总结本文详细介绍如何实现一个简单的算法,再根据计算出的差异去更新真实的。 欢迎关注我的公众号睿Talk,获取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的两大前端框架,React 和 Vue,都不约而同的借助 Virtual DOM 技术提高页面的渲染...
摘要:经过这次优化,计算的时间快了那么几毫秒。基于当前这个版本的代码还能做怎样的优化呢,请看下一篇的内容你不知道的四的作用。 欢迎关注我的公众号睿Talk,获取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的两大前端框架,React和Vue,都不约而同的借助Virtual DOM技术提高页面的渲染效率。那么,什...
阅读 2991·2021-10-13 09:39
阅读 2690·2021-09-27 13:34
阅读 2029·2019-08-30 15:55
阅读 3258·2019-08-30 15:43
阅读 3628·2019-08-30 11:16
阅读 1746·2019-08-26 18:28
阅读 1280·2019-08-26 13:56
阅读 911·2019-08-26 13:35