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 {
Link,
BrowserRouter as Router,
} from 'react-router-dom';
import {Provider} from '../../es';
import {Provider, KeepAlive} from '../../es';
import A from './views/A';
import B from './views/B';
import C from './views/C';
......@@ -58,18 +58,24 @@ class App extends React.Component {
<Route
path="/a"
render={() => (
<React.Fragment>
<A keepAlive={toggle} />
</React.Fragment>
<KeepAlive key="A" disabled={!toggle}>
<A />
</KeepAlive>
)}
/>
<Route
path="/b"
render={() => <B />}
render={() => (
<B />
)}
/>
<Route
path="/c"
render={() => <C />}
render={() => (
<KeepAlive key="C">
<C />
</KeepAlive>
)}
/>
</Switch>
</div>
......
import React from 'react';
import {keepAlive, bindLifecycle} from '../../../es';
import {bindLifecycle} from '../../../es';
@bindLifecycle
class Content extends React.Component {
......@@ -40,7 +40,7 @@ class Content extends React.Component {
}
}
@keepAlive()
@bindLifecycle
class Test extends React.Component {
state = {
index: 0,
......
import React from 'react';
import {keepAlive, bindLifecycle} from '../../../es';
import {bindLifecycle} from '../../../es';
@keepAlive()
@bindLifecycle
class B extends React.Component {
componentWillMount() {
console.log('B componentWillMount');
......
import React from 'react';
import {keepAlive} from '../../../es';
import {bindLifecycle} from '../../../es';
@bindLifecycle
class C extends React.Component {
componentWillMount() {
console.log('C componentWillMount');
......@@ -38,4 +39,4 @@ class C extends React.Component {
}
}
export default keepAlive()(C);
export default C;
{
"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.",
"author": "Shen Chang",
"homepage": "https://github.com/Sam618/react-keep-alive",
......
......@@ -3,7 +3,6 @@ import Comment from './Comment';
import {LIFECYCLE, ICache, ICacheItem} from './Provider';
import {warn} from '../utils/debug';
import findDOMNodeByFiberNode from '../utils/findDOMNodeByFiberNode';
import createUniqueIdentification from '../utils/createUniqueIdentification';
interface IConsumerProps {
children: React.ReactNode;
......@@ -19,9 +18,6 @@ class Consumer extends React.PureComponent<IConsumerProps> {
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) {
super(props, ...args);
const {cache, setCache, children} = props;
......@@ -43,7 +39,6 @@ class Consumer extends React.PureComponent<IConsumerProps> {
children,
keepAlive,
lifecycle: LIFECYCLE.MOUNTED,
key: this.key,
renderElement: this.renderElement,
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';
import createUniqueIdentification from '../utils/createUniqueIdentification';
import createStoreElement from '../utils/createStoreElement';
export const keepAliveProviderTypeName = 'KeepAliveProvider';
export const keepAliveProviderTypeName = '$$KeepAliveProvider';
export const START_MOUNTING_DOM = 'startMountingDOM';
export enum LIFECYCLE {
......@@ -20,7 +20,6 @@ export interface ICacheItem {
children: React.ReactNode;
keepAlive: boolean;
lifecycle: LIFECYCLE;
key?: string | null;
renderElement?: HTMLElement;
activated?: boolean;
ifStillActivate?: boolean;
......@@ -87,10 +86,6 @@ export default class KeepAliveProvider extends React.PureComponent<IKeepAlivePro
public setCache = (identification: string, value: ICacheItem) => {
const {cache, keys} = this;
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) {
keys.push(identification);
}
......@@ -101,6 +96,10 @@ export default class KeepAliveProvider extends React.PureComponent<IKeepAlivePro
this.forceUpdate();
}
public componentDidCatch() {
warn('[React Keep Alive] Cached components have duplicates. Please check the <KeepAlive> component of the key duplication!');
}
// private getMax = () => {
// return this.props.max ? parseInt(this.props.max) : null;
// }
......@@ -126,7 +125,6 @@ export default class KeepAliveProvider extends React.PureComponent<IKeepAlivePro
const {cache} = this;
this.cache[identification] = {
...cache[identification],
key: null,
activated: false,
lifecycle: LIFECYCLE.UNMOUNTED,
};
......
import Provider from './components/Provider';
import keepAlive from './utils/keepAlive';
import KeepAlive from './components/KeepAlive';
import bindLifecycle from './utils/bindLifecycle';
export {
Provider,
keepAlive,
KeepAlive,
bindLifecycle,
};
......@@ -5,7 +5,7 @@ import {COMMAND} from './keepAlive';
import withIdentificationContextConsumer from './withIdentificationContextConsumer';
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 {
......@@ -111,7 +111,7 @@ export default function bindLifecycle<P = any>(Component: React.ComponentType<P>
: null
),
);
const BindLifecycle = React.forwardRef((props, ref) => (
const BindLifecycle = React.forwardRef((props: P, 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';
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';
import md5 from './md5';
import noop from './noop';
import {warn} from './debug';
import getContextIdentificationByFiberNode from './getContextIdentificationByFiberNode';
import withIdentificationContextConsumer, {IIdentificationContextComponentProps} from './withIdentificationContextConsumer';
import withKeepAliveContextConsumer, {IKeepAliveContextComponentProps} from './withKeepAliveContextConsumer';
import getKeyByFiberNode from './getKeyByFiberNode';
import withIdentificationContextConsumer, {IIdentificationContextConsumerComponentProps} from './withIdentificationContextConsumer';
import withKeepAliveContextConsumer, {IKeepAliveContextConsumerComponentProps} from './withKeepAliveContextConsumer';
import changePositionByComment from './changePositionByComment';
import shallowEqual from './shallowEqual';
import getDisplayName from './getDisplayName';
import getKeepAlive from './getKeepAlive';
export enum COMMAND {
......@@ -22,53 +21,31 @@ export enum COMMAND {
CURRENT_UNACTIVATE = 'current_unactivate',
}
export interface IOptions {
name?: string;
forwardRef?: boolean;
}
export const keepAliveDisplayName = 'keepAlive';
export const keepAliveDisplayName = '$$KeepAlive';
interface IKeepAliveDecorativeComponentProps {
keepAlive?: boolean;
export interface IKeepAliveComponentProps {
disabled?: boolean;
}
interface IListenUpperKeepAliveContainerProps extends IKeepAliveDecorativeComponentProps, IIdentificationContextComponentProps, IKeepAliveContextComponentProps {
forwardedRef?: React.Ref<{}>;
}
interface IListenUpperKeepAliveContainerProps extends IKeepAliveComponentProps, IIdentificationContextConsumerComponentProps, IKeepAliveContextConsumerComponentProps {}
interface IListenUpperKeepAliveContainerState {
activated: boolean;
}
interface ITriggerLifecycleContainerProps extends IKeepAliveContextComponentProps {
forwardedRef?: React.Ref<{}>;
interface ITriggerLifecycleContainerProps extends IKeepAliveContextConsumerComponentProps {
propKey: string;
keepAlive: boolean;
getCombinedKeepAlive: () => boolean;
}
interface IComponentProps {
_container: object;
keepAlive: boolean;
ref: React.Ref<{}>;
}
export default function keepAliveDecorator({
name,
forwardRef = false,
}: IOptions = {}) {
return (Component: React.ComponentType<IComponentProps>) => {
export default (Component: React.ComponentType<any>) => {
const {
componentDidMount = noop,
componentDidUpdate = noop,
componentWillUnactivate = noop,
componentWillUnmount = noop,
} = 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 () {
const {
......@@ -265,19 +242,25 @@ export default function keepAliveDecorator({
}
public render() {
if (!this.identification) {
// We need to generate a corresponding unique identifier based on the information of the component.
const {
providerIdentification,
propKey,
keepAlive,
getCombinedKeepAlive,
_keepAliveContextProps: {
isExisted,
storeElement,
cache,
} = this.props._keepAliveContextProps;
const {
paths,
globalKey,
typeNames,
} = getContextIdentificationByFiberNode((this as any)._reactInternalFiber);
eventEmitter,
setCache,
unactivate,
providerIdentification,
},
...wrapperProps
} = this.props;
if (!this.identification) {
// We need to generate a corresponding unique identifier based on the information of the component.
this.identification = md5(
`${providerIdentification}${displayName}${globalKey ? `${globalKey}${typeNames}` : paths}`,
`${providerIdentification}${propKey}`,
);
// The last activated component must be unactivated before it can be activated again.
const currentCache = cache[this.identification];
......@@ -296,20 +279,6 @@ export default function keepAliveDecorator({
identification,
ifStillActivate,
} = this;
const {
keepAlive,
forwardedRef,
getCombinedKeepAlive,
_keepAliveContextProps: {
isExisted,
storeElement,
cache,
eventEmitter,
setCache,
unactivate,
},
...wrapperProps
} = this.props;
return !ifStillActivate
? (
<Consumer
......@@ -332,7 +301,6 @@ export default function keepAliveDecorator({
<Component
{...wrapperProps}
keepAlive={keepAlive}
ref={forwardedRef as React.Ref<{}>}
_container={{
isNeedActivate,
notNeedActivate,
......@@ -440,7 +408,7 @@ export default function keepAliveDecorator({
keepAlive: upperKeepAlive,
getLifecycle,
},
keepAlive,
disabled,
...wrapperProps
} = this.props;
const {activated} = this.state;
......@@ -452,7 +420,12 @@ export default function keepAliveDecorator({
} = 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);
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
......@@ -462,6 +435,7 @@ export default function keepAliveDecorator({
? (
<TriggerLifecycleContainer
{...wrapperProps}
propKey={propKey}
keepAlive={this.combinedKeepAlive}
getCombinedKeepAlive={this.getCombinedKeepAlive}
/>
......@@ -470,22 +444,9 @@ export default function keepAliveDecorator({
}
}
const ListenUpperKeepAliveContainerHOC: any = withKeepAliveContextConsumer(
const KeepAlive = withKeepAliveContextConsumer(
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);
};
}
};
......@@ -11,17 +11,19 @@ export interface IIdentificationContextProps {
isExisted: () => boolean;
}
export interface IIdentificationContextComponentProps {
export interface IIdentificationContextConsumerComponentProps {
_identificationContextProps: IIdentificationContextProps;
}
export default function withIdentificationContextConsumer<P = any>(Component: React.ComponentType<IIdentificationContextComponentProps & P>) {
const NewComponent = (props: P) => (
export const withIdentificationContextConsumerDisplayName = 'withIdentificationContextConsumer';
export default function withIdentificationContextConsumer<P = any>(Component: React.ComponentType<IIdentificationContextConsumerComponentProps & P>) {
const WithIdentificationContextConsumer = (props: P) => (
<IdentificationContext.Consumer>
{(contextProps: IIdentificationContextProps) => <Component _identificationContextProps={contextProps} {...props} />}
</IdentificationContext.Consumer>
);
NewComponent.displayName = `withIdentificationContextConsumer(${getDisplayName(Component)})`;
return NewComponent;
WithIdentificationContextConsumer.displayName = `${withIdentificationContextConsumerDisplayName}(${getDisplayName(Component)})`;
return WithIdentificationContextConsumer;
}
......@@ -5,17 +5,19 @@ import getDisplayName from './getDisplayName';
type IKeepAliveContextProps = IKeepAliveProviderImpl & IKeepAliveProviderProps;
export interface IKeepAliveContextComponentProps {
export interface IKeepAliveContextConsumerComponentProps {
_keepAliveContextProps: IKeepAliveContextProps;
}
export default function withKeepAliveContextConsumer<P = any>(Component: React.ComponentType<IKeepAliveContextComponentProps & P>) {
const NewComponent = (props: P) => (
export const WithKeepAliveContextConsumerDisplayName = 'withKeepAliveContextConsumer';
export default function withKeepAliveContextConsumer<P = any>(Component: React.ComponentType<IKeepAliveContextConsumerComponentProps & P>) {
const WithKeepAliveContextConsumer = (props: P) => (
<KeepAliveContext.Consumer>
{(contextProps: IKeepAliveContextProps) => <Component _keepAliveContextProps={contextProps} {...props} />}
</KeepAliveContext.Consumer>
);
NewComponent.displayName = `withKeepAliveContextConsumer(${getDisplayName(Component)})`;
return NewComponent;
WithKeepAliveContextConsumer.displayName = `${WithKeepAliveContextConsumerDisplayName}(${getDisplayName(Component)})`;
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