Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

未能从内嵌自定义的 Context 中使用 Consumer 读取数据 #36

Closed
CJY0208 opened this issue Sep 2, 2019 · 9 comments
Closed
Labels
help wanted Extra attention is needed

Comments

@CJY0208
Copy link

CJY0208 commented Sep 2, 2019

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 中给出警示

@CJY0208
Copy link
Author

CJY0208 commented Sep 3, 2019

是否可以尝试增加一个组件 <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 面板混乱程度的增加...

@ShenChang618
Copy link
Member

@CJY0208 抱歉现在才回复,这个问题确实很严重,并且我认为现在的实现方式也有一些问题,React.createPortal 可能并非最佳方案,因此我想看看有什么其他方式实现

@ShenChang618 ShenChang618 added the help wanted Extra attention is needed label Sep 3, 2019
@CJY0208
Copy link
Author

CJY0208 commented Sep 3, 2019

我自己也在尝试一个简易的 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 中的层级结构会比较难看

@ShenChang618
Copy link
Member

@CJY0208 嗯嗯,现在所讨论的这个问题,实际上最简单的方式是可以直接放在 <KeepAliveProvider> 之外,这样实际上并不会出现这个问题。

<Provider value={1}>
  <KeepAliveProvider>
    ...
    <KeepAlive name="Test">
      <Consumer>
        {context => <Test contextValue={context} />}
      </Consumer>
    </KeepAlive>
    ...
  </KeepAliveProvider>
</Provider>

你说的两种方式都很有价值,但是我认为这样会有一些复杂,我也希望暴露出来的 API 能够越少越好,这样易用性会好一些。

因此我想在不改变 API 的情况下,重构下实现。

@ShenChang618
Copy link
Member

@CJY0208 可以到这个 ISSUE #36 下讨论

@CJY0208
Copy link
Author

CJY0208 commented Sep 6, 2019

目前感觉是依赖于 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 也受了影响,不过还没测

@ShenChang618
Copy link
Member

@CJY0208 👍,这个确实是预先没有考虑到得问题

@zhangmingdi
Copy link

我自己也在尝试一个简易的 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 中的层级结构会比较难看

想学习一下是如何你是如何使用桥接,稳读了一下源码,不太懂。有两个问题,希望大佬能解决一下困惑

@zhangmingdi
Copy link

我自己也在尝试一个简易的 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是自己想要的

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants