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,53 +21,31 @@ export enum COMMAND { ...@@ -22,53 +21,31 @@ 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;
keepAlive: boolean;
ref: React.Ref<{}>;
}
export default function keepAliveDecorator({
name,
forwardRef = false,
}: IOptions = {}) {
return (Component: React.ComponentType<IComponentProps>) => {
const { const {
componentDidMount = noop, componentDidMount = noop,
componentDidUpdate = noop, componentDidUpdate = noop,
componentWillUnactivate = noop, componentWillUnactivate = noop,
componentWillUnmount = noop, componentWillUnmount = noop,
} = Component.prototype; } = Component.prototype;
const displayName = (name || getDisplayName(Component)) as any;
if (!displayName) {
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.');
}
Component.prototype.componentDidMount = function () { Component.prototype.componentDidMount = function () {
const { const {
...@@ -265,19 +242,25 @@ export default function keepAliveDecorator({ ...@@ -265,19 +242,25 @@ export default function keepAliveDecorator({
} }
public render() { public render() {
if (!this.identification) {
// We need to generate a corresponding unique identifier based on the information of the component.
const { const {
providerIdentification, propKey,
keepAlive,
getCombinedKeepAlive,
_keepAliveContextProps: {
isExisted,
storeElement,
cache, cache,
} = this.props._keepAliveContextProps; eventEmitter,
const { setCache,
paths, unactivate,
globalKey, providerIdentification,
typeNames, },
} = getContextIdentificationByFiberNode((this as any)._reactInternalFiber); ...wrapperProps
} = this.props;
if (!this.identification) {
// We need to generate a corresponding unique identifier based on the information of the component.
this.identification = md5( this.identification = md5(
`${providerIdentification}${displayName}${globalKey ? `${globalKey}${typeNames}` : paths}`, `${providerIdentification}${propKey}`,
); );
// The last activated component must be unactivated before it can be activated again. // The last activated component must be unactivated before it can be activated again.
const currentCache = cache[this.identification]; const currentCache = cache[this.identification];
...@@ -296,20 +279,6 @@ export default function keepAliveDecorator({ ...@@ -296,20 +279,6 @@ export default function keepAliveDecorator({
identification, identification,
ifStillActivate, ifStillActivate,
} = this; } = this;
const {
keepAlive,
forwardedRef,
getCombinedKeepAlive,
_keepAliveContextProps: {
isExisted,
storeElement,
cache,
eventEmitter,
setCache,
unactivate,
},
...wrapperProps
} = this.props;
return !ifStillActivate return !ifStillActivate
? ( ? (
<Consumer <Consumer
...@@ -332,7 +301,6 @@ export default function keepAliveDecorator({ ...@@ -332,7 +301,6 @@ export default function keepAliveDecorator({
<Component <Component
{...wrapperProps} {...wrapperProps}
keepAlive={keepAlive} keepAlive={keepAlive}
ref={forwardedRef as React.Ref<{}>}
_container={{ _container={{
isNeedActivate, isNeedActivate,
notNeedActivate, notNeedActivate,
...@@ -440,7 +408,7 @@ export default function keepAliveDecorator({ ...@@ -440,7 +408,7 @@ export default function keepAliveDecorator({
keepAlive: upperKeepAlive, keepAlive: upperKeepAlive,
getLifecycle, getLifecycle,
}, },
keepAlive, disabled,
...wrapperProps ...wrapperProps
} = this.props; } = this.props;
const {activated} = this.state; const {activated} = this.state;
...@@ -452,7 +420,12 @@ export default function keepAliveDecorator({ ...@@ -452,7 +420,12 @@ export default function keepAliveDecorator({
} = wrapperProps; } = wrapperProps;
// When the parent KeepAlive component is mounted or unmounted, // When the parent KeepAlive component is mounted or unmounted,
// use the keepAlive prop of the parent KeepAlive component. // use the keepAlive prop of the parent KeepAlive component.
const newKeepAlive = getKeepAlive(displayName, include, exclude, keepAlive); 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 this.combinedKeepAlive = getLifecycle === undefined || getLifecycle() === LIFECYCLE.UPDATING
? newKeepAlive ? newKeepAlive
: identification : identification
...@@ -462,6 +435,7 @@ export default function keepAliveDecorator({ ...@@ -462,6 +435,7 @@ export default function keepAliveDecorator({
? ( ? (
<TriggerLifecycleContainer <TriggerLifecycleContainer
{...wrapperProps} {...wrapperProps}
propKey={propKey}
keepAlive={this.combinedKeepAlive} keepAlive={this.combinedKeepAlive}
getCombinedKeepAlive={this.getCombinedKeepAlive} getCombinedKeepAlive={this.getCombinedKeepAlive}
/> />
...@@ -470,22 +444,9 @@ export default function keepAliveDecorator({ ...@@ -470,22 +444,9 @@ export default function keepAliveDecorator({
} }
} }
const ListenUpperKeepAliveContainerHOC: any = withKeepAliveContextConsumer( const KeepAlive = withKeepAliveContextConsumer(
withIdentificationContextConsumer(ListenUpperKeepAliveContainer) withIdentificationContextConsumer(ListenUpperKeepAliveContainer)
); ) as any;
let KeepAlive: React.ComponentType<IKeepAliveDecorativeComponentProps> = ListenUpperKeepAliveContainerHOC;
if (forwardRef) {
KeepAlive = React.forwardRef((props, ref) => (
<ListenUpperKeepAliveContainerHOC
{...props}
forwardedRef={ref}
/>
));
}
(KeepAlive as any).WrappedComponent = Component;
KeepAlive.displayName = `${keepAliveDisplayName}(${displayName})`;
return hoistNonReactStatics(KeepAlive, Component); 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