{"id":13459112,"url":"https://github.com/CJY0208/react-activation","last_synced_at":"2025-03-24T16:31:21.107Z","repository":{"id":38429393,"uuid":"206294526","full_name":"CJY0208/react-activation","owner":"CJY0208","description":"Hack \u003cKeepAlive /\u003e for React","archived":false,"fork":false,"pushed_at":"2024-11-21T08:07:21.000Z","size":1920,"stargazers_count":1900,"open_issues_count":74,"forks_count":146,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-03-18T14:47:01.643Z","etag":null,"topics":["cache","cache-control","keep-alive","keepalive","offscreen","react"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/react-activation","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CJY0208.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-09-04T10:35:13.000Z","updated_at":"2025-03-17T06:00:02.000Z","dependencies_parsed_at":"2024-12-09T04:04:08.584Z","dependency_job_id":null,"html_url":"https://github.com/CJY0208/react-activation","commit_stats":{"total_commits":177,"total_committers":9,"mean_commits":"19.666666666666668","dds":0.05649717514124297,"last_synced_commit":"ec91c985d63958a1736c94fa2b92dbd0a1fcc14d"},"previous_names":[],"tags_count":57,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CJY0208%2Freact-activation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CJY0208%2Freact-activation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CJY0208%2Freact-activation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CJY0208%2Freact-activation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CJY0208","download_url":"https://codeload.github.com/CJY0208/react-activation/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244272459,"owners_count":20426737,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","cache-control","keep-alive","keepalive","offscreen","react"],"created_at":"2024-07-31T09:01:04.643Z","updated_at":"2025-03-24T16:31:21.100Z","avatar_url":"https://github.com/CJY0208.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","[React](https://github.com/facebook/react/)","目录"],"sub_categories":[],"readme":"# ⚠️ NOTICE\n\n- DO NOT use `\u003cReact.StrictMode /\u003e`\n- (React v18+) DO NOT use `ReactDOMClient.createRoot`, use `ReactDOM.render` instead, https://github.com/CJY0208/react-activation/issues/225#issuecomment-1311136388\n\n# React Activation\n\n[![size](https://img.shields.io/bundlephobia/minzip/react-activation@latest.svg)](https://bundlephobia.com/result?p=react-activation@latest)\n[![dm](https://img.shields.io/npm/dm/react-activation.svg)](https://github.com/CJY0208/react-activation)\n![](https://komarev.com/ghpvc/?username=cjy0208-react-activation\u0026label=VIEWS)\n\nEnglish | [中文说明](./README_CN.md)\n\n**HACK Implementation** of the `\u003ckeep-alive /\u003e` function in `Vue` For `React`\n\nPlease also pay attention to official support [`\u003cOffscreen /\u003e`](https://github.com/reactwg/react-18/discussions/19) in `React 18.x`\n\n---\n\nMore stable `\u003cKeepAlive /\u003e` function with `babel` pre-compilation\n\n[Online Demo](https://codesandbox.io/s/affectionate-beaver-solkt)\n\n\u003cimg src=\"./docs/basicReactActivation.gif\"\u003e\n\n---\n\n## More examples\n\n- [Closable tabs with `react-router`](https://codesandbox.io/s/keguanbideyifangwenluyou-tab-shilikeanluyoucanshufenduofenhuancun-ewycx)\n- [Closable tabs with `umi`](https://codesandbox.io/s/umi-keep-alive-tabs-demo-knfxy)\n- [Using Animation with `react-router`](https://codesandbox.io/s/luyouzhuanchangdonghuashili-jdhq1)\n\n---\n\n## Compatibility\n\n- React v16 / v17 / v18\n\n- Preact v10+\n\n- Compatible with SSR\n\n---\n\n## Install\n\n```bash\nyarn add react-activation\n# or\nnpm install react-activation\n```\n\n---\n\n## Usage\n\n#### 1. (Optional, Recommended) Add `react-activation/babel` plugins in `.babelrc`\n\n[Why is it needed?](https://github.com/CJY0208/react-activation/issues/18#issuecomment-564360695)\n\nThe plugin adds a `_nk` attribute to each JSX element during compilation to help the `react-activation` runtime **generate an unique identifier by render location** base on [`react-node-key`](https://github.com/CJY0208/react-node-key).\n\n```javascript\n{\n  \"plugins\": [\n    \"react-activation/babel\"\n  ]\n}\n```\n\n**(0.11.0+)** If you don't want to use Babel, it is recommended that each `\u003cKeepAlive\u003e` declare a globally unique and invariant `cacheKey` attribute to ensure the stability of the cache, as follows:\n\n```jsx\n\u003cKeepAlive cacheKey=\"UNIQUE_ID\" /\u003e\n```\n\n#### 2. Wrap the components that need to keep states with `\u003cKeepAlive\u003e`\n\nLike the `\u003cCounter\u003e` component in the example\n\n```javascript\n// App.js\n\nimport React, { useState } from 'react'\nimport KeepAlive from 'react-activation'\n\nfunction Counter() {\n  const [count, setCount] = useState(0)\n\n  return (\n    \u003cdiv\u003e\n      \u003cp\u003ecount: {count}\u003c/p\u003e\n      \u003cbutton onClick={() =\u003e setCount(count =\u003e count + 1)}\u003eAdd\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\nfunction App() {\n  const [show, setShow] = useState(true)\n\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={() =\u003e setShow(show =\u003e !show)}\u003eToggle\u003c/button\u003e\n      {show \u0026\u0026 (\n        \u003cKeepAlive\u003e\n          \u003cCounter /\u003e\n        \u003c/KeepAlive\u003e\n      )}\n    \u003c/div\u003e\n  )\n}\n\nexport default App\n```\n\n#### 3. Place the `\u003cAliveScope\u003e` outer layer at a location that will not be unmounted, usually at the application entrance\n\nNote: When used with `react-router` or `react-redux`, you need to place `\u003cAliveScope\u003e` inside `\u003cRouter\u003e` or `\u003cProvider\u003e`\n\n```javascript\n// index.js\n\nimport React from 'react'\nimport ReactDOM from 'react-dom'\nimport { AliveScope } from 'react-activation'\n\nimport App from './App'\n\nReactDOM.render(\n  \u003cAliveScope\u003e\n    \u003cApp /\u003e\n  \u003c/AliveScope\u003e,\n  document.getElementById('root')\n)\n```\n\n---\n\n## Lifecycle\n\n`ClassComponent` works with `withActivation` decorator\n\nUse `componentDidActivate` and `componentWillUnactivate` to correspond to the two states of \"activate\" and \"unactivate\" respectively.\n\n`FunctionComponent` uses the `useActivate` and `useUnactivate` hooks respectively\n\n```javascript\n...\nimport KeepAlive, { useActivate, useUnactivate, withActivation } from 'react-activation'\n\n@withActivation\nclass TestClass extends Component {\n  ...\n  componentDidActivate() {\n    console.log('TestClass: componentDidActivate')\n  }\n\n  componentWillUnactivate() {\n    console.log('TestClass: componentWillUnactivate')\n  }\n  ...\n}\n...\nfunction TestFunction() {\n  useActivate(() =\u003e {\n    console.log('TestFunction: didActivate')\n  })\n\n  useUnactivate(() =\u003e {\n    console.log('TestFunction: willUnactivate')\n  })\n  ...\n}\n...\nfunction App() {\n  ...\n  return (\n    {show \u0026\u0026 (\n      \u003cKeepAlive\u003e\n        \u003cTestClass /\u003e\n        \u003cTestFunction /\u003e\n      \u003c/KeepAlive\u003e\n    )}\n  )\n}\n...\n```\n\n---\n\n## Cache Control\n\n### Manually control the cache\n\n1. Add the `name` attribute to the `\u003cKeepAlive\u003e` tag that needs to control the cache.\n\n2. Get control functions using `withAliveScope` or `useAliveController`.\n\n   - **drop(name)**: (`drop` can only be used for nodes in the cache state. If the node is not cached but needs to clear the cache state, please use `refresh`)\n\n     Unload the `\u003cKeepAlive\u003e` node in cache state by name. The name can be of type `String` or `RegExp`. Note that only the first layer of content that hits `\u003cKeepAlive\u003e` is unloaded and will not be uninstalled in `\u003cKeepAlive\u003e`. Would not unload nested `\u003cKeepAlive\u003e`.\n\n   - **dropScope(name)**: (`dropScope` can only be used for nodes in the cache state. If the node is not cached but needs to clear the cache state, please use `refreshScope`)\n\n     Unloads the `\u003cKeepAlive\u003e` node in cache state by name. The name optional type is `String` or `RegExp`, which will unload all content of `\u003cKeepAlive\u003e`, including all `\u003cKeepAlive\u003e` nested in `\u003cKeepAlive\u003e`.\n\n   - **refresh(name)**:\n\n     Refresh the `\u003cKeepAlive\u003e` node in cache state by name. The name can be of type `String` or `RegExp`. Note that only the first layer of content that hits `\u003cKeepAlive\u003e` is refreshed and will not be uninstalled in `\u003cKeepAlive\u003e`. Would not refresh nested `\u003cKeepAlive\u003e`.\n\n   - **refreshScope(name)**:\n\n     Refresh the `\u003cKeepAlive\u003e` node in cache state by name. The name optional type is `String` or `RegExp`, which will refresh all content of `\u003cKeepAlive\u003e`, including all `\u003cKeepAlive\u003e` nested in `\u003cKeepAlive\u003e`.\n\n\n   - **clear()**:\n\n     will clear all `\u003cKeepAlive\u003e` in the cache\n\n   - **getCachingNodes()**:\n\n     Get all the nodes in the cache\n\n```javascript\n...\nimport KeepAlive, { withAliveScope, useAliveController } from 'react-activation'\n...\n\u003cKeepAlive name=\"Test\"\u003e\n  ...\n    \u003cKeepAlive\u003e\n      ...\n        \u003cKeepAlive\u003e\n          ...\n        \u003c/KeepAlive\u003e\n      ...\n    \u003c/KeepAlive\u003e\n  ...\n\u003c/KeepAlive\u003e\n...\nfunction App() {\n  const { drop, dropScope, clear, getCachingNodes } = useAliveController()\n\n  useEffect(() =\u003e {\n    drop('Test')\n    // or\n    drop(/Test/)\n    // or\n    dropScope('Test')\n\n    clear()\n  })\n\n  return (\n    ...\n  )\n}\n// or\n@withAliveScope\nclass App extends Component {\n  render() {\n    const { drop, dropScope, clear, getCachingNodes } = this.props\n\n    return (\n      ...\n    )\n  }\n}\n...\n```\n\n---\n\n### Automatic control cache\n\nAdd the `when` attribute to the `\u003cKeepAlive /\u003e` tag that needs to control the cache. The value is as follows\n\n#### When the `when` type is `Boolean`\n\n- **true**: Cache after uninstallation\n- **false**: Not cached after uninstallation\n\n```javascript\n\u003cKeepAlive when={false}\u003e\n```\n\n#### When the `when` type is `Array`\n\nThe **1th** parameter indicates whether it needs to be cached at the time of uninstallation.\n\nThe **2th** parameter indicates whether to unload all cache contents of `\u003cKeepAlive\u003e`, including all `\u003cKeepAlive\u003e` nested in `\u003cKeepAlive\u003e`.\n\n```javascript\n// For example:\n// The following indicates that it is not cached when uninstalling\n// And uninstalls all nested `\u003cKeepAlive\u003e`\n\u003cKeepAlive when={[false, true]}\u003e\n  ...\n  \u003cKeepAlive\u003e\n    ...\n    \u003cKeepAlive\u003e...\u003c/KeepAlive\u003e\n    ...\n  \u003c/KeepAlive\u003e\n  ...\n\u003c/KeepAlive\u003e\n```\n\n#### When the `when` type is `Function` (**Recommended**)\n\nThe return value is the above `Boolean` or `Array`, which takes effect as described above.\n\nThe final calculation time of `when` is adjusted to `componentWillUnmount` lifecicle of `\u003cKeepAlive\u003e`, the problem that most of the `when` do not achieve the expected effect can be avoided.\n\n```jsx\n\u003cKeepAlive when={() =\u003e true}\u003e\n\u003cKeepAlive when={() =\u003e [false, true]}\u003e\n```\n\n---\n\n## Multiple Cache\n\nUnder the same parent node, `\u003cKeepAlive\u003e` in the same location will use the same cache by default.\n\nFor example, with the following parameter routing scenario, the `/item` route will be rendered differently by `id`, but only the same cache can be kept.\n\n```javascript\n\u003cRoute\n  path=\"/item/:id\"\n  render={props =\u003e (\n    \u003cKeepAlive\u003e\n      \u003cItem {...props} /\u003e\n    \u003c/KeepAlive\u003e\n  )}\n/\u003e\n```\n\nSimilar scenarios, you can use the `id` attribute of `\u003cKeepAlive\u003e` to implement multiple caches according to specific conditions.\n\n```javascript\n\u003cRoute\n  path=\"/item/:id\"\n  render={props =\u003e (\n    \u003cKeepAlive id={props.match.params.id}\u003e\n      \u003cItem {...props} /\u003e\n    \u003c/KeepAlive\u003e\n  )}\n/\u003e\n```\n\n---\n\n## Save Scroll Position (`true` by default)\n\n`\u003cKeepAlive /\u003e` would try to detect scrollable nodes in its `children`, then, save their scroll position automaticlly before `componentWillUnactivate` and restore saving position after `componentDidActivate`\n\nIf you don't want `\u003cKeepAlive /\u003e` to do this thing, set `saveScrollPosition` prop to `false`\n\n```javascript\n\u003cKeepAlive saveScrollPosition={false} /\u003e\n```\n\nIf your components share screen scroll container, `document.body` or `document.documentElement`, set `saveScrollPosition` prop to `\"screen\"` can save sharing screen container's scroll position before `componentWillUnactivate`\n\n```javascript\n\u003cKeepAlive saveScrollPosition=\"screen\" /\u003e\n```\n\n---\n\n## Principle\n\nPass the `children` attribute of `\u003cKeepAlive /\u003e` to `\u003cAliveScope /\u003e` and render it with `\u003cKeeper /\u003e`\n\nAfter rendering `\u003cKeeper /\u003e`, the content is transferred to `\u003cKeepAlive /\u003e` through `DOM` operation.\n\nSince `\u003cKeeper /\u003e` will not be uninstalled, caching can be implemented.\n\n[Simplest Implementation Demo](https://codesandbox.io/s/zuijian-react-keepalive-shixian-ovh90)\n\n\u003cimg src=\"./docs/reactActivationPrinciple.gif\"\u003e\n\n---\n\n## Breaking Change\n\n1. `\u003cKeepAlive /\u003e` needs to pass children to `\u003cAliveScope /\u003e` , so the rendering of the real content will be **slower than the normal situation**\n\n   Will have a certain impact on the function of strictly relying on the lifecycle order, such as getting the value of `ref` in `componentDidMount`, as follows\n\n   ```javascript\n   class Test extends Component {\n     componentDidMount() {\n       console.log(this.outside) // will log \u003cdiv /\u003e instance\n       console.log(this.inside) // will log undefined\n     }\n\n     render() {\n       return (\n         \u003cdiv\u003e\n           \u003cdiv\n             ref={ref =\u003e {\n               this.outside = ref\n             }}\n           \u003e\n             Outside KeepAlive\n           \u003c/div\u003e\n           \u003cKeepAlive\u003e\n             \u003cdiv\n               ref={ref =\u003e {\n                 this.inside = ref\n               }}\n             \u003e\n               Inside KeepAlive\n             \u003c/div\u003e\n           \u003c/KeepAlive\u003e\n         \u003c/div\u003e\n       )\n     }\n   }\n   ```\n\n   The above error in `ClassComponent` can be fixed by using the `withActivation` high-level component\n\n   `FunctionComponent` currently has no processing method, you can use `setTimeout` or `nextTick` to delay ref getting behavior\n\n   ```javascript\n   @withActivation\n   class Test extends Component {\n     componentDidMount() {\n       console.log(this.outside) // will log \u003cdiv /\u003e instance\n       console.log(this.inside) // will log \u003cdiv /\u003e instance\n     }\n\n     render() {\n       return (\n         \u003cdiv\u003e\n           \u003cdiv\n             ref={ref =\u003e {\n               this.outside = ref\n             }}\n           \u003e\n             Outside KeepAlive\n           \u003c/div\u003e\n           \u003cKeepAlive\u003e\n             \u003cdiv\n               ref={ref =\u003e {\n                 this.inside = ref\n               }}\n             \u003e\n               Inside KeepAlive\n             \u003c/div\u003e\n           \u003c/KeepAlive\u003e\n         \u003c/div\u003e\n       )\n     }\n   }\n   ```\n\n2. Destructive impact on `Context`\n\n   after `react-actication@0.8.0` with `react@16.3+`, this question has been automatic fixed\n\n   `react-actication@0.8.0` with `react@17+` you Need to make the following changes to achieve automatic repair\n\n   ```jsx\n   import { autoFixContext } from 'react-activation'\n\n   autoFixContext(\n    [require('react/jsx-runtime'), 'jsx', 'jsxs', 'jsxDEV'],\n    [require('react/jsx-dev-runtime'), 'jsx', 'jsxs', 'jsxDEV']\n   )\n   ```\n   \n   Versions below `react-actication@0.8.0` need to be repaired manually, refer to the following\n\n   Problem reference: https://github.com/StructureBuilder/react-keep-alive/issues/36\n\n   ```javascript\n   \u003cProvider value={1}\u003e\n     {show \u0026\u0026 (\n       \u003cKeepAlive\u003e\n         \u003cConsumer\u003e\n           {(\n             context // Since the rendering level is broken, the context cannot be obtained here.\n           ) =\u003e \u003cTest contextValue={context} /\u003e}\n         \u003c/Consumer\u003e\n       \u003c/KeepAlive\u003e\n     )}\n     \u003cbutton onClick={toggle}\u003etoggle\u003c/button\u003e\n   \u003c/Provider\u003e\n   ```\n\n   Choose a repair method\n\n   - Create `Context` using `createContext` exported from `react-activation`\n\n   - Fix the affected `Context` with `fixContext` exported from `react-activation`\n\n   ```javascript\n   ...\n   import { createContext } from 'react-activation'\n\n   const { Provider, Consumer } = createContext()\n   ...\n   // or\n   ...\n   import { createContext } from 'react'\n   import { fixContext } from 'react-activation'\n\n   const Context = createContext()\n   const { Provider, Consumer } = Context\n\n   fixContext(Context)\n   ...\n   ```\n\n3. Affects the functionality that depends on the level of the React component, as follows\n\n   - [x] [Fix `withRouter/hooks` of react-router](https://github.com/CJY0208/react-activation/issues/77)\n   - [x] ~~Error Boundaries~~ (Fixed)\n   - [x] ~~React.Suspense \u0026 React.lazy~~ (Fixed)\n   - [ ] React Synthetic Event Bubbling Failure\n   - [ ] Other undiscovered features\n\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCJY0208%2Freact-activation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FCJY0208%2Freact-activation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCJY0208%2Freact-activation/lists"}