现代框架的本质其实就是Dom操作。特别喜欢今天看到的一句话。不要为自己设定限制。最后,大部分技术本质都是一样的。比如后端用到的Kafka、redis、sql事务写入,Nginx负载均衡算法、diff算法、GRPC、Pb协议序列化反序列化、锁等,同样可以被前端的大量逻辑复用完了,即使js和Node.js都是单线程的,仔细看完这篇文章和源码,你也会收获很多,哪个框架更好,谁更差,就像Web技术的开发效率一样而Native开发的用户体验,没有谁好。至于谁高谁低,但可以肯定的是,Web技术越来越接近原生体验。作者是跨平台桌面开发的前端工程师。因为是即时通讯应用,所以对项目性能要求非常高。于是找了名医。为了达到想要的性能,我最终选择了几个非常冷门的优化方案放在一起。虽然过程很曲折,但市面??上所有的方案我都用过,也试过了,后来发现,最终的优化并不是1+1=2。必须考虑业务场景,因为一旦优化方案太多,它们的技术出发点和考虑因素可能会发生冲突。这也是前端需要架构师的原因。如果前端有架构师来开发重度应用,会少走很多弯路。后端也是如此。Vue.js中keep-alive的使用:在Vue.js中,友达定义如下:keep-alive主要用于保持组件状态或者避免重新渲染基本使用:大体思路:切换也很流畅,没有任何闪屏特别提示:这里的每个组件下面都有1000行的列表~切换也是看了第二篇-层次图,开始整理源码。第一步是第一次渲染缓存。从'react-component-keepalive'导入{Provider,KeepAlive};把需要缓存和渲染的组件包装起来,给它一个name属性。例如:importContentfrom'./Content.jsx'exportdefaultAppextendsReact.PureComponent{render(){return(
)}}这样在需要二次渲染的时候就可以直接拿缓存渲染这个组件了。以下是一组缓存组件。仔细阅读上面的注释,再看看当前bodydiv中多出来的内容,那么它们是对应的吗?渲染将如何缓存?如何缓存和查找库的源码入口:importProviderfrom'./components/Provider';importKeepAlivefrom'./components/KeepAlive';importbindLifecyclefrom'./utils/bindLifecycle';importuseKeepAliveEffect来自'./utils/useKeepAliveEffect';export{Provider,KeepAlive,bindLifecycle,useKeepAliveEffect,};最重要的是先看看Provider和KeepAlive这两个组件:缓存组件的功能是通过React.createPortalAPI实现的react-component-keepalive有两个主要组件
和;负责保存组件的缓存,并在处理前通过React.createPortalAPI将缓存的组件渲染到应用程序外部。缓存的组件必须放在中,会将应用外渲染的组件挂载到实际需要显示的位置。这样就很清楚了,所以开始源码是这样的:这个.forceUpdate();}createStoreElement函数实际上是在body中创建了一个类似注解内容为UUIDimport{prefix}from'./createUniqueIdentification'的div标签;导出默认函数createStoreElement():HTMLElement{constkeepAliveDOM=document.createElement('div');keepAliveDOM.dataset.type=prefix;keepAliveDOM.style.display='无';document.body.appendChild(keepAliveDOM);returnkeepAliveDOM;}调用createStoreElement的结果:然后调用forceUpdate强制更新组件。这个组件内部有很多变量锁:exportinterfaceICacheItem{children:React.ReactNode;//自元素节点keepAlive:boolean;//是否缓存lifecycle:LIFECYCLE;//枚举生命周期名称renderElement?:HTMLElement;//渲染的dom节点激活?:布尔值;//激活?ifStillActivate?:布尔值;//是否保持激活再激活?:()=>void;//重新激活函数}exportinterfaceICache{[key:string]:ICacheItem;}导出接口IKeepAliveProviderImpl{storeElement:HTMLElement;//刚才在body中渲染的div节点cache:ICache;//缓存遵循接口ICache一个对象key-value格式keys:string[];//缓存队列是一个数组,里面的每个key是一个字符串,一个标识符eventEmitter:any;//这是我自己写的自定义事件触发模块exists:boolean;//退出状态providerIdentification:string;//提供标识setCache:(identification:string,value:ICacheItem)=>void;//设置缓存未激活:(identification:string)=>void;//设置非活动状态isExisted:()=>boolean;//是否退出,会返回当前组件的Existed值}上面的没看懂不要着急,看下面:接下来是Provider组件实际渲染的内容代码:{innerChildren}{keys.map(identification=>{constcurrentCache=cache[identification];const{keepAlive,children,lifecycle,}=currentCache;letcacheChildren=children;//省略中间的一些细节判断returnReactDOM.createPortal((cacheChildren?({identification}{cacheChildren}this.startMountingDOM(identification)}>{identification}</Comment>):null),storeElement,);})}innerChildren是传递给Provider的孩子。一开始我们看到的缓存组件的内容显示的是A评论内容,为什么东西可以渲染?Comment组件是关键的Comment组件publicrender(){return;}最初返回的是一个空的div标签,但是看它的生命周期constcommentNode=this.createComment();this.commentNode=commentNode;this.currentNode=节点;this.parentNode=node.parentNode作为节点;this.parentNode.replaceChild(commentNode,node);ReactDOM.unmountComponentAtNode(节点);this.props.onLoaded();}这个逻辑到这里还没说完,还需要进一步看KeepAlive组件源码KeepAlive源码:componentcomponentDidMount生命周期钩子:publiccomponentDidMount(){const{_container,}=this.道具;常量{notNeedActivate,identification,eventEmitter,keepAlive,}=_container;不需要激活();constcb=()=>{this.mount();这个。听();eventEmitter.off([identification,START_MOUNTING_DOM],cb);};eventEmitter.on([identification,START_MOUNTING_DOM],cb);if(keepAlive){this.componentDidActivate();}}忽略其他逻辑,关注:constcb=()=>{this.mount();this.listen();eventEmitter.off([identification,START_MOUNTING_DOM],cb);};eventEmitter.on([identification,START_MOUNTING_DOM],cb);当接收到的事件被触发时,调用`mout和listen`方法,然后取消监听this.setMounted(true);const{renderElement}=cache[identification];setLifecycle(LIFECYCLE.UPDATING);changePositionByComment(标识,renderElement,storeElement);}changePositionByComment函数是整个调用的重点,下面会解析privatelisten(){const{_container:{identification,eventEmitter,},}=this.props;eventEmitter.on([identification,COMMAND.CURRENT_UNMOUNT],this.bindUnmount=this.componentWillUnmount.bind(this),);eventEmitter.on([identification,COMMAND.CURRENT_UNACTIVATE],this.bindUnactivate=this.componentWillUnactivate.bind(this),);}listen函数监听自定义事件以触发componentWillUnmount和componentWillUnactivateCOMMAND。CURRENT_UNMOUNT这些都是枚举而已经changePositionByComment函数:exportdefaultfunctionchangePositionByComment(identification:string,presentParentNode:Node,originalParentNode:Node){if(!presentParentNode||!原始父节点){返回;}constelementNodes=findElementsBetweenComments(originalParentNode,identification);constcommentNode=findComment(presentParentNode,identification);如果(!elementNodes.length||!commentNode){返回;}元素tNodes.push(elementNodes[elementNodes.length-1].nextSibling作为节点);elementNodes.unshift(elementNodes[0].previousSibling作为节点);//使用commet组件删除注释元素会导致组件卸载错误for(leti=elementNodes.length-1;i>=0;i--){presentParentNode.insertBefore(elementNodes[i],commentNode);}originalParentNode.appendChild(commentNode);}老规矩,上图分析源码:很多人看是云里雾里,其实最终的本质是通过Coment组件的comments来找到对应的真实节点需要渲染然后替换它们。这些节点都缓存在内存中,DOM操作速度比帧比较快很多。这里又体现了这个库,不管路由组件是否可以使用,虚拟列表+缓存KeepAlive组件Demo体验地址库原链接地址为了项目安全,我自己重新建库,自定义开发这个库。感谢原作者的贡献。当我遇到问题时,也第一时间给我技术支持。谢谢你!新库称为react-component-keepalive,可以直接在npm中找到。npmireact-component-keepalive可以正常使用。如果你对React了解不多,你可以阅读我之前的一些文章:从头开始编写React框架如何优化你的超大型React应用程序?欢迎关注我的前端公众号:前端巅峰我专注于前端前沿技术、跨平台重应用开发、即时通讯等技术。后续版本计划: