一些关于react的keep-alive功能相关知识在这里(下)
一些关于react的keep-alive功能相关知识在这里(下)
???? 本篇承接上篇内部, 所以是从第九点开始
九、保留页面scroll
???? 比如页面上的table
里有100
条数据, 我们想看第100条数据, 那就要滚动不少距离, 不少场景这种滚动距离也是有必要保留的。
???? 这里使用的方法其实比较传统啦, 首先在KeepAliveProvider
下发一个处理滚动的方法:
const handleScroll = useCallback(
(cacheId, event) => {
if (catheStates?.[cacheId]) {
const target = event.target
const scrolls = catheStates[cacheId].scrolls
scrolls[target] = target.scrollTop
}
},
[catheStates]
)
???? 在
Keeper
组件里面接收并执行:const { dispatch, mount, handleScroll } = useContext(CacheContext)useEffect(() => {
const onScroll = handleScroll.bind(null, cacheId)
(divRef?.current as any)?.addEventListener?.('scroll', onScroll, true)
return (divRef?.current as any)?.addEventListener?.('scroll', onScroll, true)
}, [handleScroll])
???? 在Keeper里面将滚动属性赋予元素:
useEffect(() => {
const catheState = catheStates[cacheId]
if (catheState && catheState.doms) {
const doms = catheState.doms
doms.forEach((dom: any) => {
(divRef?.current as any)?.appendChild?.(dom)
})// 新增
doms.forEach((dom: any) => {
if (catheState.scrolls[dom]) {
dom.scrollTop = catheState.scrolls[dom]
}
})
} else {
mount({
cacheId,
reactElement: props.children
})
}
}, [catheStates])
???? 这里如果不主动增加赋予
scroll
的方法的话, 滚动距离是不会被保存的, 因为Keeper
每次都是新的。十、KeepAliveProvider内部 Keeper子组件内部的CacheContext ????我们是把组件渲染在
KeepAliveProvider
里面, 那么如果某个Provider
是在 KeepAliveProvider
内部定义的, 则KeepAliveProvider
级别的组件是无法使用 Consumer
拿到这个值的。????这里就引出一个问题, 如何将
KeepAliveProvider
中的组件的上下文, 修改为Keeper
组件的上下文。????这里演示一下最直接的方式, 让用户传入
Provider
与其value
值。
???? 我们拿到这两个值后直接在
Keeper
中修改reactElement
的结构:mount({
cacheId,
reactElement: context ?
{props.children} :
props.children
})
????当检测到
context
有值则直接在 props.children
外面套一层, 当然这里存在一个多层Provider
嵌套的问题没有去解决, 因为逐渐复杂起来它的实用性已经在下降了, 接下来还有新的bug
来袭。十一、需要传值的组件 ???? 大家有没有发现上述组件所有逻辑, 都是直接写在
Keeper
标签里面的, 并没有任何的传值, 但是比较常见的一种场景是下面这样的:function Root (){
const [n, setN] = useState(1)
return
(
<>
>
)
}
????这个
n
是Keeper
外层传递给Home
组件的, 这种写法下会导致n
虽然变化了但是Home
里面不会响应。????这个
bug
我是这样发现的, 当我把这个插件用在我们团队的项目里的一个表格为主的页面时 , table
一直显示是空的, 并且输入框也无法输入值, 经过测试发现其实值是有变化的, 只是没有展示在组件的dom
上。????尝试了好久后试了下
react-activation
很遗憾它也有相同的问题, 那其实就说明这个bug
很可能无法解决或者就是这个插件本身的架构存在的问题。十二、为何这么奇怪的bug场景 ????当时这个
bug
折磨了我一天半的时间, 最后定位到外界的传参已经不能算是这个组件本身的参数了, 我们组件的实际渲染位置是 KeepAliveProvider
的第一层, 而Keeper
的外层还在KeepAliveProvider
的更内层, 这就导致这些值的变化其实是没有能够影响到组件。????可以理解为这些值的变化, 比如
n
的变化就如同window.n
的改变一样, react
组件是不会去响应这个变化的。????那其实我们要做的就是让外层传入的值的变化, 可以带动组件的样式变化 (逐渐入坑!)。
十三、将props单独拿出来 ???? 我借鉴了网上另一种
keep-alive
组件的写法, 把Keeper
组件改为一个keeper
的方法, 这个方法返回一个组件看, 这样就可以接收一个props
了, 也就把变量圈定在props
这个范围:const Home = keeper(HomePage, { cacheId: 'home' })function Root(){
const [n, setN] = useState(1)
return (
<>
// 此处可以传值了
>
)
}
???? 这样做的目的是让开发者把能够影响组件状态的参数一口气传进来, 比如之前一个
Keeper
里面可以有多个组件, 这种情况就不好控制哪些参数变化会导致哪些组件更新, 但以组件的方式可以明显得知组件接收到的props
里面的值的改变会导致组件更新。???? 我想到的方案是, 在
KeepAliveProvider
里面新建propsObj
, 用来专门储存每个缓存组件的props
, 之所以如此设计将其单独拿出来, 是要把传参与组件的逻辑拆分开, 不少逻辑会监控catheStates
的变化而执行, 但是props
的变化没有必要触发这些。const [propsObj, setPropsObj] = useState();
return (
{props.children}//.... 略
????
KeepAliveProvider
里面的渲染需要变一个形式, reactElement
变成组件了, 别忘了名字要变成大写的。// 旧的
// {reactElement}// 新的
{propsObj &&
}
???? 改装一下
Keeper
文件, 首先要把文件名改为 keeper
, 导出的方法要进行一下更改。 export default function (
RealComponent: React.FunctionComponent, { cacheId = '' }) {return function Keeper(props: any) {
// ... 略
????
Keeper
内mount
方法的使用也稍作调整:mount({
cacheId,
ReactElement: RealComponent
})
???? 关键的来了, 我们要在
Keeper
里面监测props
的变化, 来更新propsObj
:const { propsObj, setPropsObj } = useContext(CacheContext)useEffect(() => {
setPropsObj({
...propsObj,
[cacheId]: props
})
}, [props])
十四、缓存失效的bug ???? 上述我们已经把插件改装了形式, 并且发现可以让如下场景正常渲染, Home组件的props是外界传入的:
const Home = keeper(HomePage, { cacheId: 'home' })const RootComponent: React.FC = () => {
return (
} />
)
}
function Mid() {
const [n, setN] = useState(1)
return ()
}function HomePage(props: { n: number }) {
return home {props.n}
}
????但是此时如果切换页面后再返回
home
页面, home
页面的缓存是会失效的。????其实是因为我们实时监控
props
的变化, 下次重新渲染时会导致props
变化, 然后值就会被初始化了, 导致组件也恢复到了早期的配置, 可是.... 这不就是缓存失败了吗?????每次组件
props
被重置就会导致组件的相关数据被重置, 尝试把home
组件做如下更改:function HomePage(props: { n: number }) {
const [x, setX] = useState(1)
return (
home {props.n}
home: x {x})
}
???? 上述写法会导致每次激活
home
组件, 只能保留x
的值, n
的值会与传入的相同。???? 这种变化可能会导致
bug
, 假设只有 n > 2
才能让 x > 3
, 此时我们通过点击事件让 n = 5
, x = 4
了, 此时切换到其他页面再回来, 就变成了n = 1, x=4
, 违背了我们的初始限制条件, 以此类推在真实复杂的开发环境中此现象会导致各种奇怪的问题。十五、认知的代价 ????上面的场景可以通过开发人员自己来控制, 理想情况是
keep-alive
插件只用来处理不需要外界传参, 以及不会被外界参数的变化影响的组件, 但这就开始麻烦了。????这类问题导致开发者在插件身上要花的学习成本提高, 使用成本提高, 并且如果某个组件本来不需要传参, 我们用
keep-alive
包裹起来了, 后续又需要传参了, 改变的成本想想都麻烦。????网上现有(2022年04月10日17:16:22)组件的官网基本是没有认真的对用户讲述相关的问题, 往往都是以介绍"使用方法"与阐述自己的优势为主, 这就导致用户被莫名其妙的
bug
折磨。????传递
Provider
的方法也有问题, 需要传递可能不是本页代码的Provider
, 难受的了啊。????想要解决
keep-alive
相关问题的思路可以换一下, 最好是在react
源码里支持一波, 比如可以指定某些组件不被销毁, 其实我们可以关注一下react18
的后续版本, 现在这个时间段react18
发布了正式版。十六、如何升级到react18 方式一: create-react-app 创建新项目 ???? 现阶段直接使用下面的命令, 就可创建react18项目:
npx create-react-app my_react
文章图片
???? 下面这种使用
--template
指定模板的还不行, 因为模板代码还没更新:npx create-react-app my_react --template typescript
???? 这里可以查看所有react项目的模板 create-react-app项目可指定的模板。
方式二: 老项目改装 ???? 首先直接把依赖里面的
react
与 react-dom
的版本号改成 "^18.0.0"
即可。两种方式都需要修改 index.js ???? 启动项目会有报错信息:
文章图片
???? 旧版的
index.js
文章图片
???? 新版的
index.js
文章图片
???? 其他的没有太多更改了。
十七、react18 Offscreen 组件的用法 ????
Offscreen
允许 React
通过隐藏组件而不是卸载组件来保持这样的状态, React
将调用与卸载时相同的生命周期钩子, 但它也会保留 React
组件和 DOM
元素的状态。????
React Activatio
n 中也推荐大家关注这个属性:文章图片
????
Offscreen
是什么的官方说法可以看这篇文章里的翻译: React v18.0新特性官方文档[中英文对照文章图片
????
Offscreen
的测试用例:文章图片
????遗憾的是
Offscreen
组件并没有在当前版本推出, 其还处于不稳定阶段, 但我们可以通过 react18
里面的测试用例来预览一下其用法:文章图片
????通过上述写法还无法看出
Offscreen
到底如何使用, 只知道它可能是以组件的形式出现, 并且需要传入一个mode属性, 更多用法期待官方尽快推出吧。end
【一些关于react的keep-alive功能相关知识在这里(下)】???? 让我们一起期待
react18
来解决keep-alive
这个问题吧, 这次就是这样, 希望与你一起进步。推荐阅读
- 一些关于react的keep-alive功能相关知识在这里(上)
- react|ReactHooks+Antd Checkbox Group多选框组单独禁用其中选项
- React|React before mutation阶段
- 2022考研日志
- 收录笔记|GitHub上一些有意思地址/有趣的网站(收藏链接)
- 服务器框架设计模式|多线程Reactor分析,从性能,客户接入量方向
- 自我提升(项目中React及JavaScript遇到问题记录)
- 总结|关于c++中的临时变量
- vue|Vue2 迁 Vue3 踩过的一些坑(持续更新)
- typescript使用入门及react+ts实战