Commit 3047b0d5 authored by Shen Chang's avatar Shen Chang

refactor(Add < KeepAlive > Components and delete keepAlive higher-order components):

parent 3e12c088
...@@ -6,7 +6,7 @@ import { ...@@ -6,7 +6,7 @@ import {
Link, Link,
BrowserRouter as Router, BrowserRouter as Router,
} from 'react-router-dom'; } from 'react-router-dom';
import {Provider} from '../../es'; import {Provider, KeepAlive} from '../../es';
import A from './views/A'; import A from './views/A';
import B from './views/B'; import B from './views/B';
import C from './views/C'; import C from './views/C';
...@@ -58,18 +58,24 @@ class App extends React.Component { ...@@ -58,18 +58,24 @@ class App extends React.Component {
<Route <Route
path="/a" path="/a"
render={() => ( render={() => (
<React.Fragment> <KeepAlive key="A" disabled={!toggle}>
<A keepAlive={toggle} /> <A />
</React.Fragment> </KeepAlive>
)} )}
/> />
<Route <Route
path="/b" path="/b"
render={() => <B />} render={() => (
<B />
)}
/> />
<Route <Route
path="/c" path="/c"
render={() => <C />} render={() => (
<KeepAlive key="C">
<C />
</KeepAlive>
)}
/> />
</Switch> </Switch>
</div> </div>
......
import React from 'react'; import React from 'react';
import {keepAlive, bindLifecycle} from '../../../es'; import {bindLifecycle} from '../../../es';
@bindLifecycle @bindLifecycle
class Content extends React.Component { class Content extends React.Component {
...@@ -40,7 +40,7 @@ class Content extends React.Component { ...@@ -40,7 +40,7 @@ class Content extends React.Component {
} }
} }
@keepAlive() @bindLifecycle
class Test extends React.Component { class Test extends React.Component {
state = { state = {
index: 0, index: 0,
......
import React from 'react'; import React from 'react';
import {keepAlive, bindLifecycle} from '../../../es'; import {bindLifecycle} from '../../../es';
@keepAlive() @bindLifecycle
class B extends React.Component { class B extends React.Component {
componentWillMount() { componentWillMount() {
console.log('B componentWillMount'); console.log('B componentWillMount');
......
import React from 'react'; import React from 'react';
import {keepAlive} from '../../../es'; import {bindLifecycle} from '../../../es';
@bindLifecycle
class C extends React.Component { class C extends React.Component {
componentWillMount() { componentWillMount() {
console.log('C componentWillMount'); console.log('C componentWillMount');
...@@ -38,4 +39,4 @@ class C extends React.Component { ...@@ -38,4 +39,4 @@ class C extends React.Component {
} }
} }
export default keepAlive()(C); export default C;
{ {
"name": "react-keep-alive", "name": "react-keep-alive",
"version": "0.2.2", "version": "0.3.0",
"description": "Package will allow components to maintain their status, to avoid repeated re-rendering.", "description": "Package will allow components to maintain their status, to avoid repeated re-rendering.",
"author": "Shen Chang", "author": "Shen Chang",
"homepage": "https://github.com/Sam618/react-keep-alive", "homepage": "https://github.com/Sam618/react-keep-alive",
......
...@@ -3,7 +3,6 @@ import Comment from './Comment'; ...@@ -3,7 +3,6 @@ import Comment from './Comment';
import {LIFECYCLE, ICache, ICacheItem} from './Provider'; import {LIFECYCLE, ICache, ICacheItem} from './Provider';
import {warn} from '../utils/debug'; import {warn} from '../utils/debug';
import findDOMNodeByFiberNode from '../utils/findDOMNodeByFiberNode'; import findDOMNodeByFiberNode from '../utils/findDOMNodeByFiberNode';
import createUniqueIdentification from '../utils/createUniqueIdentification';
interface IConsumerProps { interface IConsumerProps {
children: React.ReactNode; children: React.ReactNode;
...@@ -19,9 +18,6 @@ class Consumer extends React.PureComponent<IConsumerProps> { ...@@ -19,9 +18,6 @@ class Consumer extends React.PureComponent<IConsumerProps> {
private identification: string = this.props.identification; private identification: string = this.props.identification;
// This attribute is designed to prevent duplicates of the identification of KeepAlive components.
private key: string = createUniqueIdentification();
constructor(props: IConsumerProps, ...args: any) { constructor(props: IConsumerProps, ...args: any) {
super(props, ...args); super(props, ...args);
const {cache, setCache, children} = props; const {cache, setCache, children} = props;
...@@ -43,7 +39,6 @@ class Consumer extends React.PureComponent<IConsumerProps> { ...@@ -43,7 +39,6 @@ class Consumer extends React.PureComponent<IConsumerProps> {
children, children,
keepAlive, keepAlive,
lifecycle: LIFECYCLE.MOUNTED, lifecycle: LIFECYCLE.MOUNTED,
key: this.key,
renderElement: this.renderElement, renderElement: this.renderElement,
activated: true, activated: true,
}); });
......
import React from 'react';
import noop from '../utils/noop';
import keepAlive, {IKeepAliveComponentProps} from '../utils/keepAlive';
export const keepAliveDisplayName = '$$KeepAlive';
interface IKeepAliveProps extends IKeepAliveComponentProps {
key: string;
onActivate?: () => void;
onUnactivate?: () => void;
}
class KeepAlive extends React.PureComponent<IKeepAliveProps> {
public displayName = keepAliveDisplayName;
public static defaultProps = {
onActivate: noop,
onUnactivate: noop,
};
public componentDidActivate() {
(this.props as any).onActivate();
}
public componentWillUnactivate() {
(this.props as any).onUnactivate();
}
public render() {
return this.props.children;
}
}
export default keepAlive(KeepAlive) as React.ComponentClass<IKeepAliveProps>;
...@@ -7,7 +7,7 @@ import {warn} from '../utils/debug'; ...@@ -7,7 +7,7 @@ import {warn} from '../utils/debug';
import createUniqueIdentification from '../utils/createUniqueIdentification'; import createUniqueIdentification from '../utils/createUniqueIdentification';
import createStoreElement from '../utils/createStoreElement'; import createStoreElement from '../utils/createStoreElement';
export const keepAliveProviderTypeName = 'KeepAliveProvider'; export const keepAliveProviderTypeName = '$$KeepAliveProvider';
export const START_MOUNTING_DOM = 'startMountingDOM'; export const START_MOUNTING_DOM = 'startMountingDOM';
export enum LIFECYCLE { export enum LIFECYCLE {
...@@ -20,7 +20,6 @@ export interface ICacheItem { ...@@ -20,7 +20,6 @@ export interface ICacheItem {
children: React.ReactNode; children: React.ReactNode;
keepAlive: boolean; keepAlive: boolean;
lifecycle: LIFECYCLE; lifecycle: LIFECYCLE;
key?: string | null;
renderElement?: HTMLElement; renderElement?: HTMLElement;
activated?: boolean; activated?: boolean;
ifStillActivate?: boolean; ifStillActivate?: boolean;
...@@ -87,10 +86,6 @@ export default class KeepAliveProvider extends React.PureComponent<IKeepAlivePro ...@@ -87,10 +86,6 @@ export default class KeepAliveProvider extends React.PureComponent<IKeepAlivePro
public setCache = (identification: string, value: ICacheItem) => { public setCache = (identification: string, value: ICacheItem) => {
const {cache, keys} = this; const {cache, keys} = this;
const currentCache = cache[identification]; const currentCache = cache[identification];
const key = currentCache && currentCache.key;
if (key && value.key && key !== (value.key as unknown)) {
warn('[React Keep Alive] Cached components have duplicates.');
}
if (!currentCache) { if (!currentCache) {
keys.push(identification); keys.push(identification);
} }
...@@ -101,6 +96,10 @@ export default class KeepAliveProvider extends React.PureComponent<IKeepAlivePro ...@@ -101,6 +96,10 @@ export default class KeepAliveProvider extends React.PureComponent<IKeepAlivePro
this.forceUpdate(); this.forceUpdate();
} }
public componentDidCatch() {
warn('[React Keep Alive] Cached components have duplicates. Please check the <KeepAlive> component of the key duplication!');
}
// private getMax = () => { // private getMax = () => {
// return this.props.max ? parseInt(this.props.max) : null; // return this.props.max ? parseInt(this.props.max) : null;
// } // }
...@@ -126,7 +125,6 @@ export default class KeepAliveProvider extends React.PureComponent<IKeepAlivePro ...@@ -126,7 +125,6 @@ export default class KeepAliveProvider extends React.PureComponent<IKeepAlivePro
const {cache} = this; const {cache} = this;
this.cache[identification] = { this.cache[identification] = {
...cache[identification], ...cache[identification],
key: null,
activated: false, activated: false,
lifecycle: LIFECYCLE.UNMOUNTED, lifecycle: LIFECYCLE.UNMOUNTED,
}; };
......
import Provider from './components/Provider'; import Provider from './components/Provider';
import keepAlive from './utils/keepAlive'; import KeepAlive from './components/KeepAlive';
import bindLifecycle from './utils/bindLifecycle'; import bindLifecycle from './utils/bindLifecycle';
export { export {
Provider, Provider,
keepAlive, KeepAlive,
bindLifecycle, bindLifecycle,
}; };
...@@ -5,7 +5,7 @@ import {COMMAND} from './keepAlive'; ...@@ -5,7 +5,7 @@ import {COMMAND} from './keepAlive';
import withIdentificationContextConsumer from './withIdentificationContextConsumer'; import withIdentificationContextConsumer from './withIdentificationContextConsumer';
import getDisplayName from './getDisplayName'; import getDisplayName from './getDisplayName';
export default function bindLifecycle<P = any>(Component: React.ComponentType<P>) { export default function bindLifecycle<P = any>(Component: React.ComponentClass<P>) {
const WrappedComponent = (Component as any).WrappedComponent || (Component as any).wrappedComponent || Component; const WrappedComponent = (Component as any).WrappedComponent || (Component as any).wrappedComponent || Component;
const { const {
...@@ -111,7 +111,7 @@ export default function bindLifecycle<P = any>(Component: React.ComponentType<P> ...@@ -111,7 +111,7 @@ export default function bindLifecycle<P = any>(Component: React.ComponentType<P>
: null : null
), ),
); );
const BindLifecycle = React.forwardRef((props, ref) => ( const BindLifecycle = React.forwardRef((props: P, ref) => (
<BindLifecycleHOC {...props} forwardRef={ref} /> <BindLifecycleHOC {...props} forwardRef={ref} />
)); ));
......
import React from 'react';
import {keepAliveProviderTypeName} from '../components/Provider';
import {keepAliveDisplayName} from './keepAlive';
export default function getContextIdentificationByFiberNode(fiberNode: any) {
let globalKey: React.Key | null = null;
let typeNames = '';
function getPathsByFiberNode(fiberNode: any) {
if (!fiberNode) {
return '';
}
const {
type,
key,
index,
} = fiberNode;
const typeName = type && type.displayName;
if (typeName === keepAliveProviderTypeName) {
return '';
}
const joinName: string = getPathsByFiberNode(fiberNode.return);
if (type && type.displayName && type.displayName.indexOf(keepAliveDisplayName) !== -1) {
if (!globalKey) {
globalKey = key;
}
}
typeNames += typeName;
return `${key || index}${joinName}`;
}
const paths = getPathsByFiberNode(fiberNode);
return {
paths,
globalKey,
typeNames,
};
}
import React from 'react'; import React from 'react';
export default function getDisplayName(Component: React.ComponentType) { export default function getDisplayName(Component: React.ComponentType) {
return Component.displayName || Component.name || null; return Component.displayName || Component.name || 'Component';
} }
import {WithKeepAliveContextConsumerDisplayName} from './withKeepAliveContextConsumer';
export default function getKeyByFiberNode(fiberNode: any): string | null {
if (!fiberNode) {
return null;
}
const {
key,
type,
} = fiberNode;
if (type.displayName && type.displayName.indexOf(WithKeepAliveContextConsumerDisplayName) !== -1) {
return key;
}
return getKeyByFiberNode(fiberNode.return);
}
...@@ -6,12 +6,11 @@ import {START_MOUNTING_DOM, LIFECYCLE} from '../components/Provider'; ...@@ -6,12 +6,11 @@ import {START_MOUNTING_DOM, LIFECYCLE} from '../components/Provider';
import md5 from './md5'; import md5 from './md5';
import noop from './noop'; import noop from './noop';
import {warn} from './debug'; import {warn} from './debug';
import getContextIdentificationByFiberNode from './getContextIdentificationByFiberNode'; import getKeyByFiberNode from './getKeyByFiberNode';
import withIdentificationContextConsumer, {IIdentificationContextComponentProps} from './withIdentificationContextConsumer'; import withIdentificationContextConsumer, {IIdentificationContextConsumerComponentProps} from './withIdentificationContextConsumer';
import withKeepAliveContextConsumer, {IKeepAliveContextComponentProps} from './withKeepAliveContextConsumer'; import withKeepAliveContextConsumer, {IKeepAliveContextConsumerComponentProps} from './withKeepAliveContextConsumer';
import changePositionByComment from './changePositionByComment'; import changePositionByComment from './changePositionByComment';
import shallowEqual from './shallowEqual'; import shallowEqual from './shallowEqual';
import getDisplayName from './getDisplayName';
import getKeepAlive from './getKeepAlive'; import getKeepAlive from './getKeepAlive';
export enum COMMAND { export enum COMMAND {
...@@ -22,470 +21,432 @@ export enum COMMAND { ...@@ -22,470 +21,432 @@ export enum COMMAND {
CURRENT_UNACTIVATE = 'current_unactivate', CURRENT_UNACTIVATE = 'current_unactivate',
} }
export interface IOptions { export const keepAliveDisplayName = '$$KeepAlive';
name?: string;
forwardRef?: boolean;
}
export const keepAliveDisplayName = 'keepAlive';
interface IKeepAliveDecorativeComponentProps { export interface IKeepAliveComponentProps {
keepAlive?: boolean; disabled?: boolean;
} }
interface IListenUpperKeepAliveContainerProps extends IKeepAliveDecorativeComponentProps, IIdentificationContextComponentProps, IKeepAliveContextComponentProps { interface IListenUpperKeepAliveContainerProps extends IKeepAliveComponentProps, IIdentificationContextConsumerComponentProps, IKeepAliveContextConsumerComponentProps {}
forwardedRef?: React.Ref<{}>;
}
interface IListenUpperKeepAliveContainerState { interface IListenUpperKeepAliveContainerState {
activated: boolean; activated: boolean;
} }
interface ITriggerLifecycleContainerProps extends IKeepAliveContextComponentProps { interface ITriggerLifecycleContainerProps extends IKeepAliveContextConsumerComponentProps {
forwardedRef?: React.Ref<{}>; propKey: string;
keepAlive: boolean; keepAlive: boolean;
getCombinedKeepAlive: () => boolean; getCombinedKeepAlive: () => boolean;
} }
interface IComponentProps { export default (Component: React.ComponentType<any>) => {
_container: object; const {
keepAlive: boolean; componentDidMount = noop,
ref: React.Ref<{}>; componentDidUpdate = noop,
} componentWillUnactivate = noop,
componentWillUnmount = noop,
} = Component.prototype;
export default function keepAliveDecorator({ Component.prototype.componentDidMount = function () {
name, const {
forwardRef = false, _container,
}: IOptions = {}) { keepAlive,
return (Component: React.ComponentType<IComponentProps>) => { } = this.props;
const { const {
componentDidMount = noop, notNeedActivate,
componentDidUpdate = noop, identification,
componentWillUnactivate = noop, eventEmitter,
componentWillUnmount = noop, } = _container;
} = Component.prototype; notNeedActivate();
const displayName = (name || getDisplayName(Component)) as any; const cb = () => {
mount.call(this);
if (!displayName) { listen.call(this);
warn('[React Keep Alive] Each component must have a name, which can be the component\'s displayName or name static property. You can also configure name when keepAlive decorates the component.'); eventEmitter.off([identification, START_MOUNTING_DOM], cb);
};
eventEmitter.on([identification, START_MOUNTING_DOM], cb);
componentDidMount.call(this);
if (keepAlive) {
this.componentDidActivate();
} }
};
Component.prototype.componentDidMount = function () { Component.prototype.componentDidUpdate = function () {
const { componentDidUpdate.call(this);
_container, const {
keepAlive, _container,
} = this.props; } = this.props;
const { const {
notNeedActivate, notNeedActivate,
identification, isNeedActivate,
eventEmitter, } = _container;
} = _container; if (isNeedActivate()) {
notNeedActivate(); notNeedActivate();
const cb = () => { mount.call(this);
mount.call(this); listen.call(this);
listen.call(this); this._unmounted = false;
eventEmitter.off([identification, START_MOUNTING_DOM], cb); this.componentDidActivate();
}; }
eventEmitter.on([identification, START_MOUNTING_DOM], cb); };
componentDidMount.call(this); Component.prototype.componentWillUnactivate = function () {
if (keepAlive) { componentWillUnactivate.call(this);
this.componentDidActivate(); unmount.call(this);
} unlisten.call(this);
}; };
Component.prototype.componentDidUpdate = function () { Component.prototype.componentWillUnmount = function () {
componentDidUpdate.call(this); // Because we will manually call the componentWillUnmount lifecycle
const { // so we need to prevent it from firing multiple times
_container, if (!this._unmounted) {
} = this.props; this._unmounted = true;
const { componentWillUnmount.call(this);
notNeedActivate,
isNeedActivate,
} = _container;
if (isNeedActivate()) {
notNeedActivate();
mount.call(this);
listen.call(this);
this._unmounted = false;
this.componentDidActivate();
}
};
Component.prototype.componentWillUnactivate = function () {
componentWillUnactivate.call(this);
unmount.call(this); unmount.call(this);
unlisten.call(this); unlisten.call(this);
}; }
Component.prototype.componentWillUnmount = function () { };
// Because we will manually call the componentWillUnmount lifecycle
// so we need to prevent it from firing multiple times
if (!this._unmounted) {
this._unmounted = true;
componentWillUnmount.call(this);
unmount.call(this);
unlisten.call(this);
}
};
function mount(this: any) { function mount(this: any) {
const { const {
_container: { _container: {
cache, cache,
identification, identification,
storeElement, storeElement,
setLifecycle, setLifecycle,
}, },
} = this.props; } = this.props;
const {renderElement} = cache[identification]; const {renderElement} = cache[identification];
setLifecycle(LIFECYCLE.UPDATING); setLifecycle(LIFECYCLE.UPDATING);
changePositionByComment(identification, renderElement, storeElement); changePositionByComment(identification, renderElement, storeElement);
}
function unmount(this: any) {
const {
_container: {
identification,
storeElement,
cache,
setLifecycle,
},
} = this.props;
const {renderElement, ifStillActivate, reactivate} = cache[identification];
setLifecycle(LIFECYCLE.UNMOUNTED);
changePositionByComment(identification, storeElement, renderElement);
if (ifStillActivate) {
reactivate();
} }
}
function unmount(this: any) { function listen(this: any) {
const {
_container: {
identification,
eventEmitter,
},
} = this.props;
eventEmitter.on(
[identification, COMMAND.CURRENT_UNMOUNT],
this._bindUnmount = this.componentWillUnmount.bind(this),
);
eventEmitter.on(
[identification, COMMAND.CURRENT_UNACTIVATE],
this._bindUnactivate = this.componentWillUnactivate.bind(this),
);
}
function unlisten(this: any) {
const {
_container: {
identification,
eventEmitter,
},
} = this.props;
eventEmitter.off([identification, COMMAND.CURRENT_UNMOUNT], this._bindUnmount);
eventEmitter.off([identification, COMMAND.CURRENT_UNACTIVATE], this._bindUnactivate);
}
class TriggerLifecycleContainer extends React.PureComponent<ITriggerLifecycleContainerProps> {
private identification: string;
private activated = false;
private ifStillActivate = false;
// Let the lifecycle of the cached component be called normally.
private needActivate = true;
private lifecycle = LIFECYCLE.MOUNTED;
public componentDidMount() {
if (!this.ifStillActivate) {
this.activate();
}
const { const {
_container: { keepAlive,
identification, _keepAliveContextProps: {
storeElement, eventEmitter,
cache,
setLifecycle,
}, },
} = this.props; } = this.props;
const {renderElement, ifStillActivate, reactivate} = cache[identification]; if (keepAlive) {
setLifecycle(LIFECYCLE.UNMOUNTED); this.needActivate = true;
changePositionByComment(identification, storeElement, renderElement); eventEmitter.emit([this.identification, COMMAND.ACTIVATE]);
if (ifStillActivate) {
reactivate();
} }
} }
function listen(this: any) { public componentDidCatch() {
const { if (!this.activated) {
_container: { this.activate();
identification, }
eventEmitter,
},
} = this.props;
eventEmitter.on(
[identification, COMMAND.CURRENT_UNMOUNT],
this._bindUnmount = this.componentWillUnmount.bind(this),
);
eventEmitter.on(
[identification, COMMAND.CURRENT_UNACTIVATE],
this._bindUnactivate = this.componentWillUnactivate.bind(this),
);
} }
function unlisten(this: any) { public componentWillUnmount() {
const { const {
_container: { getCombinedKeepAlive,
identification, _keepAliveContextProps: {
eventEmitter, eventEmitter,
isExisted,
}, },
} = this.props; } = this.props;
eventEmitter.off([identification, COMMAND.CURRENT_UNMOUNT], this._bindUnmount); const keepAlive = getCombinedKeepAlive();
eventEmitter.off([identification, COMMAND.CURRENT_UNACTIVATE], this._bindUnactivate); if (!keepAlive || !isExisted()) {
} eventEmitter.emit([this.identification, COMMAND.CURRENT_UNMOUNT]);
eventEmitter.emit([this.identification, COMMAND.UNMOUNT]);
class TriggerLifecycleContainer extends React.PureComponent<ITriggerLifecycleContainerProps> {
private identification: string;
private activated = false;
private ifStillActivate = false;
// Let the lifecycle of the cached component be called normally.
private needActivate = true;
private lifecycle = LIFECYCLE.MOUNTED;
public componentDidMount() {
if (!this.ifStillActivate) {
this.activate();
}
const {
keepAlive,
_keepAliveContextProps: {
eventEmitter,
},
} = this.props;
if (keepAlive) {
this.needActivate = true;
eventEmitter.emit([this.identification, COMMAND.ACTIVATE]);
}
} }
// When the Provider components are unmounted, the cache is not needed,
public componentDidCatch() { // so you don't have to execute the componentWillUnactivate lifecycle.
if (!this.activated) { if (keepAlive && isExisted()) {
this.activate(); eventEmitter.emit([this.identification, COMMAND.CURRENT_UNACTIVATE]);
} eventEmitter.emit([this.identification, COMMAND.UNACTIVATE]);
}
public componentWillUnmount() {
const {
getCombinedKeepAlive,
_keepAliveContextProps: {
eventEmitter,
isExisted,
},
} = this.props;
const keepAlive = getCombinedKeepAlive();
if (!keepAlive || !isExisted()) {
eventEmitter.emit([this.identification, COMMAND.CURRENT_UNMOUNT]);
eventEmitter.emit([this.identification, COMMAND.UNMOUNT]);
}
// When the Provider components are unmounted, the cache is not needed,
// so you don't have to execute the componentWillUnactivate lifecycle.
if (keepAlive && isExisted()) {
eventEmitter.emit([this.identification, COMMAND.CURRENT_UNACTIVATE]);
eventEmitter.emit([this.identification, COMMAND.UNACTIVATE]);
}
} }
}
private activate = () => { private activate = () => {
this.activated = true; this.activated = true;
} }
private reactivate = () => { private reactivate = () => {
this.ifStillActivate = false; this.ifStillActivate = false;
this.forceUpdate(); this.forceUpdate();
} }
private isNeedActivate = () => { private isNeedActivate = () => {
return this.needActivate; return this.needActivate;
} }
private notNeedActivate = () => { private notNeedActivate = () => {
this.needActivate = false; this.needActivate = false;
} }
private getLifecycle = () => { private getLifecycle = () => {
return this.lifecycle; return this.lifecycle;
} }
private setLifecycle = (lifecycle: LIFECYCLE) => { private setLifecycle = (lifecycle: LIFECYCLE) => {
this.lifecycle = lifecycle; this.lifecycle = lifecycle;
} }
public render() { public render() {
if (!this.identification) { const {
// We need to generate a corresponding unique identifier based on the information of the component. propKey,
const { keepAlive,
providerIdentification, getCombinedKeepAlive,
cache, _keepAliveContextProps: {
} = this.props._keepAliveContextProps; isExisted,
const { storeElement,
paths, cache,
globalKey, eventEmitter,
typeNames, setCache,
} = getContextIdentificationByFiberNode((this as any)._reactInternalFiber); unactivate,
this.identification = md5( providerIdentification,
`${providerIdentification}${displayName}${globalKey ? `${globalKey}${typeNames}` : paths}`, },
); ...wrapperProps
// The last activated component must be unactivated before it can be activated again. } = this.props;
const currentCache = cache[this.identification]; if (!this.identification) {
if (currentCache) { // We need to generate a corresponding unique identifier based on the information of the component.
this.ifStillActivate = currentCache.activated as boolean; this.identification = md5(
currentCache.ifStillActivate = this.ifStillActivate; `${providerIdentification}${propKey}`,
currentCache.reactivate = this.reactivate; );
} // The last activated component must be unactivated before it can be activated again.
const currentCache = cache[this.identification];
if (currentCache) {
this.ifStillActivate = currentCache.activated as boolean;
currentCache.ifStillActivate = this.ifStillActivate;
currentCache.reactivate = this.reactivate;
} }
const { }
isNeedActivate, const {
notNeedActivate, isNeedActivate,
activated, notNeedActivate,
getLifecycle, activated,
setLifecycle, getLifecycle,
identification, setLifecycle,
ifStillActivate, identification,
} = this; ifStillActivate,
const { } = this;
keepAlive, return !ifStillActivate
forwardedRef, ? (
getCombinedKeepAlive, <Consumer
_keepAliveContextProps: { identification={identification}
isExisted, keepAlive={keepAlive}
storeElement, cache={cache}
cache, setCache={setCache}
eventEmitter, unactivate={unactivate}
setCache, >
unactivate, <IdentificationContext.Provider
}, value={{
...wrapperProps identification,
} = this.props; eventEmitter,
return !ifStillActivate keepAlive,
? ( activated,
<Consumer getLifecycle,
identification={identification} isExisted,
keepAlive={keepAlive} }}
cache={cache}
setCache={setCache}
unactivate={unactivate}
> >
<IdentificationContext.Provider <Component
value={{ {...wrapperProps}
identification, keepAlive={keepAlive}
_container={{
isNeedActivate,
notNeedActivate,
setLifecycle,
eventEmitter, eventEmitter,
keepAlive, identification,
activated, storeElement,
getLifecycle, cache,
isExisted,
}} }}
> />
<Component </IdentificationContext.Provider>
{...wrapperProps} </Consumer>
keepAlive={keepAlive} )
ref={forwardedRef as React.Ref<{}>} : null;
_container={{
isNeedActivate,
notNeedActivate,
setLifecycle,
eventEmitter,
identification,
storeElement,
cache,
}}
/>
</IdentificationContext.Provider>
</Consumer>
)
: null;
}
} }
}
class ListenUpperKeepAliveContainer extends React.Component<IListenUpperKeepAliveContainerProps, IListenUpperKeepAliveContainerState> { class ListenUpperKeepAliveContainer extends React.Component<IListenUpperKeepAliveContainerProps, IListenUpperKeepAliveContainerState> {
private combinedKeepAlive: boolean; private combinedKeepAlive: boolean;
public state = { public state = {
activated: true, activated: true,
}; };
private activate: () => void; private activate: () => void;
private unactivate: () => void; private unactivate: () => void;
private unmount: () => void; private unmount: () => void;
public shouldComponentUpdate(nextProps: IListenUpperKeepAliveContainerProps, nextState: IListenUpperKeepAliveContainerState) { public shouldComponentUpdate(nextProps: IListenUpperKeepAliveContainerProps, nextState: IListenUpperKeepAliveContainerState) {
if (this.state.activated !== nextState.activated) { if (this.state.activated !== nextState.activated) {
return true; return true;
}
const {
_keepAliveContextProps,
_identificationContextProps,
...rest
} = this.props;
const {
_keepAliveContextProps: nextKeepAliveContextProps,
_identificationContextProps: nextIdentificationContextProps,
...nextRest
} = nextProps;
if (!shallowEqual(rest, nextRest)) {
return true;
}
if (
!shallowEqual(_keepAliveContextProps, nextKeepAliveContextProps) ||
!shallowEqual(_identificationContextProps, nextIdentificationContextProps)
) {
return true;
}
return false;
} }
const {
public componentDidMount() { _keepAliveContextProps,
this.listenUpperKeepAlive(); _identificationContextProps,
...rest
} = this.props;
const {
_keepAliveContextProps: nextKeepAliveContextProps,
_identificationContextProps: nextIdentificationContextProps,
...nextRest
} = nextProps;
if (!shallowEqual(rest, nextRest)) {
return true;
} }
if (
public componentWillUnmount() { !shallowEqual(_keepAliveContextProps, nextKeepAliveContextProps) ||
this.unlistenUpperKeepAlive(); !shallowEqual(_identificationContextProps, nextIdentificationContextProps)
) {
return true;
} }
return false;
}
private listenUpperKeepAlive() { public componentDidMount() {
const {identification, eventEmitter} = this.props._identificationContextProps; this.listenUpperKeepAlive();
if (!identification) { }
return;
}
eventEmitter.on(
[identification, COMMAND.ACTIVATE],
this.activate = () => this.setState({activated: true}),
true,
);
eventEmitter.on(
[identification, COMMAND.UNACTIVATE],
this.unactivate = () => this.setState({activated: false}),
true,
);
eventEmitter.on(
[identification, COMMAND.UNMOUNT],
this.unmount = () => this.setState({activated: false}),
true,
);
}
private unlistenUpperKeepAlive() { public componentWillUnmount() {
const {identification, eventEmitter} = this.props._identificationContextProps; this.unlistenUpperKeepAlive();
if (!identification) { }
return;
}
eventEmitter.off([identification, COMMAND.ACTIVATE], this.activate);
eventEmitter.off([identification, COMMAND.UNACTIVATE], this.unactivate);
eventEmitter.off([identification, COMMAND.UNMOUNT], this.unmount);
}
private getCombinedKeepAlive = () => { private listenUpperKeepAlive() {
return this.combinedKeepAlive; const {identification, eventEmitter} = this.props._identificationContextProps;
if (!identification) {
return;
} }
eventEmitter.on(
[identification, COMMAND.ACTIVATE],
this.activate = () => this.setState({activated: true}),
true,
);
eventEmitter.on(
[identification, COMMAND.UNACTIVATE],
this.unactivate = () => this.setState({activated: false}),
true,
);
eventEmitter.on(
[identification, COMMAND.UNMOUNT],
this.unmount = () => this.setState({activated: false}),
true,
);
}
public render() { private unlistenUpperKeepAlive() {
const { const {identification, eventEmitter} = this.props._identificationContextProps;
_identificationContextProps: { if (!identification) {
identification, return;
keepAlive: upperKeepAlive,
getLifecycle,
},
keepAlive,
...wrapperProps
} = this.props;
const {activated} = this.state;
const {
_keepAliveContextProps: {
include,
exclude,
},
} = wrapperProps;
// When the parent KeepAlive component is mounted or unmounted,
// use the keepAlive prop of the parent KeepAlive component.
const newKeepAlive = getKeepAlive(displayName, include, exclude, keepAlive);
this.combinedKeepAlive = getLifecycle === undefined || getLifecycle() === LIFECYCLE.UPDATING
? newKeepAlive
: identification
? upperKeepAlive && newKeepAlive
: newKeepAlive;
return activated
? (
<TriggerLifecycleContainer
{...wrapperProps}
keepAlive={this.combinedKeepAlive}
getCombinedKeepAlive={this.getCombinedKeepAlive}
/>
)
: null;
} }
eventEmitter.off([identification, COMMAND.ACTIVATE], this.activate);
eventEmitter.off([identification, COMMAND.UNACTIVATE], this.unactivate);
eventEmitter.off([identification, COMMAND.UNMOUNT], this.unmount);
} }
const ListenUpperKeepAliveContainerHOC: any = withKeepAliveContextConsumer( private getCombinedKeepAlive = () => {
withIdentificationContextConsumer(ListenUpperKeepAliveContainer) return this.combinedKeepAlive;
); }
let KeepAlive: React.ComponentType<IKeepAliveDecorativeComponentProps> = ListenUpperKeepAliveContainerHOC; public render() {
if (forwardRef) { const {
KeepAlive = React.forwardRef((props, ref) => ( _identificationContextProps: {
<ListenUpperKeepAliveContainerHOC identification,
{...props} keepAlive: upperKeepAlive,
forwardedRef={ref} getLifecycle,
/> },
)); disabled,
...wrapperProps
} = this.props;
const {activated} = this.state;
const {
_keepAliveContextProps: {
include,
exclude,
},
} = wrapperProps;
// When the parent KeepAlive component is mounted or unmounted,
// use the keepAlive prop of the parent KeepAlive component.
const propKey = getKeyByFiberNode((this as any)._reactInternalFiber);
if (!propKey) {
warn('[React Keep Alive] <KeepAlive> components must have key.');
return null;
}
const newKeepAlive = getKeepAlive(propKey, include, exclude, !disabled);
this.combinedKeepAlive = getLifecycle === undefined || getLifecycle() === LIFECYCLE.UPDATING
? newKeepAlive
: identification
? upperKeepAlive && newKeepAlive
: newKeepAlive;
return activated
? (
<TriggerLifecycleContainer
{...wrapperProps}
propKey={propKey}
keepAlive={this.combinedKeepAlive}
getCombinedKeepAlive={this.getCombinedKeepAlive}
/>
)
: null;
} }
}
(KeepAlive as any).WrappedComponent = Component; const KeepAlive = withKeepAliveContextConsumer(
KeepAlive.displayName = `${keepAliveDisplayName}(${displayName})`; withIdentificationContextConsumer(ListenUpperKeepAliveContainer)
return hoistNonReactStatics(KeepAlive, Component); ) as any;
};
} return hoistNonReactStatics(KeepAlive, Component);
};
...@@ -11,17 +11,19 @@ export interface IIdentificationContextProps { ...@@ -11,17 +11,19 @@ export interface IIdentificationContextProps {
isExisted: () => boolean; isExisted: () => boolean;
} }
export interface IIdentificationContextComponentProps { export interface IIdentificationContextConsumerComponentProps {
_identificationContextProps: IIdentificationContextProps; _identificationContextProps: IIdentificationContextProps;
} }
export default function withIdentificationContextConsumer<P = any>(Component: React.ComponentType<IIdentificationContextComponentProps & P>) { export const withIdentificationContextConsumerDisplayName = 'withIdentificationContextConsumer';
const NewComponent = (props: P) => (
export default function withIdentificationContextConsumer<P = any>(Component: React.ComponentType<IIdentificationContextConsumerComponentProps & P>) {
const WithIdentificationContextConsumer = (props: P) => (
<IdentificationContext.Consumer> <IdentificationContext.Consumer>
{(contextProps: IIdentificationContextProps) => <Component _identificationContextProps={contextProps} {...props} />} {(contextProps: IIdentificationContextProps) => <Component _identificationContextProps={contextProps} {...props} />}
</IdentificationContext.Consumer> </IdentificationContext.Consumer>
); );
NewComponent.displayName = `withIdentificationContextConsumer(${getDisplayName(Component)})`; WithIdentificationContextConsumer.displayName = `${withIdentificationContextConsumerDisplayName}(${getDisplayName(Component)})`;
return NewComponent; return WithIdentificationContextConsumer;
} }
...@@ -5,17 +5,19 @@ import getDisplayName from './getDisplayName'; ...@@ -5,17 +5,19 @@ import getDisplayName from './getDisplayName';
type IKeepAliveContextProps = IKeepAliveProviderImpl & IKeepAliveProviderProps; type IKeepAliveContextProps = IKeepAliveProviderImpl & IKeepAliveProviderProps;
export interface IKeepAliveContextComponentProps { export interface IKeepAliveContextConsumerComponentProps {
_keepAliveContextProps: IKeepAliveContextProps; _keepAliveContextProps: IKeepAliveContextProps;
} }
export default function withKeepAliveContextConsumer<P = any>(Component: React.ComponentType<IKeepAliveContextComponentProps & P>) { export const WithKeepAliveContextConsumerDisplayName = 'withKeepAliveContextConsumer';
const NewComponent = (props: P) => (
export default function withKeepAliveContextConsumer<P = any>(Component: React.ComponentType<IKeepAliveContextConsumerComponentProps & P>) {
const WithKeepAliveContextConsumer = (props: P) => (
<KeepAliveContext.Consumer> <KeepAliveContext.Consumer>
{(contextProps: IKeepAliveContextProps) => <Component _keepAliveContextProps={contextProps} {...props} />} {(contextProps: IKeepAliveContextProps) => <Component _keepAliveContextProps={contextProps} {...props} />}
</KeepAliveContext.Consumer> </KeepAliveContext.Consumer>
); );
NewComponent.displayName = `withKeepAliveContextConsumer(${getDisplayName(Component)})`; WithKeepAliveContextConsumer.displayName = `${WithKeepAliveContextConsumerDisplayName}(${getDisplayName(Component)})`;
return NewComponent; return WithKeepAliveContextConsumer;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment