React保活功能的一些知识在这里(下)页面滚动比如页面上的表格有100条数据,而如果我们想看到第100条数据,我们需要滚动很多次。在很多场景下,这个滚动距离也是需要预留的。这里使用的方法其实是很传统的。首先,在KeepAliveProvider中发出一个滚动方法:target]=target.scrollTop}},[catheStates])在Keeper组件中接收并执行:const{dispatch,mount,handleScroll}=useContext(CacheContext)useEffect(()=>{constonScroll=handleScroll.bind(null,cacheId)(divRef?.currentasany)?.addEventListener?.('scroll',onScroll,true)return(divRef?.currentasany)?.addEventListener?.('scroll',onScroll,true)},[handleScroll])将滚动属性分配给Keeper中的元素:useEffect(()=>{constcatheState=catheStates[cacheId]if(catheState&&catheState.doms){constdoms=catheState.domsdoms.forEach((dom:any)=>{(divRef?.currentasany)?.appendChild?.(dom)})//添加doms.forEach((dom:any)=>{if(catheState.scrolls[dom]){dom.scrollTop=catheState.scrolls[dom]}})}else{mount({cacheId,reactElement:props.children})}},[catheStates])这里如果你不主动增加grant在scroll方法的情况下,不会保存滚动距离,因为Keeper每次都是新的。10、KeepAliveProvider内部Keeper子组件内部的CacheContext????我们在KeepAliveProvider中渲染组件,所以如果一个Provider是在KeepAliveProvider内部定义的,那么KeepAliveProvider级别的组件是无法使用Consumer来获取这个值的。这里引出一个问题,如何将KeepAliveProvider中组件的上下文修改为Keeper组件的上下文。这里最直接的方式就是让用户传入Provider及其值。我们拿到这两个值后,直接修改Keeper中reactElement的结构:mount({cacheId,reactElement:context?{props.children}:props.children})当检测到context有值时,会直接包裹一个props.children外面的层,当然还有一个多层Provider嵌套的问题没有解决,因为越复杂实用性越差,接下来又会出现新的bug。11、需要传值的组件有没有注意到,上面的组件的所有逻辑都直接写在了Keeper标签中,没有任何传值,但是一个常见的场景是这样的:functionRoot(){const[n,setN]=useState(1)return(<>setN(n+1)}>n+1>)}这个n是从Keeper外层传给Home组件的。这种写法会导致n虽然变了,但是Home没有响应。这就是我发现这个错误的方式。我在我们团队项目的一个基于表格的页面上使用这个插件时,表格一直显示为空,输入框无法输入值。经过测试,发现实际值是有变化的,只是没有显示在组件的dom上。试了半天,试了一下react-activation。不幸的是,它也有同样的问题,这实际上意味着这个bug可能没有被解决或者是插件本身架构的问题。12.为什么这么奇葩的bug场景?这个bug当时折磨了我一天半,最后传给外界的参数已经不能算是这个组件本身的参数了。我们组件的实际渲染位置是KeepAliveProvider的第一层。Keeper的外层还在KeepAliveProvider的内层,这就导致了这些值的变化不会影响到组件。可以理解为这些值的变化,比如n的变化就像window.n的变化,react组件不会响应这种变化。其实我们要做的就是改变从外层传入的值,可以带动组件的样式变化(逐渐入坑!)。十三。把props单独拿出来我借用了网上另一种keep-alive组件的写法,把Keeper组件改成了keeper方法。这个方法返回一个组件来看,这样它就可以接收一个props,这个props也是在props的范围内定界的变量:constHome=keeper(HomePage,{cacheId:'home'})functionRoot(){const[n,setN]=useState(1)return(<>setN(n+1)}>n+1//值可以在这里传递>)}这样做的目的是让开发者将影响组件状态的参数一次性传入。比如之前一个Keeper中可以有多个组件。在这种情况下,不容易控制哪些参数变化会导致哪些组件更新,但是以组件的方式,你可以清楚地知道组件接收到的props。值的更改将导致组件更新。我想到的解决办法是在KeepAliveProvider中新建一个propsObj,用来存放各个缓存组件的props。之所以设计成单独取出来,是为了将参与组件传递的逻辑分开,很多逻辑会监听catheStates的变化,但props的变化并不一定会触发这些。const[propsObj,setPropsObj]=useState();return({props.children}//....略KeepAliveProvider中的渲染需要改一下,reactElement已经成为组件了,别忘了改名称大写。//Old//{reactElement}//New{propsObj&&}修改Keeper文件,先把文件名改成keeper,导出方法即可变化。exportdefaultfunction(RealComponent:React.FunctionComponent,{cacheId=''}){returnfunctionKeeper(props:any){//...略有Keeper中mount方法的使用也略有调整:mount({cacheId,ReactElement:RealComponent})关键来了,我们需要监听Keeper中的props变化来更新propsObj:const{propsObj,setPropsObj}=useContext(CacheContext)useEffect(()=>{setPropsObj({...propsObj,[cacheId]:props})},[props])14.缓存失效bug我们修改了上面的插件,发现下面的场景可以正常渲染。Home组件的props是从外界导入的:constHome=keeper(HomePage,{cacheId:'home'})constRootComponent:React.FC=()=>{return(}/>)}functionMid(){const[n,setN]=useState(1)return(setN(n+1)}>n+1
)}functionHomePage(props:{n:number}){returnhome{props.n}
}但是如果此时切换页面返回首页,首页的缓存会失效其实是因为我们实时监听props的变化,下次重新渲染会引起props的变化,然后初始化values,导致组件恢复到之前的配置,但是....这不就是缓存失败了吗?每次重置组件道具时,都会重置组件的相关数据。尝试对主页组件进行以下更改:functionHomePage(props:{n:number}){const[x,setX]=useState(1)return(setX(x+1)}>x+1home{props.n}
home:x{x}
)}上面的写法将因为每次home组件被激活时,只能保留x的值,n的值会和传入的一样。此更改可能会导致错误。假设只有n>2才能让x>3,此时我们通过点击事件让n=5,x=4。这时候,切换到其他页面回来,就变成了n=1,x=4,违反了我们最初的约束,等等。在真实复杂的开发环境中,这种现象会引起各种奇怪的问题。15.认知成本上面的场景可以由开发者自己控制。理想情况下,keep-alive插件只用于处理不需要外部参数且不会受到外部参数变化影响的组件,但这是麻烦开始了。这类问题导致开发者在插件上的学习成本和使用成本增加,而如果一个组件不需要传参,我们用keep-alive包裹起来,后面需要传参,成本的变化想想都麻烦。现有的(April10,202217:16:22)组件官网基本上没有认真向用户描述相关问题,往往侧重于介绍“如何使用”和说明自身的优势。导致用户被莫名其妙的bug折磨。传递Provider的方法也有问题。传递可能不是本页面代码的Provider,让人不爽。如果想解决keep-alive相关的问题,可以换个思路。最好在react源码中支持一波。例如,您可以指定某些组件不会被销毁。其实大家可以关注一下react18的后续版本。React18现在在这个时间段发布了正式版。16.如何升级到react18方法一:create-react-app新建工程这个阶段可以直接使用如下命令创建react18工程:npxcreate-react-appmy_react下面的方法使用--template指定模板否,因为模板代码没有更新:npxcreate-react-appmy_react--templatetypescript这里可以查看所有react项目的模板create-react-app项目可以指定的模板.方法二:修改老项目首先,直接将dependencies中react和react-dom的版本号改成“^18.0.0”。两种方式都需要修改index.js启动项目时会出现错误提示:老版本index.js新版本index.js其他变化不多。十七、react18Offscreen组件使用Offscreen允许React通过隐藏组件而不是卸载组件来保持这样的状态,React会调用与卸载时相同的生命周期钩子,但它也会保留React组件和DOM元素的状态。ReactActivation也建议大家注意这个属性:Offscreen是官方的说法,可以看这篇文章的翻译:Reactv18.0NewFeaturesOfficialDocument当前版本已经上线,还处于不稳定阶段,但是我们可以通过react18中的测试用例来预览它的用法:通过上面的写法,还是看不出Offscreen是如何使用的。我们只知道它可能以组件的形式出现,需要传入一个mode属性,更多的用法期待尽快正式上线。end??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????