未能从内嵌自定义的 Context 中使用 Consumer 读取数据
CJY0208 opened this issue · 9 comments
Hello
由于 react-keep-alive
原理大致为将 KeepAlive children
提取出来渲染到 <KeepAliveProvider />
节点下,而非 <KeepAlive />
之下
这将导致 KeepAlive
中的组件无法被 React
认为是在其所处的上下文之中
样例大致如下:
https://codesandbox.io/s/basic-currently-rwo9y
import React, { createContext, useState } from "react";
import ReactDOM from "react-dom";
import { Provider as KeepAliveProvider, KeepAlive } from "react-keep-alive";
const { Provider, Consumer } = createContext();
function Test({ contextValue = null }) {
return (
<div>
<p>contextValue: {contextValue}</p>
</div>
);
}
function App() {
const [show, setShow] = useState(true);
const toggle = () => setShow(show => !show);
return (
<KeepAliveProvider>
<div>
<Provider value={1}>
{show && (
<KeepAlive name="Test">
<Consumer>
{context => <Test contextValue={context} />}
</Consumer>
</KeepAlive>
)}
<button onClick={toggle}>toggle</button>
</Provider>
</div>
</KeepAliveProvider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
样例中的 <Test />
无法从 <Consumer />
中获得 contextValue
属性
从实现原理上来说目前似乎无法避免,是不得已而为之
想问问在这方面有没有考虑其他可能的实现方式呢?
目前会对直觉上的 Context
造成破坏,有不小的危害性,如果目前无法修复的话,个人认为有必要在 README 中给出警示
是否可以尝试增加一个组件 <KeepAliveStation />
,类似于 ”驿站“ 的概念
每个 KeepAlive 可选择不同的缓存节点,Provider 作为默认驿站备用,也可以用在驿站被销毁时的 fallback 缓存节点
如上述例子中
...
import { Provider as KeepAliveProvider, KeepAlive, KeepAliveStation } from "react-keep-alive";
...
function App() {
...
return (
<KeepAliveProvider>
...
<Provider value={1}>
<KeepAliveStation name="station-1" />
...
<KeepAlive name="Test" station="station-1">
<Consumer>
{context => <Test contextValue={context} />}
</Consumer>
</KeepAlive>
...
</Provider>
...
</KeepAliveProvider>
);
}
...
不过也许无法实现 fallback 功能,驿站销毁的话,上边挂载的缓存节点也许会强迫走 unmount 周期
这个方案也会造成 react-dev-tools 面板混乱程度的增加...
@CJY0208 抱歉现在才回复,这个问题确实很严重,并且我认为现在的实现方式也有一些问题,React.createPortal
可能并非最佳方案,因此我想看看有什么其他方式实现
我自己也在尝试一个简易的 keep-alive 实现,从 React 渲染流程来看,这个问题很难绕过...目前来说似乎是实现 keep-alive 的唯一途径
在实现过程中我做了一个可行的尝试:
增加 fixContext
函数来接收要修复的上下文,在 KeepAlive 中先使用待修复上下文的 Consumer 获取到可能存在的 Provider 数据,传递到对应的 KeepAliveProvider 中,再重新使用 Provider 重建上下文,就可以修复这个问题
// 书写时
...
<KeepAliveProvider>
...
<Provider value={1}>
...
<KeepAlive name="Test">
<Consumer>
{context => <Test contextValue={context} />}
</Consumer>
</KeepAlive>
...
</Provider>
...
</KeepAliveProvider>
...
// 实际渲染时
...
<KeepAliveProvider>
...
<Provider value={1}>
...
<Consumer>
{context => (
<KeepAlive name="Test" contextValue={context}>
{/* render to Keeper named "Test" */}
</KeepAlive>
)}
</Consumer>
...
</Provider>
...
<Keeper name="Test">
{contextValue => (
<Provider value={contextValue}>
<Consumer>
{context => <Test contextValue={context} />}
</Consumer>
</Provider>
)}
</Keeper>
</KeepAliveProvider>
...
大概是这样子,预先声明可能需要修复的 Context 组,KeepAlive 提前做 HOC 封装,套上一层 Consumer 尝试获取可能存在的上下文,再将 KeepAlive 获取到的上下文传入其对应的 Keeper 中,重建上下文关系
后续可以尝试包装 createContext
函数,让用户使用从 react-keep-alive
导出的 createContext,创建出可自动修复的 context,这样比较贴近无感知的使用体验
但如果需要修复的上下文过多,dev-tools 中的层级结构会比较难看
@CJY0208 嗯嗯,现在所讨论的这个问题,实际上最简单的方式是可以直接放在 <KeepAliveProvider>
之外,这样实际上并不会出现这个问题。
<Provider value={1}>
<KeepAliveProvider>
...
<KeepAlive name="Test">
<Consumer>
{context => <Test contextValue={context} />}
</Consumer>
</KeepAlive>
...
</KeepAliveProvider>
</Provider>
你说的两种方式都很有价值,但是我认为这样会有一些复杂,我也希望暴露出来的 API 能够越少越好,这样易用性会好一些。
因此我想在不改变 API 的情况下,重构下实现。
目前感觉是依赖于 React 层级结构的行为,可能都产生了破坏性,例如下述有两个影响
1、事件冒泡失效
2、KeepAlive 内部依赖于外部数据的 children 更新失效
https://codesandbox.io/s/basic-currently-5gfz9
function App() {
const [show, setShow] = useState(true);
const toggle = () => setShow(show => !show);
return (
<KeepAliveProvider>
<div onClick={() => {
console.log('捕获到冒泡事件')
}}>
<KeepAlive name="Test">{show && <div>random</div>}</KeepAlive>
{show && <div>random</div>}
<button onClick={toggle}>toggle</button>
</div>
</KeepAliveProvider>
)
}
猜测 Error Boundaries 也受了影响,不过还没测
@CJY0208 👍,这个确实是预先没有考虑到得问题
我自己也在尝试一个简易的 keep-alive 实现,从 React 渲染流程来看,这个问题很难绕过...目前来说似乎是实现 keep-alive 的唯一途径
在实现过程中我做了一个可行的尝试:
增加
fixContext
函数来接收要修复的上下文,在 KeepAlive 中先使用待修复上下文的 Consumer 获取到可能存在的 Provider 数据,传递到对应的 KeepAliveProvider 中,再重新使用 Provider 重建上下文,就可以修复这个问题// 书写时 ... <KeepAliveProvider> ... <Provider value={1}> ... <KeepAlive name="Test"> <Consumer> {context => <Test contextValue={context} />} </Consumer> </KeepAlive> ... </Provider> ... </KeepAliveProvider> ... // 实际渲染时 ... <KeepAliveProvider> ... <Provider value={1}> ... <Consumer> {context => ( <KeepAlive name="Test" contextValue={context}> {/* render to Keeper named "Test" */} </KeepAlive> )} </Consumer> ... </Provider> ... <Keeper name="Test"> {contextValue => ( <Provider value={contextValue}> <Consumer> {context => <Test contextValue={context} />} </Consumer> </Provider> )} </Keeper> </KeepAliveProvider> ...大概是这样子,预先声明可能需要修复的 Context 组,KeepAlive 提前做 HOC 封装,套上一层 Consumer 尝试获取可能存在的上下文,再将 KeepAlive 获取到的上下文传入其对应的 Keeper 中,重建上下文关系
后续可以尝试包装
createContext
函数,让用户使用从react-keep-alive
导出的 createContext,创建出可自动修复的 context,这样比较贴近无感知的使用体验但如果需要修复的上下文过多,dev-tools 中的层级结构会比较难看
想学习一下是如何你是如何使用桥接,稳读了一下源码,不太懂。有两个问题,希望大佬能解决一下困惑
我自己也在尝试一个简易的 keep-alive 实现,从 React 渲染流程来看,这个问题很难绕过...目前来说似乎是实现 keep-alive 的唯一途径
在实现过程中我做了一个可行的尝试:
增加fixContext
函数来接收要修复的上下文,在 KeepAlive 中先使用待修复上下文的 Consumer 获取到可能存在的 Provider 数据,传递到对应的 KeepAliveProvider 中,再重新使用 Provider 重建上下文,就可以修复这个问题// 书写时 ... <KeepAliveProvider> ... <Provider value={1}> ... <KeepAlive name="Test"> <Consumer> {context => <Test contextValue={context} />} </Consumer> </KeepAlive> ... </Provider> ... </KeepAliveProvider> ... // 实际渲染时 ... <KeepAliveProvider> ... <Provider value={1}> ... <Consumer> {context => ( <KeepAlive name="Test" contextValue={context}> {/* render to Keeper named "Test" */} </KeepAlive> )} </Consumer> ... </Provider> ... <Keeper name="Test"> {contextValue => ( <Provider value={contextValue}> <Consumer> {context => <Test contextValue={context} />} </Consumer> </Provider> )} </Keeper> </KeepAliveProvider> ...大概是这样子,预先声明可能需要修复的 Context 组,KeepAlive 提前做 HOC 封装,套上一层 Consumer 尝试获取可能存在的上下文,再将 KeepAlive 获取到的上下文传入其对应的 Keeper 中,重建上下文关系
后续可以尝试包装createContext
函数,让用户使用从react-keep-alive
导出的 createContext,创建出可自动修复的 context,这样比较贴近无感知的使用体验
但如果需要修复的上下文过多,dev-tools 中的层级结构会比较难看想学习一下是如何你是如何使用桥接,稳读了一下源码,不太懂。有两个问题,希望大佬能解决一下困惑
1.如何让Keeper获取不被包裹的Provider,Provider有多个的时候,怎么知道哪个Provider是自己想要的