From 0ad596bd3e730bb02f2cd0a5f4e9ed25036670b4 Mon Sep 17 00:00:00 2001 From: CJY <375564567@qq.com> Date: Thu, 7 Nov 2019 15:52:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20#53=EF=BC=8C#54=EF=BC=9B?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E6=96=B0=E5=A2=9E=20CacheSwitch=20which=20?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E8=AF=B4=E6=98=8E=EF=BC=9B=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=AF=B9=20multiple=20=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E7=9A=84=E7=89=88=E6=9C=AC=E9=9C=80=E6=B1=82=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 26 +++++++++++++++++--------- README_CN.md | 10 +++++++++- dist/cacheRoute.js | 13 +++++++------ dist/cacheRoute.min.js | 2 +- package.json | 2 +- src/components/CacheRoute.js | 2 +- src/components/CacheSwitch.js | 29 +++++++++++++---------------- src/core/CacheComponent.js | 7 +++---- src/helpers/saveScrollPosition.js | 9 ++++++--- 9 files changed, 58 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index b137659..3ec0787 100644 --- a/README.md +++ b/README.md @@ -78,15 +78,15 @@ export default App ## CacheRoute props -| name | type | default | description | -| -------------------------------- | --------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| when | `String` / `Function` | `"forward"` | Decide when to cache | -| className | `String` | - | `className` prop for the wrapper component | -| behavior | `Function` | `cached => cached ? { style: { display: "none" }} : undefined` | Return `props` effective on the wrapper component to control rendering behavior | -| cacheKey | `String` | - | For imperative control caching | -| multiple (React v16.3+) | `Boolean` / `Number` | `false` | Allows different caches to be distinguished by dynamic routing parameters. When the value is a number, it indicates the maximum number of caches. When the maximum value is exceeded, the oldest updated cache will be cleared. | -| unmount (UNSTABLE) | `Boolean` | `false` | Whether to unmount the real dom node after cached, to save performance (Will cause losing the scroll position after recovered, fixed with `saveScrollPosition` props) | -| saveScrollPosition (UNSTABLE) | `Boolean` | `false` | Save scroll position | +| name | type | default | description | +| ----------------------------- | --------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| when | `String` / `Function` | `"forward"` | Decide when to cache | +| className | `String` | - | `className` prop for the wrapper component | +| behavior | `Function` | `cached => cached ? { style: { display: "none" }} : undefined` | Return `props` effective on the wrapper component to control rendering behavior | +| cacheKey | `String` | - | For imperative control caching | +| multiple (React v16.2+) | `Boolean` / `Number` | `false` | Allows different caches to be distinguished by dynamic routing parameters. When the value is a number, it indicates the maximum number of caches. When the maximum value is exceeded, the oldest updated cache will be cleared. | +| unmount (UNSTABLE) | `Boolean` | `false` | Whether to unmount the real dom node after cached, to save performance (Will cause losing the scroll position after recovered, fixed with `saveScrollPosition` props) | +| saveScrollPosition (UNSTABLE) | `Boolean` | `false` | Save scroll position | `CacheRoute` is only a wrapper component that works based on the `children` property of `Route`, and does not affect the functionality of `Route` itself. @@ -106,6 +106,14 @@ When the type is `Function`, the component's `props` will be accepted as the fir --- +## CacheSwitch props + +| name | type | default | description | +| ----- | ---------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| which | `Function` | `element => element.type === CacheRoute` | `` only saves the first layer of nodes which type is `CacheRoute` **by default**, `which` prop is a function that would receive a instance of React Component, return `true/false` to decide if `` need to save it, reference [#55](https://github.com/CJY0208/react-router-cache-route/issues/55) | + +--- + ## Lifecycles Component with CacheRoute will accept one prop named `cacheLifecycles` which contains two functions to inject customer Lifecycle `didCache` and `didRecover` diff --git a/README_CN.md b/README_CN.md index f437c0c..eb6a2d9 100644 --- a/README_CN.md +++ b/README_CN.md @@ -86,7 +86,7 @@ export default App | className | `String` | - | 作用于包裹容器上的样式类名 | | behavior | `Function` | `cached => cached ? { style: { display: "none" }} : undefined` | 返回一个作用于包裹容器的 `props`,控制包裹容器的渲染方式 | | cacheKey | `String` | - | 增加此属性用于命令式控制缓存 | -| multiple (React v16.3+) | `Boolean` / `Number` | `false` | 允许按动态路由参数区分不同缓存,值为数字时表示最大缓存份数,超出最大值时将清除最早更新的缓存 | +| multiple (React v16.2+) | `Boolean` / `Number` | `false` | 允许按动态路由参数区分不同缓存,值为数字时表示最大缓存份数,超出最大值时将清除最早更新的缓存 | | unmount (实验性) | `Boolean` | `false` | 缓存时是否卸载 dom 节点,用于节约性能(单独使用将导致恢复时滚动位置丢失,可配合 saveScrollPosition 修复) | | saveScrollPosition (实验性) | `Boolean` | `false` | 用以保存滚动位置 | @@ -108,6 +108,14 @@ export default App --- +## CacheSwitch 属性说明 + +| 名称 | 类型 | 默认值 | 描述 | +| ----- | ---------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| which | `Function` | `element => element.type === CacheRoute` | `` 默认只保存第一层子节点中类型为 `CacheRoute` 的节点, `which` 属性是一个将获得待渲染 React 节点实例的方法, 通过返回 `true/false` 来决定 `` 是否需要保存它,参考 [#55](https://github.com/CJY0208/react-router-cache-route/issues/55) | + +--- + ## 额外的生命周期 使用 `CacheRoute` 的组件将会得到一个名为 `cacheLifecycles` 的属性,里面包含两个额外生命周期的注入函数 `didCache` 和 `didRecover`,分别在组件 **被缓存** 和 **被恢复** 时触发 diff --git a/dist/cacheRoute.js b/dist/cacheRoute.js index 9654125..64f78e6 100644 --- a/dist/cacheRoute.js +++ b/dist/cacheRoute.js @@ -278,6 +278,7 @@ }; var body = get(globalThis, 'document.body'); + var screenScrollingElement = get(globalThis, 'document.scrollingElement', get(globalThis, 'document.documentElement', {})); function isScrollableNode() { var node = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; @@ -298,7 +299,7 @@ } function saveScrollPosition(from) { - var nodes = [].concat(toConsumableArray(new Set([].concat(toConsumableArray(flatten((!isArray(from) ? [from] : from).map(getScrollableNodes))), toConsumableArray([get(globalThis, 'document.documentElement', {}), body].filter(isScrollableNode)))))); + var nodes = [].concat(toConsumableArray(new Set([].concat(toConsumableArray(flatten((!isArray(from) ? [from] : from).map(getScrollableNodes))), toConsumableArray([screenScrollingElement, body].filter(isScrollableNode)))))); var saver = nodes.map(function (node) { return [node, { @@ -398,7 +399,7 @@ }, {}); }; - var __isUsingNewLifecycle = Number(get(run(React__default, 'version.match', /^\d*\.\d*/), [0])) >= 16.3; + var isUsingNewLifecycle = isExist(React__default.forwardRef); var COMPUTED_UNMATCH_KEY = '__isComputedUnmatch'; var isMatch = function isMatch(match) { @@ -499,7 +500,7 @@ * React 16.3 + 版本中替代 componentWillReceiveProps 的新生命周期 */ }; - _this.componentWillReceiveProps = !__isUsingNewLifecycle ? function (nextProps) { + _this.componentWillReceiveProps = !isUsingNewLifecycle ? function (nextProps) { var nextState = getDerivedStateFromProps(nextProps, _this.state); _this.setState(nextState); @@ -696,7 +697,7 @@ } : undefined; } }; - CacheComponent.getDerivedStateFromProps = __isUsingNewLifecycle ? getDerivedStateFromProps : undefined; + CacheComponent.getDerivedStateFromProps = isUsingNewLifecycle ? getDerivedStateFromProps : undefined; var Updatable = function (_Component) { inherits(Updatable, _Component); @@ -972,7 +973,7 @@ var _location = _this.props.location || route.location; return { - location: _this.props.location || route.location, + location: _location, match: route.match }; } @@ -1064,7 +1065,7 @@ CacheSwitch.defaultProps = { which: function which(element) { - return value(get(element, 'type.componentName'), get(element, 'type.displayName')) === 'CacheRoute'; + return get(element, 'type') === CacheRoute; } }; diff --git a/dist/cacheRoute.min.js b/dist/cacheRoute.min.js index 5cbaecd..d50a4c2 100644 --- a/dist/cacheRoute.min.js +++ b/dist/cacheRoute.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react"),require("prop-types"),require("react-router-dom")):"function"==typeof define&&define.amd?define(["exports","react","prop-types","react-router-dom"],t):t(e.CacheRoute={},e.React,e.PropTypes,e.reactRouterDom)}(this,function(e,w,t,u){"use strict";var g="default"in w?w.default:w;t=t&&t.hasOwnProperty("default")?t.default:t;var o=function(e){return void 0===e},l=function(e){return null===e},i=function(e){return"function"==typeof e},h=function(e){return"string"==typeof e},n=function(e){return!(o(e)||l(e))},r=function(e){return e instanceof Array},O=function(e){return"number"==typeof e&&!((t=e)!=t);var t},s=function(e){var t=1e.clientWidth||e.scrollHeight>e.clientHeight}function T(e){return i(s(_,"document.getElementById"))?[].concat(a(m(j(e,"querySelectorAll","*"),[])),[e]).filter(S):[]}function N(e){var t=[].concat(a(new Set([].concat(a(function n(e){return e.reduce(function(e,t){return[].concat(a(e),a(r(t)?n(t):[t]))},[])}((r(e)?e:[e]).map(T))),a([s(_,"document.documentElement",{}),R].filter(S)))))).map(function(e){return[e,{x:e.scrollLeft,y:e.scrollTop}]});return function(){t.forEach(function(e){var t=E(e,2),n=t[0],r=t[1],o=r.x,c=r.y;n.scrollLeft=o,n.scrollTop=c})}}var A={},K=function(){return Object.entries(A).filter(function(e){var t=E(e,2)[1];return t instanceof W?t.state.cached:Object.values(t).some(function(e){return e.state.cached})})},M=function(){return C({},A)},k=function(e,t){A[e]=t},x=function(e){delete A[e]},q=function(e){return j(e,"reset")},D=function(e){var t=s(A,e);t&&(t instanceof W?q(t):Object.values(t).forEach(q))},F=16.3<=Number(s(j(g,"version.match",/^\d*\.\d*/),[0])),L="__isComputedUnmatch",U=function(e){return n(e)&&!0!==s(e,L)},B=function(e,t){var n=e.match,r=e.when,o=void 0===r?"forward":r;if(U(n)||(n=null),!t.cached&&n)return{cached:!0,matched:!0};if(t.matched&&!n){var c=s(e,"history.action"),a=!1;if(i(o))a=!o(e);else switch(o){case"always":break;case"back":["PUSH","REPLACE"].includes(c)&&(a=!0);break;case"forward":default:"POP"===c&&(a=!0)}if(a)return{cached:!1,matched:!1}}return{matched:!!n}},W=function(e){function l(e){var t;f(this,l);for(var n=arguments.length,r=Array(1e.clientWidth||e.scrollHeight>e.clientHeight}function N(e){return i(s(_,"document.getElementById"))?[].concat(a(m(j(e,"querySelectorAll","*"),[])),[e]).filter(T):[]}function A(e){var t=[].concat(a(new Set([].concat(a(function n(e){return e.reduce(function(e,t){return[].concat(a(e),a(r(t)?n(t):[t]))},[])}((r(e)?e:[e]).map(N))),a([S,R].filter(T)))))).map(function(e){return[e,{x:e.scrollLeft,y:e.scrollTop}]});return function(){t.forEach(function(e){var t=E(e,2),n=t[0],r=t[1],o=r.x,c=r.y;n.scrollLeft=o,n.scrollTop=c})}}var K={},M=function(){return Object.entries(K).filter(function(e){var t=E(e,2)[1];return t instanceof H?t.state.cached:Object.values(t).some(function(e){return e.state.cached})})},k=function(){return C({},K)},x=function(e,t){K[e]=t},q=function(e){delete K[e]},D=function(e){return j(e,"reset")},F=function(e){var t=s(K,e);t&&(t instanceof H?D(t):Object.values(t).forEach(D))},L=n(g.forwardRef),U="__isComputedUnmatch",B=function(e){return n(e)&&!0!==s(e,U)},W=function(e,t){var n=e.match,r=e.when,o=void 0===r?"forward":r;if(B(n)||(n=null),!t.cached&&n)return{cached:!0,matched:!0};if(t.matched&&!n){var c=s(e,"history.action"),a=!1;if(i(o))a=!o(e);else switch(o){case"always":break;case"back":["PUSH","REPLACE"].includes(c)&&(a=!0);break;case"forward":default:"POP"===c&&(a=!0)}if(a)return{cached:!1,matched:!1}}return{matched:!!n}},H=function(e){function l(e){var t;f(this,l);for(var n=arguments.length,r=Array(1 React.Children.count(children) === 0 const isFragmentable = isExist(Fragment) diff --git a/src/components/CacheSwitch.js b/src/components/CacheSwitch.js index 9d9b1dd..953f1c3 100644 --- a/src/components/CacheSwitch.js +++ b/src/components/CacheSwitch.js @@ -10,7 +10,8 @@ import { import { COMPUTED_UNMATCH_KEY, isMatch } from '../core/CacheComponent' import Updatable from '../core/Updatable' import SwitchFragment from './SwitchFragment' -import { get, value, isNull, isExist } from '../helpers' +import { get, isNull, isExist } from '../helpers' +import CacheRoute from './CacheRoute' const isUsingNewContext = isExist(__RouterContext) @@ -25,7 +26,7 @@ class CacheSwitch extends Switch { const location = this.props.location || route.location return { - location: this.props.location || route.location, + location, match: route.match } } @@ -50,15 +51,15 @@ class CacheSwitch extends Switch { const match = __matchedAlready ? null : path - ? matchPath( - location.pathname, - { - ...element.props, - path - }, - contextMatch - ) - : contextMatch + ? matchPath( + location.pathname, + { + ...element.props, + path + }, + contextMatch + ) + : contextMatch let child @@ -131,11 +132,7 @@ if (isUsingNewContext) { } CacheSwitch.defaultProps = { - which: element => - value( - get(element, 'type.componentName'), - get(element, 'type.displayName') - ) === 'CacheRoute' + which: element => get(element, 'type') === CacheRoute } export default CacheSwitch diff --git a/src/core/CacheComponent.js b/src/core/CacheComponent.js index 2d195e1..ae13c77 100644 --- a/src/core/CacheComponent.js +++ b/src/core/CacheComponent.js @@ -11,8 +11,7 @@ import { } from '../helpers' import * as manager from './manager' -const __isUsingNewLifecycle = - Number(get(run(React, 'version.match', /^\d*\.\d*/), [0])) >= 16.3 +const isUsingNewLifecycle = isExist(React.forwardRef) export const COMPUTED_UNMATCH_KEY = '__isComputedUnmatch' export const isMatch = match => @@ -161,7 +160,7 @@ export default class CacheComponent extends Component { * New lifecycle for replacing the `componentWillReceiveProps` in React 16.3 + * React 16.3 + 版本中替代 componentWillReceiveProps 的新生命周期 */ - static getDerivedStateFromProps = __isUsingNewLifecycle + static getDerivedStateFromProps = isUsingNewLifecycle ? getDerivedStateFromProps : undefined @@ -169,7 +168,7 @@ export default class CacheComponent extends Component { * Compatible React 16.3 - * 兼容 React 16.3 - 版本 */ - componentWillReceiveProps = !__isUsingNewLifecycle + componentWillReceiveProps = !isUsingNewLifecycle ? nextProps => { const nextState = getDerivedStateFromProps(nextProps, this.state) diff --git a/src/helpers/saveScrollPosition.js b/src/helpers/saveScrollPosition.js index 84ea8d8..aa5d557 100644 --- a/src/helpers/saveScrollPosition.js +++ b/src/helpers/saveScrollPosition.js @@ -4,6 +4,11 @@ import { isArray, isFunction, isExist } from './base/is' import { flatten } from './utils' const body = get(root, 'document.body') +const screenScrollingElement = get( + root, + 'document.scrollingElement', + get(root, 'document.documentElement', {}) +) function isScrollableNode(node = {}) { // if (!isExist(node)) { @@ -29,9 +34,7 @@ export default function saveScrollPosition(from) { const nodes = [ ...new Set([ ...flatten((!isArray(from) ? [from] : from).map(getScrollableNodes)), - ...[get(root, 'document.documentElement', {}), body].filter( - isScrollableNode - ) + ...[screenScrollingElement, body].filter(isScrollableNode) ]) ]