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

can React support feature like keep-alive in Vue? #12039

Closed
zengjialuo opened this issue Jan 18, 2018 · 20 comments
Closed

can React support feature like keep-alive in Vue? #12039

zengjialuo opened this issue Jan 18, 2018 · 20 comments

Comments

@zengjialuo
Copy link

i found this issue: #4770, and @sophiebits said that React never reuses an instance after it's been unmounted.

does it means that React will never support feature like keep-alive in Vue? or there is other way to maintain component's state?

@gaearon
Copy link
Collaborator

gaearon commented Jan 18, 2018

Can you describe what this feature is for someone who doesn’t know Vue, and how you imagine it working in React?

@zengjialuo
Copy link
Author

As Vue's API said:

When wrapped around a dynamic component, <keep-alive> caches the inactive component instances without destroying them. Similar to <transition>, <keep-alive> is an abstract component: it doesn’t render a DOM element itself, and doesn’t show up in the component parent chain.

When a component is toggled inside <keep-alive>, its activated and deactivated lifecycle hooks will be invoked accordingly.

Primarily used with preserve component state or avoid re-rendering.

basic usage:

<keep-alive>
  <!-- page-component matched by the route will render here -->
  <router-view></router-view>
</keep-alive>

so, with <keep-alive>, when user switch between pages, these pages will not be refreshed and their status will be preserved.

It will give users a great experience in some scenarios like news webapp.

@wingyiu
Copy link

wingyiu commented Jan 23, 2018

+1

keep the component in cache when route to next component,
instead of recreating a new one, reuse the cached component, avoid refetching the data via network to fulfill the component.

act some like a stack.

@gaearon
Copy link
Collaborator

gaearon commented Jan 23, 2018

In React this is usually solved in one of the two ways:

  • Keep data cached separately from the component. For example, you can lift state up to an ancestor that doesn't get mounted, or put it in a sideways cache like Redux. We're also working on a first-class API support for this.

  • Don't unmount the views you want to “keep alive”, just hide them with style={{display: 'none'}}.

@zengjialuo
Copy link
Author

zengjialuo commented Jan 24, 2018

  • The first way: this way can make things done, but, sometimes it's too heavy. E.g., we use sate to record whether a component is exposed or not. It's suitable to use state, and seems be strange to lift these state up or store them with Redux.
  • The second way: the problem is that just hide the element is not enough, some event listeners should be removed, usually implemented in componentWillUnmount

@gaearon Could you please explain some details about the first-class API you mentioned?

@bjrmatos
Copy link
Contributor

bjrmatos commented Jan 24, 2018

The second way: the problem is that just hide the element is not enough, some event listeners should be removed, usually implemented in componentWillUnmount

you can also intercept the change (prop/state change) that leads to visible -> display: none and do your event listeners clean up easily, and when going from display: none to visible attach the listeners again. you are effectively making the component inactive and maintaining component state by doing display: none, any other use case for keeping an instance alive?

@zengjialuo
Copy link
Author

@bjrmatos , your suggestion is practical. There are ways to work around. But i think they are not so perfect or clean. since when you write a component, you should concern both the React life-cycle and something like page life-cycle. Doesn't it?

@bjrmatos
Copy link
Contributor

your suggestion is practical. There are ways to work around. But i think they are not so perfect or clean

my suggestion is not a work around, it is the react way of doing things.. reacting to state/props changes and do something is the whole react model. i would say it is clean because it is the way how things should be done with react.

you should concern both the React life-cycle and something like page life-cycle. Doesn't it?

to me what you are describing is just a react component life-cycle concern, a component is a component no matter if it is a whole page or not and you need to use the available lifecycle hooks to do your thing, you can even create a PageContainer component that has the visible -> display: none, display:none -> visible logic and never bother with this in your app again.

don't know about the Vue approach but a keep alive feature in react sounds dirty, because there is the traditional way of doing the same with just reacting to changes in normal react lifecycles (componetWillReceiveProps)

@gaearon
Copy link
Collaborator

gaearon commented Jan 24, 2018

Could you please explain some details about the first-class API you mentioned?

We’ll post it soon at https://github.com/reactjs/rfcs. Stay tuned for PRs there.

@zengjialuo
Copy link
Author

zengjialuo commented Jan 25, 2018

@bjrmatos the keypoint is how the PageComponent inform its descendant component to remove event listeners except for all component has a visible prop?

@ninahaack
Copy link

ninahaack commented Jul 10, 2018

Hello @gaearon, I'm a big fan of React butI had to work with Vue at work. I'm working with Vue for months now and I still prefere React. But today I found about keep-alive and for the first time I found something better than React. Just because React does not have this feature. Anyway, I think it's something really increreble and maybe something that React could do too (I don't know).

Here the doc to help you to know more about it: https://br.vuejs.org/v2/guide/components-dynamic-async.html#keep-alive-with-Dynamic-Components

@gaearon
Copy link
Collaborator

gaearon commented Aug 9, 2018

Restrictions like this sound quite artificial:

Note, <keep-alive> is designed for the case where it has one direct child component that is being toggled. It does not work if you have v-for inside it. When there are multiple conditional children, as above, <keep-alive> requires that only one child is rendered at a time.

We try to avoid introducing APIs in React that work in a very limited subset of cases but then can’t work in others. This might seem “nice” at first but then you need to change the code a little bit, and hit a wall because you have to change the whole approach. Instead, we prefer a more limited set of APIs that work the same way in all circumstances. Then you can learn them once and apply the same techniques everywhere without having to tweak the code whenever you run into a limitation in some convenience shortcut.

The approaches I mentioned above (either lifting state up to a parent component or hiding/showing with style={{display: visible ? 'block' : 'none'}}) don’t suffer from these limitations and work regardless of how many children you have. The lifecycles are also handled in a predictable way: either you get a full unmount and then re-mount of children (with state preserved in the parent), or the children stay mounted all the way through (and thus don’t need special lifecycle calls). This is consistent with how React works in general and doesn’t require special behavior.

so, with <keep-alive>, when user switch between pages, these pages will not be refreshed and their status will be preserved.

This sounds like a recipe for memory leaks to me. If you keep all the components instantiated forever, your app will keep eating memory as you navigate between the pages. Just caching data alone would be okay (and that’s exactly what React lets you do), but keeping component instances and state sounds like it would create problems as the app becomes more complex (but at that point it’s too late to fix because the app heavily relies on this pattern). I don’t think we’d want to introduce APIs that encourage irresponsible memory usage into React.

That said we definitely want to make the caching use case easier (as a few people rightly noted, brining in something like Redux just to cache network responses is overkill). We’re working on something for this — stay tuned for announcements.

Thanks everyone for your links and the discussion! Hope my answer wasn’t too frustrating. I think the “React way” might feel annoying if you’re used to having a helper like <keep-alive> but I also think that having used React a bit more you’ll appreciate the explicitness and control that React gives you in this case.

@gaearon gaearon closed this as completed Aug 9, 2018
@ninahaack
Copy link

Fair enough.Thank you for your explanation @gaearon. I understand... everything it's trade offs after all.

@ssjtbcf
Copy link

ssjtbcf commented Mar 7, 2019

good

@ShenChang618
Copy link

@zengjialuo @gaearon @bjrmatos @ninahaack
I wrote a component using the React.createPortal API (react-keep-alive), it implements similar functionality to <keep-alive> in Vue.

Now, I have some doubts about the lifecycle of react-keep-alive. In the first version, I added two lifecycles componentDidActivate and componentWillUnactivate, which of course was affected by Vue. In the current version I deleted these two lifecycles and replaced the new lifecycle of the old version with componentDidMount and componentWillUnmount.

This can be confusing for users, such as [Lifecycle and events] (https://codesandbox.io/s/q1xprn1qq) and [Control cache] (https://codesandbox.io/s/llp50vxnq7), via `bindLifecycle The life cycle of the component after the high-order function package will be:

Mounting: componentWillMount -> componentDidMount
Unmounting: componentWillUnmount

Activating: componentWillUpdate -> componentDidMount(This is where the user is confused. I am tampering with the life cycle when I activate it.)
Unactivating: componentWillUnmount

I used the life cycle of the old version of React, and it would be confusing if the user used it like this; but the new life cycle may not be a problem, I am not sure.

@AielloChan
Copy link

Try https://github.com/AielloChan/KeepAliveRoute

@zhangyu1818
Copy link

i hope react can support keep-alive

@CJY0208
Copy link

CJY0208 commented Sep 29, 2019

I have my implementation react-activation and here is the Online Demo

Because React will unload components that are in the intrinsic component hierarchy, we can extract the components in <KeepAlive>, that is, its children prop, and render them into a component that will not be unloaded, drag the rendered content back to the <KeepAlive> using the DOM operation

The principle is easy to say but it's not a good idea...The implementation destroys the original rendering level and brings some bad effects.

Simplest Implementation as follows

import React, { Component, createContext } from 'react'

const { Provider, Consumer } = createContext()
const withScope = WrappedCompoennt => props => (
  <Consumer>{keep => <WrappedCompoennt {...props} keep={keep} />}</Consumer>
)

export class AliveScope extends Component {
  nodes = {}
  state = {}

  keep = (id, children) =>
    new Promise(resolve =>
      this.setState(
        {
          [id]: { id, children }
        },
        () => resolve(this.nodes[id])
      )
    )

  render() {
    return (
      <Provider value={this.keep}>
        {this.props.children}
        {Object.values(this.state).map(({ id, children }) => (
          <div
            key={id}
            ref={node => {
              this.nodes[id] = node
            }}
          >
            {children} {/* render them into a component that will not be unloaded */}
          </div>
        ))}
      </Provider>
    )
  }
}

@withScope
class KeepAlive extends Component {
  constructor(props) {
    super(props)
    this.init(props)
  }

  init = async ({ id, children, keep }) => {
    const realContent = await keep(id, children) // extract the children in <KeepAlive>
    this.placeholder.appendChild(realContent) // drag the rendered content back using the DOM operation
  }

  render() {
    return (
      <div
        ref={node => {
          this.placeholder = node
        }}
      />
    )
  }
}

export default KeepAlive

@ChenZongHeng
Copy link

I love Vue than React, Vue is so great!!

@MachoManCk
Copy link

@bjrmatos的关键是PageComponent如何通知其后代组件除去所有组件都有可见道具的事件侦听器?

Sometimes it's not a good thing that brother's behavior Vue encapsulates perfectly. The best way to build it is to fit your own framework, isn't it?

@facebook facebook locked as resolved and limited conversation to collaborators Feb 25, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests