diff --git a/.npmignore b/.npmignore index e352baa..89017a6 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,4 @@ +docs node_modules src .babelrc diff --git a/README.md b/README.md index 32d1214..8ad3fb2 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,32 @@ function App() { - - - +## Multiple Cache + +Under the same parent node, `` in the same location will use the same cache by default. + +For example, with the following parameter routing scenario, the `/item` route will be rendered differently by `id`, but only the same cache can be kept. + +```javascript + ( + + + +)} /> +``` + +Similar scenarios, you can use the `id` attribute of `` to implement multiple caches according to specific conditions. + +```javascript + ( + + + +)} /> +``` + +- - - + ## Cache Control ### Automatic control cache @@ -410,3 +436,4 @@ Since `` will not be uninstalled, caching can be implemented. ## More complicated example - [Closable tabs with `react-router`](https://codesandbox.io/s/keguanbideyifangwenluyou-tab-shilikeanluyoucanshufenduofenhuancun-ewycx) +- [Using Animation with `react-router`](https://codesandbox.io/s/using-animation-y35hh) diff --git a/README_CN.md b/README_CN.md index d40d799..f01018f 100644 --- a/README_CN.md +++ b/README_CN.md @@ -159,6 +159,32 @@ function App() { - - - +## 多份缓存 + +同一个父节点下,相同位置的 `` 默认会使用同一份缓存 + +例如下述的带参数路由场景,`/item` 路由会按 `id` 来做不同呈现,但只能保留同一份缓存 + +```javascript + ( + + + +)} /> +``` + +类似场景,可以使用 `` 的 `id` 属性,来实现按特定条件分成多份缓存 + +```javascript + ( + + + +)} /> +``` + +- - - + ## 缓存控制 ### 自动控制缓存 @@ -408,4 +434,5 @@ class App extends Component { ## 更多复杂示例 -- [可关闭的路由 tabs 示例](https://codesandbox.io/s/keguanbideyifangwenluyou-tab-shilikeanluyoucanshufenduofenhuancun-ewycx) \ No newline at end of file +- [可关闭的路由 tabs 示例](https://codesandbox.io/s/keguanbideyifangwenluyou-tab-shilikeanluyoucanshufenduofenhuancun-ewycx) +- [使用路由转场动画](https://codesandbox.io/s/using-animation-y35hh) \ No newline at end of file diff --git a/babel.config.js b/babel.config.js index e198f38..ed571ca 100644 --- a/babel.config.js +++ b/babel.config.js @@ -10,6 +10,6 @@ module.exports = { ], plugins: [ '@babel/plugin-proposal-class-properties', - './src/babel/helpers' + './src/babel' ] } diff --git a/babel.js b/babel.js index 459c976..694cab4 100644 --- a/babel.js +++ b/babel.js @@ -1,3 +1,3 @@ 'use strict' -module.exports = require('./lib/babel/helpers.js'); +module.exports = require('./lib/babel/index.js'); diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..90dd534 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,42 @@ +/// +import * as React from 'react' + +export declare class KeepAlive extends React.Component<{ + children: ReactNode | ReactNodeArray, + name?: string, + id?: string, + when?: boolean | Array | (() => boolean | Array) +}> {} +export default KeepAlive + +export declare class AliveScope extends React.Component<{ + children: ReactNode | ReactNodeArray, +}> {} + +export function withActivation(Component: React.ComponentClass): React.ComponentClass +export function withAliveScope(Component: React.ComponentClass): React.ComponentClass + +export function fixContext(Context: React.Context): void +export function createContext( + defaultValue: T, + calculateChangedBits?: (prev: T, next: T) => number +): Context + + +type HookLifecycleEffectCallback = () => void +export function useActivate(effect: HookLifecycleEffectCallback): void +export function useUnactivate(effect: HookLifecycleEffectCallback): void + +interface CachingNode { + createTime: number, + updateTime: number, + name?: string, + id: string +} +interface AliveController { + drop: (name: string | RegExp) => Promise, + dropScope: (name: string | RegExp) => Promise, + clear: () => Promise, + getCachingNodes: () => Array +} +export function useAliveController(): AliveController \ No newline at end of file diff --git a/package.json b/package.json index bcf6850..4f1208c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-activation", - "version": "0.1.1", + "version": "0.2.0", "description": " for React like in vue", "main": "index.js", "scripts": { @@ -26,7 +26,8 @@ }, "dependencies": { "create-react-context": "^0.3.0", - "hoist-non-react-statics": "^3.3.0" + "hoist-non-react-statics": "^3.3.0", + "jsx-ast-utils": "^2.2.1" }, "devDependencies": { "@babel/core": "^7.5.5", @@ -35,6 +36,7 @@ "@babel/preset-react": "^7.0.0", "rollup": "^1.11.3", "rollup-plugin-babel": "^4.3.2", + "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^4.2.3", "rollup-plugin-uglify": "^6.0.2" } diff --git a/rollup.config.js b/rollup.config.js index 86e1cf2..fe68f5d 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,4 +1,5 @@ import resolve from 'rollup-plugin-node-resolve' +import commonjs from 'rollup-plugin-commonjs' import babel from 'rollup-plugin-babel' import { uglify } from 'rollup-plugin-uglify' @@ -36,14 +37,27 @@ export default [ ] }, { - input: 'src/babel/helpers.js', + input: 'src/babel/index.js', output: { - file: 'lib/babel/helpers.js', + file: 'lib/babel/index.js', format: 'cjs' }, + external: ['jsx-ast-utils'], plugins: [ resolve(), + commonjs(), babel({ + babelrc: false, + presets: [ + [ + '@babel/env', + { + targets: { + node: true + } + } + ] + ], exclude: 'node_modules/**' }) ] diff --git a/src/babel/helpers.js b/src/babel/helpers.js index 2528a7b..2ca8dd3 100644 --- a/src/babel/helpers.js +++ b/src/babel/helpers.js @@ -1,13 +1,29 @@ -// https://github.com/CJY0208/babel-plugin-tester 开发 - -const crypto = require('crypto') - -function getMap() { +const isUndefined = val => typeof val === 'undefined' +const isNull = val => val === null +const isString = val => typeof val === 'string' +const isExist = val => !(isUndefined(val) || isNull(val)) +const isNumber = val => typeof val === 'number' && !isNaN(val) +const isFunction = val => typeof val === 'function' +const get = (obj, keys = [], defaultValue) => { + try { + if (isNumber(keys)) { + keys = String(keys) + } + let result = (isString(keys) ? keys.split('.') : keys).reduce( + (res, key) => res[key], + obj + ) + return isUndefined(result) ? defaultValue : result + } catch (e) { + return defaultValue + } +} +function getKey2Id() { let uuid = 0 const map = new Map() // 对每种 NodeType 做编号处理 - function getIdByKey(key) { + return function key2Id(key) { let id = map.get(key) if (!id) { @@ -17,137 +33,55 @@ function getMap() { return id } - - return getIdByKey } -module.exports = function({ types: t, template }) { - const jSXAttribute = (t.jSXAttribute || t.jsxAttribute).bind(t) - const jSXIdentifier = (t.jSXIdentifier || t.jsxIdentifier).bind(t) - const jSXExpressionContainer = ( - t.jSXExpressionContainer || t.jsxExpressionContainer - ).bind(t) - - function getVisitor(filehashIdentifier) { - const KATypeCountMap = new Map() - // 对每种 NodeType 做编号处理 - const getTypeId = getMap() - - function genKAValue(openingElementNode) { - try { - const typeId = getTypeId(openingElementNode.name.name) +function markIsArrayElement(node) { + if (node) { + node.__isArrayElement = true + } +} - const count = KATypeCountMap.get(typeId) || 0 - const kaValue = count + 1 - KATypeCountMap.set(typeId, kaValue) - const nodeId = `${typeId}${kaValue.toString(32)}` +function getReturnStatement(body) { + return body.filter(item => item.type === 'ReturnStatement')[0] +} - return jSXExpressionContainer( - t.templateLiteral( - [ - t.templateElement({ raw: '', cooked: '' }), - t.templateElement({ raw: nodeId, cooked: nodeId }, true) - ], - [filehashIdentifier] - ) - ) - // return t.stringLiteral(`${typeId}${kaValue.toString(32)}`) - } catch (error) { - return t.stringLiteral(`error`) - } +// 参考 eslint-plugin-react 对数组 key 的校验过程,来标记数组元素 +// https://github.com/yannickcr/eslint-plugin-react/blob/master/lib/rules/jsx-key.js#L93 +const callExpressionVisitor = { + // Array.prototype.map + CallExpression(path) { + const { node } = path + if (get(node, 'callee.type') !== 'MemberExpression') { + return } - return { - JSXOpeningElement: { - enter(path) { - // 排除 Fragment - // TODO: 考虑 Fragment 重命名情况 - if (path.node.name.name.includes('Fragment')) { - return - } - - // 排除 key 为以下的项,保证 SSR 时两端结果一致 - const keyAttr = path.node.attributes.find( - attr => attr.type === 'JSXAttribute' && attr.name.name === 'key' - ) - if ( - keyAttr && - keyAttr.value && - ['keep-alive-placeholder', 'keeper-container'].includes( - keyAttr.value.value - ) - ) { - return - } - - // 不允许自定义 _ka 属性 - // TODO: 使用 key 属性替换,需考虑不覆盖 array 结构中的 key 属性,array 结构中保持 _ka 属性 - // 可参考:https://github.com/yannickcr/eslint-plugin-react/blob/master/lib/rules/jsx-key.js - const attributes = path.node.attributes.filter(attr => { - try { - return attr.type !== 'JSXAttribute' || attr.name.name !== '_ka' - } catch (error) { - return true - } - }) - - path.node.attributes = [ - ...attributes, - jSXAttribute(jSXIdentifier('_ka'), genKAValue(path.node)) - ] - } - } + if (get(node, 'callee.property.name' !== 'map')) { + return } - } - return { - visitor: { - Program: { - enter(path, { cwd, filename, file: { opts = {} } = {} }) { - const md5 = crypto.createHash('md5') - const filepath = - filename && filename.replace && cwd - ? filename.replace(cwd, '') - : opts.sourceFileName - md5.update(filepath) - const hash = md5.digest('base64').slice(0, 4) - const filehashIdentifier = path.scope.generateUidIdentifier( - 'filehash' - ) + const fn = node.arguments[0] + const fnType = get(fn, 'type') + const isFn = fnType === 'FunctionExpression' + const isArrFn = fnType === 'ArrowFunctionExpression' - let filehashTemplate - - try { - filehashTemplate = template(`const %%filehash%% = %%hashString%%;`)( - { - filehash: filehashIdentifier, - hashString: t.stringLiteral(hash) - } - ) - } catch (error) { - filehashTemplate = template(`const ${filehashIdentifier.name} = '${hash}';`)() - } - - const imports = path.node.body.filter(node => - t.isImportDeclaration(node) - ) - - if (imports.length > 0) { - const insertPlace = imports[imports.length - 1] - const insertPlacePath = path.get( - `body.${path.node.body.indexOf(insertPlace)}` - ) - insertPlacePath.insertAfter(filehashTemplate) - } else { - const insertPlacePath = path.get(`body.0`) - if (insertPlacePath) { - insertPlacePath.insertBefore(filehashTemplate) - } - } + if (isArrFn && ['JSXElement', 'JSXFragment'].includes(fn.body.type)) { + markIsArrayElement(fn.body.openingElement) + } - path.traverse(getVisitor(filehashIdentifier)) + if (isFn || isArrFn) { + if (fn.body.type === 'BlockStatement') { + const returnStatement = getReturnStatement(fn.body.body) + if (isExist(get(returnStatement, 'argument.openingElement'))) { + markIsArrayElement(returnStatement.argument.openingElement) } } } } } + +module.exports = { + get, + isFunction, + callExpressionVisitor, + getKey2Id +} diff --git a/src/babel/index.js b/src/babel/index.js new file mode 100644 index 0000000..676e67a --- /dev/null +++ b/src/babel/index.js @@ -0,0 +1,152 @@ +// https://github.com/CJY0208/babel-plugin-tester 开发 + +const crypto = require('crypto') +const jsxHelpers = require('jsx-ast-utils') +const { get, getKey2Id, isFunction, callExpressionVisitor } = require('./helpers') + +module.exports = function({ types: t, template, env: getEnv }) { + // 7.x https://github.com/babel/babel/blob/master/babel.config.js#L4 + // 6.x https://github.com/babel/babel/blob/6.x/packages/babel-core/src/transformation/file/options/build-config-chain.js#L165 + // 尝试从 env 函数或 process 中获取当前 babel 环境 + const env = isFunction(getEnv) ? getEnv() : process.env.BABEL_ENV || process.env.NODE_ENV || 'development' + const jSXAttribute = (t.jSXAttribute || t.jsxAttribute).bind(t) + const jSXIdentifier = (t.jSXIdentifier || t.jsxIdentifier).bind(t) + const jSXExpressionContainer = ( + t.jSXExpressionContainer || t.jsxExpressionContainer + ).bind(t) + + function getElementVisitor(filehashIdentifier) { + const KATypeCountMap = new Map() + // 对每种 NodeType 做编号处理 + const key2Id = getKey2Id() + + function genUUID(node) { + try { + const typeId = key2Id(get(node, 'name.name')) + + const count = KATypeCountMap.get(typeId) || 0 + const kaValue = count + 1 + + KATypeCountMap.set(typeId, kaValue) + + const nodeId = `${typeId}${kaValue.toString(32)}` + const isArrayElement = node.__isArrayElement + const rawStart = isArrayElement ? 'iAr' : '' + + return jSXExpressionContainer( + t.templateLiteral( + [ + t.templateElement({ raw: rawStart, cooked: rawStart }), + t.templateElement({ raw: nodeId, cooked: nodeId }, true) + ], + [filehashIdentifier] + ) + ) + } catch (error) { + return t.stringLiteral(`error`) + } + } + + return { + JSXOpeningElement: { + enter(path) { + const { node } = path + // 排除 Fragment + // TODO: 考虑 Fragment 重命名情况 + if (jsxHelpers.elementType(node).includes('Fragment')) { + return + } + + const hasKey = jsxHelpers.hasProp(node.attributes, 'key') + const keyAttr = jsxHelpers.getProp(node.attributes, 'key') + const keyAttrValue = get(keyAttr, 'value.value') + + // 排除 key 为以下的项,保证 SSR 时两端结果一致 + if ( + hasKey && + ['keep-alive-placeholder', 'keeper-container'].includes( + keyAttrValue + ) + ) { + return + } + + const isArrayElement = node.__isArrayElement + + // 不允许自定义 _ka 属性 + // DONE: 使用 key 属性替换,需考虑不覆盖 array 结构中的 key 属性,array 结构中保持 _ka 属性 + // 可参考:https://github.com/yannickcr/eslint-plugin-react/blob/master/lib/rules/jsx-key.js + const attributes = node.attributes.filter(attr => { + try { + return ( + attr.type !== 'JSXAttribute' || + jsxHelpers.propName(attr) !== '_ka' + ) + } catch (error) { + return true + } + }) + + const uuidName = + env !== 'production' || isArrayElement || hasKey ? '_ka' : 'key' + + node.attributes = [ + ...attributes, + jSXAttribute(jSXIdentifier(uuidName), genUUID(node)) + ] + } + } + } + } + + return { + visitor: { + Program: { + enter(path, { cwd, filename, file: { opts = {} } = {} }) { + const md5 = crypto.createHash('md5') + const filepath = + filename && filename.replace && cwd + ? filename.replace(cwd, '') + : opts.sourceFileName + md5.update(filepath) + const hash = md5.digest('base64').slice(0, 4) + const filehashIdentifier = path.scope.generateUidIdentifier( + 'filehash' + ) + + let filehashTemplate + + try { + filehashTemplate = template(`const %%filehash%% = %%hashString%%;`)( + { + filehash: filehashIdentifier, + hashString: t.stringLiteral(hash) + } + ) + } catch (error) { + filehashTemplate = template(`const ${filehashIdentifier.name} = '${hash}';`)() + } + + const imports = path.node.body.filter(node => + t.isImportDeclaration(node) + ) + + if (imports.length > 0) { + const insertPlace = imports[imports.length - 1] + const insertPlacePath = path.get( + `body.${path.node.body.indexOf(insertPlace)}` + ) + insertPlacePath.insertAfter(filehashTemplate) + } else { + const insertPlacePath = path.get(`body.0`) + if (insertPlacePath) { + insertPlacePath.insertBefore(filehashTemplate) + } + } + path.traverse(callExpressionVisitor) + path.traverse(getElementVisitor(filehashIdentifier)) + } + } + } + } +} diff --git a/src/core/AliveIdProvider.js b/src/core/AliveIdProvider.js index a58667e..38d7c66 100644 --- a/src/core/AliveIdProvider.js +++ b/src/core/AliveIdProvider.js @@ -8,7 +8,8 @@ import getKeyByFiberNode from './getKeyByFiberNode' export default class AliveIdProvider extends Component { id = null genId = () => { - this.id = getKeyByFiberNode(this._reactInternalFiber) + const { prefix = '' } = this.props + this.id = `${prefix}${getKeyByFiberNode(this._reactInternalFiber)}` return this.id } diff --git a/src/core/AliveScope.js b/src/core/AliveScope.js index 78ac55e..7a38f4d 100644 --- a/src/core/AliveScope.js +++ b/src/core/AliveScope.js @@ -71,19 +71,26 @@ export default class AliveScope extends Component { dropNodes = nodesId => new Promise(resolve => { - nodesId.forEach(id => { + const willDropNodes = nodesId.filter(id => { const cache = this.store.get(id) - const canDrop = get(cache, 'cached') + const canDrop = get(cache, 'cached') || get(cache, 'willDrop') if (canDrop) { // 用在多层 KeepAlive 同时触发 drop 时,避免触发深层 KeepAlive 节点的缓存生命周期 cache.willDrop = true this.nodes.delete(id) } + + return canDrop }) + if (willDropNodes.length === 0) { + resolve(false) + return + } + this.helpers = { ...this.helpers } - this.forceUpdate(resolve) + this.forceUpdate(() => resolve(true)) }) clear = () => this.dropNodes(this.getCachingNodes().map(({ id }) => id)) diff --git a/src/core/Bridge/Suspense.js b/src/core/Bridge/Suspense.js index e0b34f2..9083eb5 100644 --- a/src/core/Bridge/Suspense.js +++ b/src/core/Bridge/Suspense.js @@ -75,4 +75,4 @@ export const LazyBridge = isSupported } : SusNotSupported -export default isSupported ? SuspenseBridge : SusNotSupported +export default (isSupported ? SuspenseBridge : SusNotSupported) diff --git a/src/core/KeepAlive.js b/src/core/KeepAlive.js index 082d61f..df11c84 100644 --- a/src/core/KeepAlive.js +++ b/src/core/KeepAlive.js @@ -108,7 +108,7 @@ class KeepAlive extends Component { run(cache.revertScrollPos) } } catch (error) { - console.error(error) + // console.error(error) } } @@ -147,7 +147,7 @@ class KeepAlive extends Component { } }) } catch (error) { - console.error(error) + // console.error(error) } } @@ -218,12 +218,10 @@ class KeepAlive extends Component { ] needToDrop.forEach(cache => { - cache.cached = true cache.willDrop = true }) nextTick(() => _helpers.dropScopeByIds([id])) } else { - cache.cached = true cache.willDrop = true nextTick(() => _helpers.dropById(id)) } diff --git a/src/core/Keeper.js b/src/core/Keeper.js index f521d69..015a5a9 100644 --- a/src/core/Keeper.js +++ b/src/core/Keeper.js @@ -17,7 +17,7 @@ export default class Keeper extends Component { // 已存在检测,防止意外现象 if (store.has(id)) { return - } + } store.set(id, { listeners, diff --git a/src/core/getKeyByFiberNode.js b/src/core/getKeyByFiberNode.js index 45eea66..c6efe4d 100644 --- a/src/core/getKeyByFiberNode.js +++ b/src/core/getKeyByFiberNode.js @@ -1,34 +1,29 @@ -import { get, isObject } from '../helpers' +import { get, isObject, isString, getKey2Id } from '../helpers' -let uuid = 1 -const typeIdMap = new Map() +const isArrReg = /^iAr/ // 对每种 NodeType 做编号处理 -export const getTypeId = type => { - let typeId = typeIdMap.get(type) +const key2Id = getKey2Id() - if (!typeId) { - typeId = (++uuid).toString(32) - typeIdMap.set(type, typeId) - } - - return typeId -} // 获取节点的渲染路径,作为节点的 X 坐标 const genRenderPath = node => node.return ? [node, ...genRenderPath(node.return)] : [node] -// 使用节点 _ka 属性或下标与其 key 作为 Y 坐标 -// FIXME: 使用 index 作为 Y 坐标是十分不可靠的行为,待想出更好的法子替代 -const getNodeId = fiberNode => - `${get(fiberNode, 'pendingProps._ka', fiberNode.index)}:${fiberNode.key || - ''}` +// 使用节点 _ka 属性或下标与其 key/index 作为 Y 坐标 +const getNodeId = fiberNode => { + // FIXME: 使用 index 作为 Y 坐标是十分不可靠的行为,待想出更好的法子替代 + const id = get(fiberNode, 'key') || fiberNode.index + const ka = get(fiberNode, 'pendingProps._ka') + const isArray = isString(ka) && isArrReg.test(ka) + + return isArray ? `${ka}.${id}` : ka || id +} // 根据 X,Y 坐标生成 Key const getKeyByCoord = nodes => nodes .map(node => { - const x = getTypeId(get(node, 'type.$$typeof', node.type)) + const x = key2Id(get(node, 'type.$$typeof', node.type)) const y = getNodeId(node) return `${x},${y}` @@ -38,7 +33,7 @@ const getKeyByCoord = nodes => const getKeyByFiberNode = fiberNode => { const key = getKeyByCoord(genRenderPath(fiberNode)) - return getTypeId(key) + return key2Id(key) } export default getKeyByFiberNode diff --git a/src/core/withAliveScope.js b/src/core/withAliveScope.js index 7789e47..480dea2 100644 --- a/src/core/withAliveScope.js +++ b/src/core/withAliveScope.js @@ -7,6 +7,11 @@ import { Acceptor } from './Bridge' import AliveIdProvider from './AliveIdProvider' import { AliveScopeConsumer, aliveScopeContext } from './context' +function controllerCherryPick(controller) { + const { drop, dropScope, clear, getCachingNodes } = controller + return { drop, dropScope, clear, getCachingNodes } +} + export const expandKeepAlive = KeepAlive => { const renderContent = ({ id, helpers, props }) => ( @@ -16,19 +21,19 @@ export const expandKeepAlive = KeepAlive => { ) - function HookExpand(props) { + function HookExpand({ id: idPrefix, ...props }) { const helpers = useContext(aliveScopeContext) return ( - + {id => renderContent({ id, helpers, props })} ) } - function WithExpand(props) { + function WithExpand({ id: idPrefix, ...props }) { return ( - + {id => ( {helpers => renderContent({ id, helpers, props })} @@ -47,11 +52,10 @@ const withAliveScope = WrappedComponent => { ) function HookStore({ forwardedRef, ...props }) { - const { drop, dropScope, clear, getCachingNodes } = - useContext(aliveScopeContext) || {} + const controller = useContext(aliveScopeContext) || {} return renderContent({ - helpers: { drop, dropScope, clear, getCachingNodes }, + helpers: controllerCherryPick(controller), props, forwardedRef }) @@ -60,9 +64,9 @@ const withAliveScope = WrappedComponent => { function WithStore({ forwardedRef, ...props }) { return ( - {({ drop, dropScope, clear, getCachingNodes } = {}) => + {(controller = {}) => renderContent({ - helpers: { drop, dropScope, clear, getCachingNodes }, + helpers: controllerCherryPick(controller), props, forwardedRef }) @@ -95,8 +99,7 @@ export const useAliveController = () => { return {} } - const { drop, dropScope, clear, getCachingNodes } = ctxValue - return { drop, dropScope, clear, getCachingNodes } + return controllerCherryPick(ctxValue) } export default withAliveScope diff --git a/src/helpers/utils.js b/src/helpers/utils.js index 40b6e67..3d51216 100644 --- a/src/helpers/utils.js +++ b/src/helpers/utils.js @@ -26,3 +26,20 @@ export const debounce = (func, wait = 16) => { return timeout } } + +export function getKey2Id() { + let uuid = 0 + const map = new Map() + + // 对每种 NodeType 做编号处理 + return function key2Id(key) { + let id = map.get(key) + + if (!id) { + id = (++uuid).toString(32) + map.set(key, id) + } + + return id + } +} diff --git a/src/index.js b/src/index.js index 696178d..eb9a440 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ import withAliveScope, { useAliveController } from './core/withAliveScope' export default KeepAlive export { + KeepAlive, AliveScope, withActivation, fixContext, diff --git a/yarn.lock b/yarn.lock index 2572fc7..fb673f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -745,6 +745,14 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +array-includes@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" + integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0= + dependencies: + define-properties "^1.1.2" + es-abstract "^1.7.0" + babel-plugin-dynamic-import-node@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" @@ -812,6 +820,14 @@ core-js-compat@^3.1.1: browserslist "^4.6.6" semver "^6.3.0" +create-react-context@^0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz#546dede9dc422def0d3fc2fe03afe0bc0f4f7d8c" + integrity sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw== + dependencies: + gud "^1.0.0" + warning "^4.0.3" + debug@^4.1.0: version "4.1.1" resolved "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" @@ -819,7 +835,7 @@ debug@^4.1.0: dependencies: ms "^2.1.1" -define-properties@^1.1.2: +define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -831,6 +847,31 @@ electron-to-chromium@^1.3.247: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.253.tgz#bc3b2c94c2a109c08d37b04f526dc05fdabcbb5b" integrity sha512-LAwFRWViiiCSxQ2Lj3mnyEP8atkpAoHSPUnkFoy4mNabbnPHxtfseWvPCGGhewjHQI+ky/V4LdlTyyI0d3YPXA== +es-abstract@^1.7.0: + version "1.14.2" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.14.2.tgz#7ce108fad83068c8783c3cdf62e504e084d8c497" + integrity sha512-DgoQmbpFNOofkjJtKwr87Ma5EW4Dc8fWhD0R+ndq7Oc456ivUfGOOP6oAZTTKl5/CcNMP+EN+e3/iUzgE0veZg== + dependencies: + es-to-primitive "^1.2.0" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.0" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-inspect "^1.6.0" + object-keys "^1.1.1" + string.prototype.trimleft "^2.0.0" + string.prototype.trimright "^2.0.0" + +es-to-primitive@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" + integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -856,6 +897,11 @@ globals@^11.1.0: resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -866,6 +912,20 @@ has-symbols@^1.0.0: resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= +has@^1.0.1, has@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hoist-non-react-statics@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" + integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA== + dependencies: + react-is "^16.7.0" + invariant@^2.2.2: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -873,11 +933,42 @@ invariant@^2.2.2: dependencies: loose-envify "^1.0.0" +is-callable@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" + integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== + +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= + is-module@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= +is-reference@^1.1.2: + version "1.1.4" + resolved "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz#3f95849886ddb70256a3e6d062b1a68c13c51427" + integrity sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw== + dependencies: + "@types/estree" "0.0.39" + +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= + dependencies: + has "^1.0.1" + +is-symbol@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" + integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + dependencies: + has-symbols "^1.0.0" + jest-worker@^24.0.0: version "24.9.0" resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5" @@ -913,6 +1004,14 @@ json5@^2.1.0: dependencies: minimist "^1.2.0" +jsx-ast-utils@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz#4d4973ebf8b9d2837ee91a8208cc66f3a2776cfb" + integrity sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ== + dependencies: + array-includes "^3.0.3" + object.assign "^4.1.0" + lodash@^4.17.13: version "4.17.15" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" @@ -925,6 +1024,13 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +magic-string@^0.25.2: + version "0.25.3" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.3.tgz#34b8d2a2c7fec9d9bdf9929a3fd81d271ef35be9" + integrity sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA== + dependencies: + sourcemap-codec "^1.4.4" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -947,7 +1053,12 @@ node-releases@^1.1.29: dependencies: semver "^5.3.0" -object-keys@^1.0.11, object-keys@^1.0.12: +object-inspect@^1.6.0: + version "1.6.0" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" + integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== @@ -972,6 +1083,11 @@ private@^0.1.6: resolved "https://registry.npmjs.org/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== +react-is@^16.7.0: + version "16.9.0" + resolved "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz#21ca9561399aad0ff1a7701c01683e8ca981edcb" + integrity sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw== + regenerate-unicode-properties@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" @@ -1020,7 +1136,7 @@ regjsparser@^0.6.0: dependencies: jsesc "~0.5.0" -resolve@^1.10.0, resolve@^1.3.2: +resolve@^1.10.0, resolve@^1.11.0, resolve@^1.3.2: version "1.12.0" resolved "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== @@ -1035,6 +1151,17 @@ rollup-plugin-babel@^4.3.2: "@babel/helper-module-imports" "^7.0.0" rollup-pluginutils "^2.8.1" +rollup-plugin-commonjs@^10.1.0: + version "10.1.0" + resolved "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz#417af3b54503878e084d127adf4d1caf8beb86fb" + integrity sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q== + dependencies: + estree-walker "^0.6.1" + is-reference "^1.1.2" + magic-string "^0.25.2" + resolve "^1.11.0" + rollup-pluginutils "^2.8.1" + rollup-plugin-node-resolve@^4.2.3: version "4.2.4" resolved "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-4.2.4.tgz#7d370f8d6fd3031006a0032c38262dd9be3c6250" @@ -1101,6 +1228,27 @@ source-map@~0.6.1: resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +sourcemap-codec@^1.4.4: + version "1.4.6" + resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz#e30a74f0402bad09807640d39e971090a08ce1e9" + integrity sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg== + +string.prototype.trimleft@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634" + integrity sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string.prototype.trimright@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58" + integrity sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -1155,3 +1303,10 @@ unicode-property-aliases-ecmascript@^1.0.4: version "1.0.5" resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57" integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw== + +warning@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0"