Commit a6c65439 authored by Shen Chang's avatar Shen Chang

refactor(KeepAlive): Asynchronous mount component

parent 1ed0cf2f
...@@ -247,9 +247,14 @@ ReactDOM.render( ...@@ -247,9 +247,14 @@ ReactDOM.render(
**Note**: If you want to use the **lifecycle**, wrap the components in a `bindLifecycle` high-level component. **Note**: If you want to use the **lifecycle**, wrap the components in a `bindLifecycle` high-level component.
### `bindLifecycle` ### `bindLifecycle`
Components that pass this high-level component wrap will have the **correct** lifecycle, entering the component must trigger the `componentDidMount` lifecycle, and leaving will also trigger the `componentWillUnmount` lifecycle. Refer to this [example] (https://codesandbox.io/s/q1xprn1qq) for a better understanding, pay attention to open the console. Components that pass this high-level component wrap will have the **correct** lifecycle, and we have added two additional lifecycles, `componentDidActivate` and `componentWillUnactivate`.
The old version of ~~`componentDidActivate`~~ and ~~`componentWillUnactivate`~~ has been deleted, this is a component that is inevitably unaccustomed to the new life cycle, and was originally written with reference to Vue, but it is not entirely suitable for React. Lifecycle after adding:
![Lifecycle after adding](https://github.com/Sam618/react-keep-alive/raw/master/assets/lifecycle.png)
`componentDidActivate` will be executed once after the initial mount or from the unactivated state to the active state. although we see `componentDidActivate` after `componentDidUpdate` in the `Updating` phase, this does not mean `componentDidActivate` Always triggered.
At the same time, only one of the lifecycles of `componentWillUnactivate` and `componentWillUnmount` is triggered. `componentWillUnactivate` is executed when caching is required; `componentWillUnmount` is executed without caching.
#### Example #### Example
```JavaScript ```JavaScript
......
...@@ -250,9 +250,14 @@ ReactDOM.render( ...@@ -250,9 +250,14 @@ ReactDOM.render(
**注意**:如果要使用 **生命周期**,请将组件包装在 `bindLifecycle` 高阶组件中。 **注意**:如果要使用 **生命周期**,请将组件包装在 `bindLifecycle` 高阶组件中。
### `bindLifecycle` ### `bindLifecycle`
这个高阶组件包装的组件将具有 **正确的** 的生命周期,进入组件必定会触发 `componentDidMount` 生命周期,离开也必定会触发 `componentWillUnmount` 生命周期。参考这个 [例子](https://codesandbox.io/s/q1xprn1qq) 能够更好的理解,注意打开控制台 这个高阶组件包装的组件将具有 **正确的** 的生命周期,并且我们添加了两个额外的生命周期 `componentDidActivate``componentWillUnactivate`
旧版本新增的 ~~`componentDidActivate`~~ 和 ~~`componentWillUnactivate`~~ 生命周期已经删除,这是考虑到了新增生命周期难免会不习惯,并且原来是参照 Vue 来写的组件,但其实并不完全适合 React。 添加新的生命周期之后:
![Lifecycle after adding](https://github.com/Sam618/react-keep-alive/raw/master/assets/lifecycle.png)
`componentDidActivate` 将在组件刚挂载或从未激活状态变为激活状态时执行。虽然我们在 `Updating` 阶段的 `componentDidUpdate` 之后能够看到 `componentDidActivate`,但这并不意味着 `componentDidActivate` 总是被触发。
同时只能触发 `componentWillUnactivate``componentWillUnmount` 生命周期的其中之一。当需要缓存时执行 `componentWillUnactivate`,而 `componentWillUnmount` 在禁用缓存的情况下执行。
#### 例子 #### 例子
```JavaScript ```JavaScript
......
import React, {useState, useEffect} from 'react'; import React, {useState, useEffect, useRef} from 'react';
import {useKeepAliveEffect} from '../../../es'; import {useKeepAliveEffect} from '../../../es';
import B from './B';
function Test() { function Test() {
const [index, setIndex] = useState(0); const [index, setIndex] = useState(0);
const divRef = useRef();
useKeepAliveEffect(() => { useKeepAliveEffect(() => {
console.log('activated', index); console.log('activated', index);
console.log(divRef.current.offsetWidth);
const i = 0; const i = 0;
return () => { return () => {
...@@ -13,7 +16,8 @@ function Test() { ...@@ -13,7 +16,8 @@ function Test() {
}); });
return ( return (
<div> <div>
<div>This is a.</div> <div ref={divRef}>This is a.</div>
<B />
<button onClick={() => setIndex(index + 1)}>click me({index})</button> <button onClick={() => setIndex(index + 1)}>click me({index})</button>
</div> </div>
); );
......
...@@ -8,6 +8,7 @@ class B extends React.Component { ...@@ -8,6 +8,7 @@ class B extends React.Component {
} }
componentDidMount() { componentDidMount() {
console.log(this.ref.offsetWidth);
console.log('B componentDidMount'); console.log('B componentDidMount');
} }
...@@ -20,6 +21,7 @@ class B extends React.Component { ...@@ -20,6 +21,7 @@ class B extends React.Component {
} }
componentDidUpdate() { componentDidUpdate() {
console.log(this.ref.offsetWidth);
console.log('B componentDidUpdate'); console.log('B componentDidUpdate');
} }
...@@ -34,7 +36,7 @@ class B extends React.Component { ...@@ -34,7 +36,7 @@ class B extends React.Component {
render() { render() {
console.log('B render'); console.log('B render');
return ( return (
<div>This is b.</div> <div ref={ref => this.ref = ref}>This is b.</div>
); );
} }
} }
......
...@@ -3,11 +3,20 @@ import {bindLifecycle} from '../../../es'; ...@@ -3,11 +3,20 @@ import {bindLifecycle} from '../../../es';
@bindLifecycle @bindLifecycle
class C extends React.Component { class C extends React.Component {
state = {
value: false,
};
componentWillMount() { componentWillMount() {
console.log('C componentWillMount'); console.log('C componentWillMount');
} }
componentDidMount() { componentDidMount() {
setTimeout(() => {
this.setState({
value: true,
});
}, 1000);
console.log('C componentDidMount'); console.log('C componentDidMount');
} }
...@@ -34,7 +43,9 @@ class C extends React.Component { ...@@ -34,7 +43,9 @@ class C extends React.Component {
render() { render() {
console.log('C render'); console.log('C render');
return ( return (
<div>This is c.</div> <div>
{this.state.value ? <div>This is c.</div> : null}
</div>
); );
} }
} }
......
{ {
"name": "react-keep-alive", "name": "react-keep-alive",
"version": "0.4.3", "version": "1.2.2",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
...@@ -7413,6 +7413,11 @@ ...@@ -7413,6 +7413,11 @@
} }
} }
}, },
"react-deep-force-update": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/react-deep-force-update/-/react-deep-force-update-2.1.3.tgz",
"integrity": "sha512-lqD4eHKVuB65RyO/hGbEST53E2/GPbcIPcFYyeW/p4vNngtH4G7jnKGlU6u1OqrFo0uNfIvwuBOg98IbLHlNEA=="
},
"react-dom": { "react-dom": {
"version": "16.8.5", "version": "16.8.5",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.5.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.5.tgz",
......
{ {
"name": "react-keep-alive", "name": "react-keep-alive",
"version": "1.2.2", "version": "2.0.0.alpha.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",
...@@ -37,7 +37,8 @@ ...@@ -37,7 +37,8 @@
"dependencies": { "dependencies": {
"@types/js-md5": "^0.4.2", "@types/js-md5": "^0.4.2",
"hoist-non-react-statics": "^3.3.0", "hoist-non-react-statics": "^3.3.0",
"js-md5": "^0.7.3" "js-md5": "^0.7.3",
"react-deep-force-update": "^2.1.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.2.3", "@babel/cli": "^7.2.3",
......
import * as React from 'react';
import deepForceUpdate from 'react-deep-force-update';
interface IProps {
setMounted: any;
getMounted: any;
correctionPosition: any;
}
interface IState {
component: any;
}
export default class AsyncComponent extends React.Component<IProps, IState> {
public state = {
component: null,
};
public componentDidMount() {
const {children} = this.props;
Promise.resolve().then(() => this.setState({component: children}));
}
public componentDidUpdate() {
this.props.correctionPosition();
}
// Delayed update
// In order to be able to get real DOM data
public shouldComponentUpdate() {
if (!this.state.component) {
// If it is already mounted asynchronously, you don't need to do it again when you update it.
this.props.setMounted(false);
return true;
}
Promise.resolve().then(() => {
if (this.props.getMounted()) {
this.props.setMounted(false);
deepForceUpdate(this);
}
});
return false;
}
public render() {
return this.state.component;
}
}
import React from 'react'; import React from 'react';
import AsyncComponent from './AsyncComponent';
import {START_MOUNTING_DOM, LIFECYCLE} from './Provider'; import {START_MOUNTING_DOM, LIFECYCLE} from './Provider';
import keepAlive, {COMMAND} from '../utils/keepAliveDecorator'; import keepAlive, {COMMAND} from '../utils/keepAliveDecorator';
import changePositionByComment from '../utils/changePositionByComment'; import changePositionByComment from '../utils/changePositionByComment';
...@@ -16,6 +17,18 @@ interface IKeepAliveInnerProps extends IKeepAliveProps { ...@@ -16,6 +17,18 @@ interface IKeepAliveInnerProps extends IKeepAliveProps {
class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> { class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> {
private bindUnmount: (() => void) | null = null; private bindUnmount: (() => void) | null = null;
private bindUnactivate: (() => void) | null = null;
private unmounted = false;
private mounted = false;
private ref: null | Element = null;
private refNextSibling: null | ChildNode = null;
private childNodes: ChildNode[] = [];
public componentDidMount() { public componentDidMount() {
const { const {
_container, _container,
...@@ -24,6 +37,7 @@ class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> { ...@@ -24,6 +37,7 @@ class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> {
notNeedActivate, notNeedActivate,
identification, identification,
eventEmitter, eventEmitter,
keepAlive,
} = _container; } = _container;
notNeedActivate(); notNeedActivate();
const cb = () => { const cb = () => {
...@@ -32,6 +46,13 @@ class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> { ...@@ -32,6 +46,13 @@ class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> {
eventEmitter.off([identification, START_MOUNTING_DOM], cb); eventEmitter.off([identification, START_MOUNTING_DOM], cb);
}; };
eventEmitter.on([identification, START_MOUNTING_DOM], cb); eventEmitter.on([identification, START_MOUNTING_DOM], cb);
if (keepAlive) {
this.componentDidActivate();
}
}
public componentDidActivate() {
// tslint-disable
} }
public componentDidUpdate() { public componentDidUpdate() {
...@@ -46,13 +67,23 @@ class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> { ...@@ -46,13 +67,23 @@ class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> {
notNeedActivate(); notNeedActivate();
this.mount(); this.mount();
this.listen(); this.listen();
this.unmounted = false;
this.componentDidActivate();
} }
} }
public componentWillUnactivate() {
this.unmount();
this.unlisten();
}
public componentWillUnmount() { public componentWillUnmount() {
if (!this.unmounted) {
this.unmounted = true;
this.unmount(); this.unmount();
this.unlisten(); this.unlisten();
} }
}
private mount() { private mount() {
const { const {
...@@ -63,11 +94,33 @@ class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> { ...@@ -63,11 +94,33 @@ class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> {
setLifecycle, setLifecycle,
}, },
} = this.props; } = this.props;
this.setMounted(true);
const {renderElement} = cache[identification]; const {renderElement} = cache[identification];
setLifecycle(LIFECYCLE.UPDATING); setLifecycle(LIFECYCLE.UPDATING);
changePositionByComment(identification, renderElement, storeElement); changePositionByComment(identification, renderElement, storeElement);
} }
private correctionPosition = () => {
if (this.ref && this.ref.parentNode && this.ref.nextSibling) {
const childNodes = this.ref.childNodes as any;
this.refNextSibling = this.ref.nextSibling;
for (const child of childNodes) {
this.childNodes.push(child);
this.ref.parentNode.insertBefore(child, this.ref.nextSibling);
}
this.ref.parentNode.removeChild(this.ref);
}
}
private retreatPosition = () => {
if (this.ref && this.refNextSibling && this.refNextSibling.parentNode) {
for (const child of this.childNodes) {
this.ref.appendChild(child);
}
this.refNextSibling.parentNode.insertBefore(this.ref, this.refNextSibling);
}
}
private unmount() { private unmount() {
const { const {
_container: { _container: {
...@@ -79,6 +132,7 @@ class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> { ...@@ -79,6 +132,7 @@ class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> {
} = this.props; } = this.props;
const {renderElement, ifStillActivate, reactivate} = cache[identification]; const {renderElement, ifStillActivate, reactivate} = cache[identification];
setLifecycle(LIFECYCLE.UNMOUNTED); setLifecycle(LIFECYCLE.UNMOUNTED);
this.retreatPosition();
changePositionByComment(identification, storeElement, renderElement); changePositionByComment(identification, storeElement, renderElement);
if (ifStillActivate) { if (ifStillActivate) {
reactivate(); reactivate();
...@@ -96,6 +150,10 @@ class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> { ...@@ -96,6 +150,10 @@ class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> {
[identification, COMMAND.CURRENT_UNMOUNT], [identification, COMMAND.CURRENT_UNMOUNT],
this.bindUnmount = this.componentWillUnmount.bind(this), this.bindUnmount = this.componentWillUnmount.bind(this),
); );
eventEmitter.on(
[identification, COMMAND.CURRENT_UNACTIVATE],
this.bindUnactivate = this.componentWillUnactivate.bind(this),
);
} }
private unlisten() { private unlisten() {
...@@ -106,10 +164,31 @@ class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> { ...@@ -106,10 +164,31 @@ class KeepAlive extends React.PureComponent<IKeepAliveInnerProps> {
}, },
} = this.props; } = this.props;
eventEmitter.off([identification, COMMAND.CURRENT_UNMOUNT], this.bindUnmount); eventEmitter.off([identification, COMMAND.CURRENT_UNMOUNT], this.bindUnmount);
eventEmitter.off([identification, COMMAND.CURRENT_UNACTIVATE], this.bindUnactivate);
}
private setMounted = (value: boolean) => {
this.mounted = value;
}
private getMounted = () => {
return this.mounted;
} }
public render() { public render() {
return this.props.children; // The purpose of this div is to not report an error when moving the DOM,
// so you need to remove this div later.
return (
<div ref={ref => this.ref = ref}>
<AsyncComponent
setMounted={this.setMounted}
getMounted={this.getMounted}
correctionPosition={this.correctionPosition}
>
{this.props.children}
</AsyncComponent>
</div>
);
} }
} }
......
...@@ -12,7 +12,10 @@ export default function bindLifecycle<P = any>(Component: React.ComponentClass<P ...@@ -12,7 +12,10 @@ export default function bindLifecycle<P = any>(Component: React.ComponentClass<P
const { const {
componentDidMount = noop, componentDidMount = noop,
componentDidUpdate = noop, componentDidUpdate = noop,
componentDidActivate = noop,
componentWillUnactivate = noop,
componentWillUnmount = noop, componentWillUnmount = noop,
shouldComponentUpdate = noop,
} = WrappedComponent.prototype; } = WrappedComponent.prototype;
WrappedComponent.prototype.componentDidMount = function () { WrappedComponent.prototype.componentDidMount = function () {
...@@ -22,12 +25,25 @@ export default function bindLifecycle<P = any>(Component: React.ComponentClass<P ...@@ -22,12 +25,25 @@ export default function bindLifecycle<P = any>(Component: React.ComponentClass<P
_container: { _container: {
identification, identification,
eventEmitter, eventEmitter,
activated,
}, },
keepAlive,
} = this.props; } = this.props;
this._unmounted = false; // Determine whether to execute the componentDidActivate life cycle of the current component based on the activation state of the KeepAlive components
if (!activated && keepAlive !== false) {
componentDidActivate.call(this);
}
eventEmitter.on(
[identification, COMMAND.ACTIVATE],
this._bindActivate = () => this._needActivate = true,
true,
);
eventEmitter.on( eventEmitter.on(
[identification, COMMAND.MOUNT], [identification, COMMAND.UNACTIVATE],
this._bindMount = () => this._needActivate = true, this._bindUnactivate = () => {
componentWillUnactivate.call(this);
this._unmounted = false;
},
true, true,
); );
eventEmitter.on( eventEmitter.on(
...@@ -39,13 +55,20 @@ export default function bindLifecycle<P = any>(Component: React.ComponentClass<P ...@@ -39,13 +55,20 @@ export default function bindLifecycle<P = any>(Component: React.ComponentClass<P
true, true,
); );
}; };
WrappedComponent.prototype.componentDidUpdate = function (...args: any) {
// In order to be able to re-update after transferring the DOM, we need to block the first update.
WrappedComponent.prototype.shouldComponentUpdate = function (...args: any) {
if (this._needActivate) {
return false;
}
return shouldComponentUpdate.call(this, ...args) || true;
};
WrappedComponent.prototype.componentDidUpdate = function () {
componentDidUpdate.call(this);
if (this._needActivate) { if (this._needActivate) {
this._needActivate = false; this._needActivate = false;
this._unmounted = false; componentDidActivate.call(this);
componentDidMount.call(this);
} else {
componentDidUpdate.apply(this, args);
} }
}; };
WrappedComponent.prototype.componentWillUnmount = function () { WrappedComponent.prototype.componentWillUnmount = function () {
...@@ -59,8 +82,12 @@ export default function bindLifecycle<P = any>(Component: React.ComponentClass<P ...@@ -59,8 +82,12 @@ export default function bindLifecycle<P = any>(Component: React.ComponentClass<P
}, },
} = this.props; } = this.props;
eventEmitter.off( eventEmitter.off(
[identification, COMMAND.MOUNT], [identification, COMMAND.ACTIVATE],
this._bindMount, this._bindActivate,
);
eventEmitter.off(
[identification, COMMAND.UNACTIVATE],
this._bindUnactivate,
); );
eventEmitter.off( eventEmitter.off(
[identification, COMMAND.UNMOUNT], [identification, COMMAND.UNMOUNT],
...@@ -74,6 +101,8 @@ export default function bindLifecycle<P = any>(Component: React.ComponentClass<P ...@@ -74,6 +101,8 @@ export default function bindLifecycle<P = any>(Component: React.ComponentClass<P
_identificationContextProps: { _identificationContextProps: {
identification, identification,
eventEmitter, eventEmitter,
activated,
keepAlive,
}, },
...wrapperProps ...wrapperProps
}) => { }) => {
...@@ -88,6 +117,8 @@ export default function bindLifecycle<P = any>(Component: React.ComponentClass<P ...@@ -88,6 +117,8 @@ export default function bindLifecycle<P = any>(Component: React.ComponentClass<P
_container={{ _container={{
identification, identification,
eventEmitter, eventEmitter,
activated,
keepAlive,
}} }}
/> />
); );
......
...@@ -3,12 +3,7 @@ import {prefix} from './createUniqueIdentification'; ...@@ -3,12 +3,7 @@ import {prefix} from './createUniqueIdentification';
export default function createStoreElement(): HTMLElement { export default function createStoreElement(): HTMLElement {
const keepAliveDOM = document.createElement('div'); const keepAliveDOM = document.createElement('div');
keepAliveDOM.dataset.type = prefix; keepAliveDOM.dataset.type = prefix;
keepAliveDOM.style.visibility = 'hidden'; keepAliveDOM.style.display = 'none';
keepAliveDOM.style.opacity = '0';
keepAliveDOM.style.position = 'absolute';
keepAliveDOM.style.top = '0';
keepAliveDOM.style.left = '0';
keepAliveDOM.style.zIndex = '-1';
document.body.appendChild(keepAliveDOM); document.body.appendChild(keepAliveDOM);
return keepAliveDOM; return keepAliveDOM;
} }
...@@ -12,9 +12,11 @@ import shallowEqual from './shallowEqual'; ...@@ -12,9 +12,11 @@ import shallowEqual from './shallowEqual';
import getKeepAlive from './getKeepAlive'; import getKeepAlive from './getKeepAlive';
export enum COMMAND { export enum COMMAND {
UNACTIVATE = 'unactivate',
UNMOUNT = 'unmount', UNMOUNT = 'unmount',
MOUNT = 'mount', ACTIVATE = 'activate',
CURRENT_UNMOUNT = 'current_unmount', CURRENT_UNMOUNT = 'current_unmount',
CURRENT_UNACTIVATE = 'current_unactivate',
} }
interface IListenUpperKeepAliveContainerProps extends IIdentificationContextConsumerComponentProps, IKeepAliveContextConsumerComponentProps { interface IListenUpperKeepAliveContainerProps extends IIdentificationContextConsumerComponentProps, IKeepAliveContextConsumerComponentProps {
...@@ -29,6 +31,7 @@ interface IListenUpperKeepAliveContainerState { ...@@ -29,6 +31,7 @@ interface IListenUpperKeepAliveContainerState {
interface ITriggerLifecycleContainerProps extends IKeepAliveContextConsumerComponentProps { interface ITriggerLifecycleContainerProps extends IKeepAliveContextConsumerComponentProps {
propKey: string; propKey: string;
keepAlive: boolean; keepAlive: boolean;
getCombinedKeepAlive: () => boolean;
} }
/** /**
...@@ -43,6 +46,8 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy ...@@ -43,6 +46,8 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy
class TriggerLifecycleContainer extends React.PureComponent<ITriggerLifecycleContainerProps> { class TriggerLifecycleContainer extends React.PureComponent<ITriggerLifecycleContainerProps> {
private identification: string; private identification: string;
private activated = false;
private ifStillActivate = false; private ifStillActivate = false;
// Let the lifecycle of the cached component be called normally. // Let the lifecycle of the cached component be called normally.
...@@ -63,6 +68,9 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy ...@@ -63,6 +68,9 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy
} }
public componentDidMount() { public componentDidMount() {
if (!this.ifStillActivate) {
this.activate();
}
const { const {
keepAlive, keepAlive,
_keepAliveContextProps: { _keepAliveContextProps: {
...@@ -71,19 +79,40 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy ...@@ -71,19 +79,40 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy
} = this.props; } = this.props;
if (keepAlive) { if (keepAlive) {
this.needActivate = true; this.needActivate = true;
eventEmitter.emit([this.identification, COMMAND.MOUNT]); eventEmitter.emit([this.identification, COMMAND.ACTIVATE]);
}
}
public componentDidCatch() {
if (!this.activated) {
this.activate();
} }
} }
public componentWillUnmount() { public componentWillUnmount() {
const { const {
getCombinedKeepAlive,
_keepAliveContextProps: { _keepAliveContextProps: {
eventEmitter, eventEmitter,
isExisted,
}, },
} = this.props; } = this.props;
const keepAlive = getCombinedKeepAlive();
if (!keepAlive || !isExisted()) {
eventEmitter.emit([this.identification, COMMAND.CURRENT_UNMOUNT]); eventEmitter.emit([this.identification, COMMAND.CURRENT_UNMOUNT]);
eventEmitter.emit([this.identification, COMMAND.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 = () => {
this.activated = true;
}
private reactivate = () => { private reactivate = () => {
this.ifStillActivate = false; this.ifStillActivate = false;
...@@ -110,6 +139,7 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy ...@@ -110,6 +139,7 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy
const { const {
propKey, propKey,
keepAlive, keepAlive,
getCombinedKeepAlive,
_keepAliveContextProps: { _keepAliveContextProps: {
isExisted, isExisted,
storeElement, storeElement,
...@@ -137,6 +167,7 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy ...@@ -137,6 +167,7 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy
const { const {
isNeedActivate, isNeedActivate,
notNeedActivate, notNeedActivate,
activated,
getLifecycle, getLifecycle,
setLifecycle, setLifecycle,
identification, identification,
...@@ -156,6 +187,7 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy ...@@ -156,6 +187,7 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy
identification, identification,
eventEmitter, eventEmitter,
keepAlive, keepAlive,
activated,
getLifecycle, getLifecycle,
isExisted, isExisted,
}} }}
...@@ -169,6 +201,7 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy ...@@ -169,6 +201,7 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy
eventEmitter, eventEmitter,
identification, identification,
storeElement, storeElement,
keepAlive,
cache, cache,
}} }}
/> />
...@@ -180,11 +213,15 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy ...@@ -180,11 +213,15 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy
} }
class ListenUpperKeepAliveContainer extends React.Component<IListenUpperKeepAliveContainerProps, IListenUpperKeepAliveContainerState> { class ListenUpperKeepAliveContainer extends React.Component<IListenUpperKeepAliveContainerProps, IListenUpperKeepAliveContainerState> {
private combinedKeepAlive: boolean;
public state = { public state = {
activated: true, activated: true,
}; };
private mount: () => void; private activate: () => void;
private unactivate: () => void;
private unmount: () => void; private unmount: () => void;
...@@ -228,8 +265,13 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy ...@@ -228,8 +265,13 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy
return; return;
} }
eventEmitter.on( eventEmitter.on(
[identification, COMMAND.MOUNT], [identification, COMMAND.ACTIVATE],
this.mount = () => this.setState({activated: true}), this.activate = () => this.setState({activated: true}),
true,
);
eventEmitter.on(
[identification, COMMAND.UNACTIVATE],
this.unactivate = () => this.setState({activated: false}),
true, true,
); );
eventEmitter.on( eventEmitter.on(
...@@ -244,10 +286,15 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy ...@@ -244,10 +286,15 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy
if (!identification) { if (!identification) {
return; return;
} }
eventEmitter.off([identification, COMMAND.MOUNT], this.mount); eventEmitter.off([identification, COMMAND.ACTIVATE], this.activate);
eventEmitter.off([identification, COMMAND.UNACTIVATE], this.unactivate);
eventEmitter.off([identification, COMMAND.UNMOUNT], this.unmount); eventEmitter.off([identification, COMMAND.UNMOUNT], this.unmount);
} }
private getCombinedKeepAlive = () => {
return this.combinedKeepAlive;
}
public render() { public render() {
const { const {
_identificationContextProps: { _identificationContextProps: {
...@@ -274,7 +321,7 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy ...@@ -274,7 +321,7 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy
return null; return null;
} }
const newKeepAlive = getKeepAlive(propKey, include, exclude, disabled); const newKeepAlive = getKeepAlive(propKey, include, exclude, disabled);
const combinedKeepAlive = getLifecycle === undefined || getLifecycle() === LIFECYCLE.UPDATING this.combinedKeepAlive = getLifecycle === undefined || getLifecycle() === LIFECYCLE.UPDATING
? newKeepAlive ? newKeepAlive
: identification : identification
? upperKeepAlive && newKeepAlive ? upperKeepAlive && newKeepAlive
...@@ -285,7 +332,8 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy ...@@ -285,7 +332,8 @@ export default function keepAliveDecorator<P = any>(Component: React.ComponentTy
{...wrapperProps} {...wrapperProps}
key={propKey} key={propKey}
propKey={propKey} propKey={propKey}
keepAlive={combinedKeepAlive} keepAlive={this.combinedKeepAlive}
getCombinedKeepAlive={this.getCombinedKeepAlive}
/> />
) )
: null; : null;
......
import React, {useEffect, useContext, useRef} from 'react'; import React, {useEffect, useContext, useRef, useState} from 'react';
import {warn} from './debug'; import {warn} from './debug';
import {COMMAND} from './keepAliveDecorator'; import {COMMAND} from './keepAliveDecorator';
import IdentificationContext, {IIdentificationContextProps} from '../contexts/IdentificationContext'; import IdentificationContext, {IIdentificationContextProps} from '../contexts/IdentificationContext';
...@@ -14,18 +14,32 @@ export default function useKeepAliveEffect(effect: React.EffectCallback) { ...@@ -14,18 +14,32 @@ export default function useKeepAliveEffect(effect: React.EffectCallback) {
const effectRef: React.MutableRefObject<React.EffectCallback> = useRef(effect); const effectRef: React.MutableRefObject<React.EffectCallback> = useRef(effect);
effectRef.current = effect; effectRef.current = effect;
useEffect(() => { useEffect(() => {
let bindMount: (() => void) | null = null; let bindActivate: (() => void) | null = null;
let bindUnactivate: (() => void) | null = null;
let bindUnmount: (() => void) | null = null; let bindUnmount: (() => void) | null = null;
let effectResult = effectRef.current(); let effectResult = effectRef.current();
let unmounted = false; let unmounted = false;
eventEmitter.on( eventEmitter.on(
[identification, COMMAND.MOUNT], [identification, COMMAND.ACTIVATE],
bindMount = () => { bindActivate = () => {
// Delayed update
Promise.resolve().then(() => {
effectResult = effectRef.current(); effectResult = effectRef.current();
});
unmounted = false; unmounted = false;
}, },
true, true,
); );
eventEmitter.on(
[identification, COMMAND.UNACTIVATE],
bindUnactivate = () => {
if (effectResult) {
effectResult();
unmounted = true;
}
},
true,
);
eventEmitter.on( eventEmitter.on(
[identification, COMMAND.UNMOUNT], [identification, COMMAND.UNMOUNT],
bindUnmount = () => { bindUnmount = () => {
...@@ -41,8 +55,12 @@ export default function useKeepAliveEffect(effect: React.EffectCallback) { ...@@ -41,8 +55,12 @@ export default function useKeepAliveEffect(effect: React.EffectCallback) {
effectResult(); effectResult();
} }
eventEmitter.off( eventEmitter.off(
[identification, COMMAND.MOUNT], [identification, COMMAND.ACTIVATE],
bindMount, bindActivate,
);
eventEmitter.off(
[identification, COMMAND.UNACTIVATE],
bindUnactivate,
); );
eventEmitter.off( eventEmitter.off(
[identification, COMMAND.UNMOUNT], [identification, COMMAND.UNMOUNT],
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
"rules": { "rules": {
"max-line-length": false, "max-line-length": false,
"no-console": false, "no-console": false,
"no-debugger": false,
"quotemark": [true, "single", "jsx-double"], "quotemark": [true, "single", "jsx-double"],
"trailing-comma": [true, {"multiline": "ignore", "singleline": "never"}], "trailing-comma": [true, {"multiline": "ignore", "singleline": "never"}],
"ordered-imports": false, "ordered-imports": false,
......
declare module "react-deep-force-update" {
export default function deepForceUpdate(instance: any, shouldUpdate?: Function, onUpdate?: Function): void;
}
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