├── .babelrc ├── .gitignore ├── .npmignore ├── README.md ├── example ├── demo │ ├── .babelrc │ ├── .eslintrc.js │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── rux.config.js │ ├── src │ │ ├── app.scss │ │ ├── app.tsx │ │ ├── assets │ │ │ ├── images │ │ │ │ └── alien.jpg │ │ │ └── styles │ │ │ │ └── common.scss │ │ ├── asyncRouter.js │ │ ├── index.js │ │ ├── mock.js │ │ ├── model │ │ │ └── index.ts │ │ └── page │ │ │ ├── goodsList │ │ │ ├── index.scss │ │ │ └── index.tsx │ │ │ ├── home │ │ │ ├── index.scss │ │ │ └── index.tsx │ │ │ ├── input │ │ │ └── index.tsx │ │ │ └── lifecycle │ │ │ ├── index.tsx │ │ │ └── style.scss │ └── tsconfig.json └── renderRoutesDemo.js ├── index.js ├── md ├── 111.gif ├── form.gif ├── lifecycle.gif └── scroll.gif ├── package-lock.json ├── package.json ├── rollup.config.js └── src ├── components ├── keepCache.js ├── keepliveRoute.js └── keepliveRouteSwitch.js ├── core ├── cacheContext.js └── keeper.js ├── hoc ├── extendsSelf.js └── lifecycle.js ├── index.js └── utils ├── const.js └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "presets": [ 4 | "@babel/preset-env", 5 | "@babel/preset-react" 6 | ], 7 | "plugins":[ 8 | "@babel/plugin-proposal-class-properties" 9 | ] 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | node_modules 3 | src 4 | md 5 | .babelrc 6 | .gitignore 7 | .npmignore 8 | .prettierrc 9 | rollup.config.js 10 | yarn.lock 11 | example -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![npm version](https://img.shields.io/npm/v/react-keepalive-router.svg?style=flat-square)](https://www.npmjs.org/package/react-keepalive-router) 4 | [![NPM Downloads](https://badgen.net/npm/dm/react-keepalive-router)](https://npmjs.org/package/react-keepalive-router) 5 | ![](https://img.shields.io/github/stars/GoodLuckAlien/react-keepalive-router.svg?style=social&label=Star) 6 | 7 | # react-keepalive-router 8 | 9 | 10 | ## 一 介绍 11 | 12 | 基于`react 16.8+` ,`react-router 4+` 开发的`react`缓存组件,可以用于缓存页面组件,类似`vue`的`keepalive`包裹`vue-router`的效果功能。 13 | 14 | 采用`react hooks`全新`api`,支持缓存路由,手动解除缓存,增加了**缓存的状态周期**,监听函数等。 15 | 16 | 后续版本会完善其他功能。 17 | 18 | 19 | 20 | ### demo 21 | 22 | #### 缓存组件 + 监听 23 | 24 | 25 | ## 二 快速上手 26 | 27 | 28 | ### 下载 29 | 30 | ```bash 31 | npm install react-keepalive-router --save 32 | # or 33 | yarn add react-keepalive-router 34 | ``` 35 | 36 | 37 | ### 使用 38 | 39 | ### 1 基本用法 40 | 41 | 42 | #### KeepaliveRouterSwitch 43 | 44 | 45 | `KeepaliveRouterSwitch`可以理解为常规的Switch,也可以理解为 `keepaliveScope`,我们**确保整个缓存作用域,只有一个 `KeepaliveRouterSwitch` 就可以了**。 46 | 47 | #### 常规用法 48 | 49 | ````jsx 50 | import { BrowserRouter as Router, Route, Redirect ,useHistory } from 'react-router-dom' 51 | import { KeepaliveRouterSwitch ,KeepaliveRoute ,addKeeperListener } from 'react-keepalive-router' 52 | 53 | const index = () => { 54 | useEffect(()=>{ 55 | /* 增加缓存监听器 */ 56 | addKeeperListener((history,cacheKey)=>{ 57 | if(history)console.log('当前激活状态缓存组件:'+ cacheKey ) 58 | }) 59 | },[]) 60 | return
61 |
62 | 63 | 64 | 65 | 66 | 67 | { /* 我们将详情页加入缓存 */ } 68 | 69 | 70 | 71 | 72 |
73 |
74 | } 75 | ```` 76 | 77 | 78 | 这里应该注意⚠️的是对于复杂的路由结构。或者KeepaliveRouterSwitch 包裹的子组件不是Route ,我们要给 `KeepaliveRouterSwitch` 增加特有的属性 `withoutRoute` 就可以了。如下例子🌰🌰🌰: 79 | 80 | **例子一** 81 | 82 | ````jsx 83 | 84 |
85 | 86 | 87 | 88 |
89 |
90 | 91 | ```` 92 | 93 | **例子二** 94 | 95 | 或者我们可以使用 `renderRoutes` 等`api`配合 `KeepliveRouterSwitch` 使用 。 96 | 97 | ````jsx 98 | import {renderRoutes} from "react-router-config" 99 | { renderRoutes(routes) } 100 | ```` 101 | 102 | 103 | #### KeepaliveRoute 104 | 105 | `KeepaliveRoute` 基本使用和 `Route`没有任何区别。 106 | 107 | 108 | **在当前版本中⚠️⚠️⚠️如果 `KeepaliveRoute` 如果没有被 `KeepaliveRouterSwitch`包裹就会失去缓存作用。** 109 | 110 | **效果** 111 | 112 | ![demo演示](https://raw.githubusercontent.com/AlienZhaolin/react-keepalive-router/master/md/111.gif) 113 | 114 | 115 | ![demo演示1](https://raw.githubusercontent.com/AlienZhaolin/react-keepalive-router/master/md/form.gif) 116 | 117 | ### 2 其他功能 118 | 119 | 120 | 121 | #### 1 缓存组件激活监听器 122 | 123 | 如果我们希望对当前激活的组件,有一些额外的操作,我们可以添加监听器,用来监听缓存组件的激活状态。 124 | 125 | ````js 126 | addKeeperListener((history,cacheKey)=>{ 127 | if(history)console.log('当前激活状态缓存组件:'+ cacheKey ) 128 | }) 129 | ```` 130 | 第一个参数未history对象,第二个参数为当前缓存路由的唯一标识cacheKey 131 | 132 | #### 2 清除缓存 133 | 134 | 缓存的组件,或是被`route`包裹的组件,会在`props`增加额外的方法`cacheDispatch`用来清除缓存。 135 | 136 | 如果props没有`cacheDispatch`方法,可以通过 137 | 138 | 139 | ````js 140 | 141 | 142 | import React from 'react' 143 | import { useCacheDispatch } from 'react-keepalive-router' 144 | 145 | function index(){ 146 | const cacheDispatch = useCacheDispatch() 147 | return
我是首页 148 | 149 |
150 | } 151 | 152 | export default index 153 | ```` 154 | 155 | **1 清除所有缓存** 156 | 157 | ````js 158 | cacheDispatch({ type:'reset' }) 159 | ```` 160 | 161 | **2 清除单个缓存** 162 | 163 | ````js 164 | cacheDispatch({ type:'reset',payload:'cacheId' }) 165 | ```` 166 | 167 | **3 清除多个缓存** 168 | 169 | ````js 170 | cacheDispatch({ type:'reset',payload:['cacheId1','cacheId2'] }) 171 | ```` 172 | 173 | #### 3 缓存scroll ,增加缓存滚动条功能 174 | 175 | 如果我们想要缓存列表 `scrollTop` 的位置 ,我们可以在 `KeepaliveRoute` 动态添加 `scroll` 属性 ( 目前仅支持y轴 )。 为什么加入`scroll`,我们这里考虑到,只有在想要缓存`scroll`的y值的时候,才进行缓存,避免不必要的事件监听和内存开销。 176 | 177 | ````js 178 | 179 | ```` 180 | 181 | **效果** 182 | 183 | ![scroll demo演示](https://raw.githubusercontent.com/AlienZhaolin/react-keepalive-router/master/md/scroll.gif) 184 | 185 | 186 | 187 | #### 4 生命周期 188 | 189 | `react-keepalive-router`加入了全新的页面组件生命周期 `actived` 和 `unActived`, `actived` 作为缓存路由组件激活时候用,初始化的时候会默认执行一次 , `unActived`作为路由组件缓存完成后调用。但是生命周期需要用一个`HOC`组件`keepaliveLifeCycle`包裹。 190 | 191 | 使用: 192 | 193 | 194 | 195 | 196 | ````js 197 | import React from 'react' 198 | 199 | import { keepaliveLifeCycle } from 'react-keepalive-router' 200 | import './style.scss' 201 | 202 | @keepaliveLifeCycle 203 | class index extends React.Component{ 204 | 205 | state={ 206 | activedNumber:0, 207 | unActivedNumber:0 208 | } 209 | actived(){ 210 | this.setState({ 211 | activedNumber:this.state.activedNumber + 1 212 | }) 213 | } 214 | unActived(){ 215 | this.setState({ 216 | unActivedNumber:this.state.unActivedNumber + 1 217 | }) 218 | } 219 | render(){ 220 | const { activedNumber , unActivedNumber } = this.state 221 | return
222 |
页面 actived 次数: { activedNumber }
223 |
页面 unActived 次数:{ unActivedNumber }
224 |
225 | } 226 | } 227 | 228 | export default index 229 | ```` 230 | 231 | 效果: 232 | 233 | 234 | ![lifecycle demo演示](https://raw.githubusercontent.com/AlienZhaolin/react-keepalive-router/master/md/lifecycle.gif) 235 | 236 | 这里注意的是 `keepaliveLifeCycle` 要是组件最近的 `Hoc`。 237 | 238 | 比如 239 | 240 | 装饰器模式下: 241 | **🙅错误做法** 242 | ````js 243 | @keepaliveLifeCycle 244 | @withStyles(styles) 245 | @withRouter 246 | class Index extends React.Componen{ 247 | 248 | } 249 | ```` 250 | 251 | **🙆正确做法** 252 | ````js 253 | @withStyles(styles) 254 | @withRouter 255 | @keepaliveLifeCycle 256 | class Index extends React.Componen{ 257 | 258 | } 259 | ```` 260 | 261 | 非装饰器模式下: 262 | **🙅错误做法** 263 | ````js 264 | class Index extends React.Componen{ 265 | 266 | } 267 | 268 | export default keepaliveLifeCycle( withRouter(Index) ) 269 | ```` 270 | 271 | **🙆正确做法** 272 | ````js 273 | class Index extends React.Componen{ 274 | 275 | } 276 | 277 | export default withRouter( keepaliveLifeCycle(Index) ) 278 | ```` -------------------------------------------------------------------------------- /example/demo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules":"commonjs", 7 | "targets": { 8 | "chrome": "67" 9 | }, 10 | "useBuiltIns": "usage", 11 | "corejs": 2 12 | } 13 | ], 14 | "@babel/preset-react" 15 | ], 16 | "plugins": [ 17 | "@babel/plugin-proposal-class-properties", 18 | [ 19 | "@babel/plugin-transform-runtime", 20 | { 21 | "absoluteRuntime": false, 22 | "helpers": true, 23 | "regenerator": true, 24 | "useESModules": false 25 | } 26 | ], 27 | "@babel/plugin-syntax-class-properties", 28 | ["import", { 29 | "libraryName": 30 | "antd", 31 | "libraryDirectory": "es", 32 | "style": true 33 | }] 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /example/demo/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "globals": { 9 | "$": true, 10 | "process": true, 11 | "__dirname": true 12 | }, 13 | "parser": "babel-eslint", 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "experimentalObjectRestSpread": true, 17 | "jsx": true 18 | }, 19 | "sourceType": "module", 20 | "ecmaVersion": 7 21 | }, 22 | "plugins": [ 23 | "react" 24 | ], 25 | "rules": { 26 | "quotes": [2, "single"], //单引号 27 | "no-console": 0, //不禁用console 28 | "no-debugger": 2, //禁用debugger 29 | "no-var": 0, //对var警告 30 | "semi": 0, //不强制使用分号 31 | "no-irregular-whitespace": 0, //不规则的空白不允许 32 | "no-trailing-spaces": 1, //一行结束后面有空格就发出警告 33 | "eol-last": 0, //文件以单一的换行符结束 34 | "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], //不能有声明后未被使用的变量或参数 35 | "no-underscore-dangle": 0, //标识符不能以_开头或结尾 36 | "no-alert": 2, //禁止使用alert confirm prompt 37 | "no-lone-blocks": 0, //禁止不必要的嵌套块 38 | "no-class-assign": 2, //禁止给类赋值 39 | "no-cond-assign": 2, //禁止在条件表达式中使用赋值语句 40 | "no-const-assign": 2, //禁止修改const声明的变量 41 | "no-delete-var": 2, //不能对var声明的变量使用delete操作符 42 | "no-dupe-keys": 2, //在创建对象字面量时不允许键重复 43 | "no-duplicate-case": 2, //switch中的case标签不能重复 44 | "no-dupe-args": 2, //函数参数不能重复 45 | "no-empty": 2, //块语句中的内容不能为空 46 | "no-func-assign": 2, //禁止重复的函数声明 47 | "no-invalid-this": 0, //禁止无效的this,只能用在构造器,类,对象字面量 48 | "no-redeclare": 2, //禁止重复声明变量 49 | "no-spaced-func": 2, //函数调用时 函数名与()之间不能有空格 50 | "no-this-before-super": 0, //在调用super()之前不能使用this或super 51 | "no-undef": 2, //不能有未定义的变量 52 | "no-use-before-define": 2, //未定义前不能使用 53 | "camelcase": 0, //强制驼峰法命名 54 | "jsx-quotes": [2, "prefer-double"], //强制在JSX属性(jsx-quotes)中一致使用双引号 55 | "react/display-name": 0, //防止在React组件定义中丢失displayName 56 | "react/forbid-prop-types": [2, {"forbid": ["any"]}], //禁止某些propTypes 57 | "react/jsx-boolean-value": 2, //在JSX中强制布尔属性符号 58 | "react/jsx-closing-bracket-location": 1, //在JSX中验证右括号位置 59 | "react/jsx-curly-spacing": [2, {"when": "never", "children": true}], //在JSX属性和表达式中加强或禁止大括号内的空格。 60 | "react/jsx-indent-props": [2, 4], //验证JSX中的props缩进 61 | "react/jsx-key": 2, //在数组或迭代器中验证JSX具有key属性 62 | "react/jsx-max-props-per-line": [1, {"maximum": 1}], // 限制JSX中单行上的props的最大数量 63 | "react/jsx-no-bind": 0, //JSX中不允许使用箭头函数和bind 64 | "react/jsx-no-duplicate-props": 2, //防止在JSX中重复的props 65 | "react/jsx-no-literals": 0, //防止使用未包装的JSX字符串 66 | "react/jsx-no-undef": 1, //在JSX中禁止未声明的变量 67 | "react/jsx-pascal-case": 0, //为用户定义的JSX组件强制使用PascalCase 68 | "react/jsx-sort-props": 2, //强化props按字母排序 69 | "react/jsx-uses-react": 1, //防止反应被错误地标记为未使用 70 | "react/jsx-uses-vars": 2, //防止在JSX中使用的变量被错误地标记为未使用 71 | "react/no-danger": 0, //防止使用危险的JSX属性 72 | "react/no-did-mount-set-state": 0, //防止在componentDidMount中使用setState 73 | "react/no-did-update-set-state": 1, //防止在componentDidUpdate中使用setState 74 | "react/no-direct-mutation-state": 2, //防止this.state的直接变异 75 | "react/no-multi-comp": 2, //防止每个文件有多个组件定义 76 | "react/no-set-state": 0, //防止使用setState 77 | "react/no-unknown-property": 2, //防止使用未知的DOM属性 78 | "react/prefer-es6-class": 2, //为React组件强制执行ES5或ES6类 79 | "react/prop-types": 0, //防止在React组件定义中丢失props验证 80 | "react/react-in-jsx-scope": 2, //使用JSX时防止丢失React 81 | "react/self-closing-comp": 0, //防止没有children的组件的额外结束标签 82 | "react/sort-comp": 2, //强制组件方法顺序 83 | "no-extra-boolean-cast": 0, //禁止不必要的bool转换 84 | "react/no-array-index-key": 0, //防止在数组中遍历中使用数组key做索引 85 | "react/no-deprecated": 1, //不使用弃用的方法 86 | "react/jsx-equals-spacing": 2, //在JSX属性中强制或禁止等号周围的空格 87 | "no-unreachable": 1, //不能有无法执行的代码 88 | "comma-dangle": 2, //对象字面量项尾不能有逗号 89 | "no-mixed-spaces-and-tabs": 0, //禁止混用tab和空格 90 | "prefer-arrow-callback": 0, //比较喜欢箭头回调 91 | "arrow-parens": 0, //箭头函数用小括号括起来 92 | "arrow-spacing": 0 //=>的前/后括号 93 | }, 94 | "settings": { 95 | "import/ignore": [ 96 | "node_modules" 97 | ] 98 | } 99 | } -------------------------------------------------------------------------------- /example/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 |
16 | 17 | -------------------------------------------------------------------------------- /example/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-keepalive-router-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "rux start", 8 | "build": "rux build" 9 | }, 10 | "author": "zhaolin", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@babel/plugin-syntax-class-properties": "^7.12.1", 14 | "@types/react": "^16.9.25", 15 | "antd": "^4.9.3", 16 | "react": "^16.8.1", 17 | "react-dom": "^16.8.1", 18 | "react-keepalive-router": "^1.1.2", 19 | "react-router": "^5.1.2", 20 | "react-router-dom": "^5.2.0", 21 | "react-tiny-virtual-list": "^2.2.0", 22 | "ruxjs": "0.0.2" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "^7.5.4", 26 | "@babel/plugin-transform-runtime": "^7.5.0", 27 | "@babel/preset-env": "^7.5.4", 28 | "@babel/preset-react": "^7.0.0", 29 | "@types/webpack-env": "^1.15.1", 30 | "babel-eslint": "^10.1.0", 31 | "babel-loader": "^8.0.6", 32 | "babel-plugin-import": "^1.13.0", 33 | "css-hot-loader": "^1.4.4", 34 | "css-loader": "^3.0.0", 35 | "eslint": "^6.8.0", 36 | "eslint-plugin-react": "^7.19.0", 37 | "file-loader": "^6.0.0", 38 | "happypack": "^5.0.1", 39 | "node-sass": "^4.13.1", 40 | "postcss-loader": "^3.0.0", 41 | "rux-react-webpack-plugin": "^1.0.3", 42 | "sass-loader": "^7.3.1", 43 | "style-loader": "^0.23.1", 44 | "ts-loader": "^4.5.0", 45 | "typescript": "^3.9.3", 46 | "url-loader": "^4.0.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /example/demo/rux.config.js: -------------------------------------------------------------------------------- 1 | module.exports ={ 2 | dev:{ 3 | }, 4 | pro:{ 5 | }, 6 | base:{ 7 | } 8 | } -------------------------------------------------------------------------------- /example/demo/src/app.scss: -------------------------------------------------------------------------------- 1 | .page{ 2 | position: fixed; 3 | left:0; 4 | right: 0; 5 | top:0; 6 | bottom: 0; 7 | background-color: #ffff; 8 | 9 | } 10 | .content{ 11 | position: absolute; 12 | left:50%; 13 | top: 50%; 14 | transform: translate(-50%,-50%); 15 | } 16 | .title{ 17 | text-align: center; 18 | font-size: 10px; 19 | color: #ccc; 20 | } 21 | .btns{ 22 | padding-left: 30px; 23 | button{ 24 | margin-right: 30px; 25 | } 26 | } 27 | .routerLink{ 28 | margin-left: 15px; 29 | color: #0366d6; 30 | font-size: 14px; 31 | } 32 | .theStyle{ 33 | height: 50px; 34 | background-color: #fff; 35 | position: fixed; 36 | left:0; 37 | right:0; 38 | top:0; 39 | z-index: 10000; 40 | } -------------------------------------------------------------------------------- /example/demo/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './app.scss' 3 | import { BrowserRouter as Router, Route ,useHistory , Redirect } from 'react-router-dom' 4 | // import { KeepaliveRouterSwitch ,KeepaliveRoute} from './keep-src/index' 5 | 6 | import { KeepaliveRouterSwitch ,KeepaliveRoute} from 'react-keepalive-router' 7 | import Detail from './page/input' 8 | import List from './page/lifecycle' 9 | import TheIndex from '../src/page/home/index' 10 | import List2 from './page/goodsList' 11 | 12 | const menusList = [ 13 | { 14 | name: '首页', 15 | path: '/home' 16 | }, 17 | { 18 | name: '生命周期demo', 19 | path: '/list' 20 | }, 21 | { 22 | name: '缓存列表demo', 23 | path: '/list2' 24 | }, 25 | { 26 | name: '表单demo', 27 | path: '/detail' 28 | }, 29 | ] 30 | 31 | 32 | function Meuns(){ 33 | const history = useHistory() 34 | return
35 | { menusList.map(item=> { history.push(item.path) } } key={item.path} >{ item.name }) } 36 |
37 | } 38 | 39 | const RouteWithSubRoutes = (item)=>
40 | 41 | const index = () => { 42 | return
43 |
44 | 45 | 46 | 47 | { 48 | [{ path:'/detail' ,component:Detail }].map(item=> ) 49 | } 50 | 51 | 52 | 53 | {/* 路由不匹配,重定向到/index */} 54 | 55 | 56 | 57 |
58 |
59 | } 60 | 61 | export default index -------------------------------------------------------------------------------- /example/demo/src/assets/images/alien.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoodLuckAlien/react-keepalive-router/511132c882629a75458e4391dcf44ea8705bf9da/example/demo/src/assets/images/alien.jpg -------------------------------------------------------------------------------- /example/demo/src/assets/styles/common.scss: -------------------------------------------------------------------------------- 1 | #app{ 2 | height: 100%; 3 | width: 100%; 4 | section{ 5 | height: 100%; 6 | width: 100%; 7 | } 8 | } 9 | #components-layout-demo-custom-trigger .trigger { 10 | font-size: 18px; 11 | line-height: 64px; 12 | padding: 0 24px; 13 | cursor: pointer; 14 | transition: color 0.3s; 15 | } 16 | 17 | #components-layout-demo-custom-trigger .trigger:hover { 18 | color: #1890ff; 19 | } 20 | 21 | #components-layout-demo-custom-trigger .logo { 22 | height: 32px; 23 | background: rgba(255, 255, 255, 0.2); 24 | margin: 16px; 25 | } 26 | 27 | .site-layout .site-layout-background { 28 | background: #fff; 29 | } -------------------------------------------------------------------------------- /example/demo/src/asyncRouter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const routerObserveQueue = [] 4 | 5 | /* 懒加载路由卫士钩子 */ 6 | export const RouterHooks = { 7 | /* 路由组件加载之前 */ 8 | beforeRouterComponentLoad: function (callback) { 9 | routerObserveQueue.push({ 10 | type: 'before', 11 | callback 12 | }) 13 | }, 14 | /* 路由组件加载之后 */ 15 | afterRouterComponentDidLoaded(callback) { 16 | routerObserveQueue.push({ 17 | type: 'after', 18 | callback 19 | }) 20 | } 21 | } 22 | 23 | /* 路由懒加载HOC */ 24 | export default function AsyncRouter(loadRouter) { 25 | return class Content extends React.Component { 26 | constructor(props) { 27 | super(props) 28 | this.dispatchRouterQueue('before') 29 | } 30 | state = { 31 | Component: null 32 | } 33 | dispatchRouterQueue(type) { 34 | const { 35 | history 36 | } = this.props 37 | routerObserveQueue.forEach(item => { 38 | if (item.type === type) item.callback(history) 39 | }) 40 | } 41 | componentDidMount() { 42 | if (this.state.Component) return 43 | loadRouter() 44 | .then(module => module.default) 45 | .then(Component => this.setState({ 46 | Component 47 | }, 48 | () => { 49 | this.dispatchRouterQueue('after') 50 | })) 51 | } 52 | render() { 53 | const { 54 | Component 55 | } = this.state 56 | return Component ? < Component { 57 | ...this.props 58 | } 59 | /> : null 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /example/demo/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './app' 4 | import rux from 'ruxjs' 5 | import './assets/styles/common.scss' 6 | 7 | 8 | ReactDOM.render( 9 | rux.create({}, () => < App / > ), 10 | document.getElementById('app') 11 | ) 12 | 13 | -------------------------------------------------------------------------------- /example/demo/src/mock.js: -------------------------------------------------------------------------------- 1 | export const listData = { 2 | "code" : 200, 3 | "message" : "success", 4 | "data" : [ { 5 | "skuId" : "1", 6 | "giftName" : "约斯夫家庭校园多功能创可贴卡通女少女可爱超弹防水透气弹力小面积开放性创伤创口贴 超弹防水透气型 100贴/盒", 7 | "giftImage" : "https://img14.360buyimg.com/n1/jfs/t1/117043/23/16493/438028/5f50a682E96819e0d/a3678e5c4fb5a3cf.jpg", 8 | "price" : "19.90", 9 | }, { 10 | "skuId" : "2", 11 | "giftName" : "【MaincareBio】医用外科口罩一次性无菌三层透气成人挂耳式防细菌病毒飞沫防护医用口罩 儿童医用外科口罩50只【10只/包*5包】", 12 | "giftImage" : "https://img14.360buyimg.com/n1/jfs/t1/133614/39/16312/128620/5fb3a1b8E02fec0c6/0b7d82a132932f35.jpg", 13 | "price" : "39.90", 14 | }, { 15 | "skuId" : "3", 16 | "giftName" : "乐樊一次性医用外科口罩医生专用成人通用三层医疗口罩透气单片防护 医用外科口罩100只蓝色【非独立包装/2包】", 17 | "giftImage" : "https://img14.360buyimg.com/n1/jfs/t1/151889/33/15018/129441/6008e066Ee813ef0d/1f1a8218fa30a05f.jpg", 18 | "price" : "31.90", 19 | }, { 20 | "skuId" : "4", 21 | "giftName" : "俏东方 一次性医用口罩白色 轻薄透气 三层防护含熔喷过滤成人男女适用冬季防护面罩 50只医用口罩白色整包(工厂特惠)", 22 | "giftImage" : "https://img14.360buyimg.com/n1/jfs/t1/164271/11/7365/212791/6032be25E162107e3/df794675c5095edf.jpg", 23 | "price" : "9.90", 24 | }, { 25 | "skuId" : "5", 26 | "giftName" : "【7仓隔日达】咔贝爱(KABEIAI)一次性医用防护口罩防尘防雾霾防颗粒物 三层防护透气医用口罩 医用口罩50只(1包)", 27 | "giftImage" : "https://img14.360buyimg.com/n1/jfs/t1/156216/4/9112/168310/601e5d2aE4ad9ee3b/65a25f358d136a20.jpg", 28 | "price" : "19.90", 29 | }, { 30 | "skuId" : "6", 31 | "giftName" : "康诺嘉口罩KN95一次性口罩5层防护日常防雾霾灰尘通用型男女口罩 50只", 32 | "giftImage" : "https://img14.360buyimg.com/n1/jfs/t1/161195/25/1659/131874/5ff7d289E597c8999/700182369a7bed58.jpg", 33 | "price" : "26.90", 34 | }, { 35 | "skuId" : "7", 36 | "giftName" : "拓家中药泡脚包艾草红花草益母草老姜当归泡脚包缓解疲劳泡脚粉 30小包/袋 随机发货 3袋(90包)", 37 | "giftImage" : "https://img14.360buyimg.com/n1/jfs/t1/168773/15/1476/92527/5ff6d15fE46e9b990/98acc32416799ab9.jpg", 38 | "price" : "29.90", 39 | }, { 40 | "skuId" : "8", 41 | "giftName" : "多美忆 2021新年装饰品窗贴春节装饰窗花牛年福字贴纸贴画家用室内商场店铺场景布置 春节窗贴(新年快乐)", 42 | "giftImage" : "https://img14.360buyimg.com/n1/jfs/t1/165342/17/1238/201103/5ff58107E7b42ee87/aab515ec5cb209b3.jpg", 43 | "price" : "7.90", 44 | }, { 45 | "skuId" : "9", 46 | "giftName" : "南极人【5双装】保暖袜子男加厚款秋冬季毛圈袜纯色长袜中筒毛巾袜棉袜男 加厚毛圈袜5色5双", 47 | "giftImage" : "https://img14.360buyimg.com/n1/jfs/t1/100815/34/3889/179336/5de36360E458679a3/af803962c81a6939.jpg", 48 | "price" : "12.90", 49 | } ], 50 | "totalCount" : 338, 51 | "pageCount" : 34, 52 | "currentPage" : 1 53 | } -------------------------------------------------------------------------------- /example/demo/src/model/index.ts: -------------------------------------------------------------------------------- 1 | 2 | function getData(){ 3 | return new Promise((resolve)=>setTimeout(()=>{ resolve(1) },100)).then(res=>res) 4 | } 5 | 6 | export default { 7 | state: { 8 | number: 1 9 | }, 10 | reducer: { 11 | numberAdd(state) { 12 | return { 13 | ...state, 14 | number: state.number + 1 15 | } 16 | }, 17 | numberDel(state) { 18 | return { 19 | ...state, 20 | number: state.number - 1 21 | } 22 | }, 23 | numberReset(state,{ payload }){ 24 | return { 25 | ...state, 26 | number: payload 27 | } 28 | } 29 | }, 30 | effect: { 31 | asyncnumberAdd(dispatch) { 32 | setTimeout(() => { 33 | dispatch({ 34 | type: 'numberAdd' 35 | }) 36 | }, 3000) 37 | }, 38 | async resetNumber(dispatch){ 39 | const res = await getData() 40 | dispatch({ 41 | type:'numberReset', 42 | payload:res 43 | }) 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /example/demo/src/page/goodsList/index.scss: -------------------------------------------------------------------------------- 1 | .list{ 2 | list-style: none; 3 | background-color: pink; 4 | padding: 10px 20px; 5 | color: #fff; 6 | height: 50px; 7 | line-height: 50px; 8 | box-sizing: border-box; 9 | margin-bottom: 10px; 10 | margin-left: 24px; 11 | margin-right: 24px; 12 | font-weight: bold; 13 | border-radius:10px ; 14 | } 15 | 16 | 17 | .list_box{ 18 | position: fixed; 19 | left:0; 20 | top:60px; 21 | overflow: scroll; 22 | bottom:0; 23 | right: 0; 24 | } 25 | 26 | .goods_item { 27 | line-height: 1!important; 28 | height: 134px; 29 | box-sizing: border-box; 30 | padding-bottom: 16px; 31 | display: flex; 32 | margin-bottom: 50px; 33 | position: relative; 34 | 35 | .newPerson { 36 | height: 34px; 37 | transform: translateY(7px); 38 | // top: -50px; 39 | width: 122px; 40 | } 41 | 42 | 43 | 44 | 45 | .item_image { 46 | height: 168px; 47 | width: 168px; 48 | transition: opacity 0.7s; 49 | border-radius: 12px; 50 | transform: translateY(-2px); 51 | } 52 | 53 | .item_content { 54 | flex: 1; 55 | box-sizing: border-box; 56 | position: relative; 57 | padding-left: 15px; 58 | 59 | .goods_name { 60 | font-family: PingFangSC-Regular; 61 | font-size: 14px; 62 | color: #303133; 63 | letter-spacing: 0; 64 | line-height: 18px; 65 | letter-spacing: 0; 66 | line-height: 18px; 67 | text-overflow: -o-ellipsis-lastline; 68 | overflow: hidden; 69 | text-overflow: ellipsis; 70 | word-break: break-all; 71 | display: -webkit-box; 72 | -webkit-line-clamp: 2; 73 | line-clamp: 2; 74 | min-height: 37px; 75 | -webkit-box-orient: vertical; 76 | margin-bottom: 7px; 77 | } 78 | 79 | // .go_share { 80 | // border-radius: 29px; 81 | // height: 58px; 82 | // width: 150px; 83 | // position: absolute; 84 | // bottom: 0; 85 | // right: 0; 86 | // line-height: 58px; 87 | // text-align: center; 88 | // font-family: HYYakuHei-GEW; 89 | // font-size: 26px; 90 | // color: #FFFFFF; 91 | // letter-spacing: 0; 92 | // } 93 | 94 | 95 | .new_price { 96 | position: relative; 97 | height: 25px; 98 | margin-bottom: 2px; 99 | display: inline-block; 100 | margin-left: -1px; 101 | transform: translateY(5px); 102 | .view { 103 | display: inline-block; 104 | font-family: JDZhengHT-Regular; 105 | color: #f12e40; 106 | letter-spacing: 0; 107 | 108 | } 109 | 110 | .one { 111 | transform: translateX(7px); 112 | } 113 | 114 | .two { 115 | font-size: 42px; 116 | line-height: 36px; 117 | transform: translateX(-1px); 118 | } 119 | 120 | .three { 121 | font-size: 26px; 122 | transform: translateX(-7px); 123 | } 124 | } 125 | 126 | .hold_price { 127 | height: 12px; 128 | } 129 | 130 | .old_price { 131 | display: inline-block; 132 | margin-left: 2px; 133 | font-family: JDZhengHT-Regular; 134 | font-size: 26px !important; 135 | color: #C0C4CC !important; 136 | letter-spacing: 0; 137 | transform: translate(-5px,4px); 138 | text-decoration: line-through; 139 | } 140 | 141 | .goods_tag { 142 | height: 30px; 143 | margin-bottom: 33px; 144 | transform: translateY(-2px); 145 | .ziying { 146 | width: 52px; 147 | height: 30px; 148 | margin-right: 5px; 149 | } 150 | 151 | .peisong { 152 | width: 96px; 153 | height: 30px; 154 | } 155 | } 156 | } 157 | } -------------------------------------------------------------------------------- /example/demo/src/page/goodsList/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { listData } from '../../mock' 3 | 4 | import './index.scss' 5 | 6 | 7 | class Index extends React.Component{ 8 | num = 0 9 | state = { 10 | list:[], 11 | renderList: [] /* 渲染列表 */ 12 | } 13 | 14 | componentDidMount() { 15 | this.setState({ 16 | list : listData.data 17 | }) 18 | } 19 | /* 处理滚动效果 */ 20 | render() { 21 | const { list } = this.state 22 | return
23 |
26 | {/* 显然区 */} 27 |
28 | { 29 | list.map((item:any, index) => ( 30 |
33 | 36 |
37 |
38 | {item.giftName} 39 |
40 |
41 |
42 |
43 |
44 | ¥ {item.price} 45 |
46 |
47 |
48 | 49 |
50 |
51 | )) 52 | } 53 |
54 |
55 | 56 |
57 | } 58 | } 59 | 60 | 61 | export default Index -------------------------------------------------------------------------------- /example/demo/src/page/home/index.scss: -------------------------------------------------------------------------------- 1 | .box{ 2 | margin-top: 50px; 3 | } 4 | 5 | .item{ 6 | height: 50px; 7 | line-height: 50px; 8 | border-radius: 20px; 9 | width: 200px; 10 | font-size: 15px; 11 | background-color: orange; 12 | color: #fff; 13 | font-weight: bold; 14 | text-align: center; 15 | margin-bottom: 20px; 16 | } -------------------------------------------------------------------------------- /example/demo/src/page/home/index.tsx: -------------------------------------------------------------------------------- 1 | //todo 2 | import React from 'react' 3 | import './index.scss' 4 | import { useCacheDispatch } from 'react-keepalive-router' 5 | 6 | class Index extends React.Component{ 7 | 8 | handerClick=(payload)=>{ 9 | const dispatch = useCacheDispatch() 10 | dispatch({ type:'reset' , payload }) 11 | } 12 | 13 | render(){ 14 | console.log(this.props) 15 | return
16 |
this.handerClick('/list')} 18 | >清除 生命周期
19 |
this.handerClick('/list2')} 21 | >清除 缓存列表
22 |
this.handerClick('/detail')} 24 | >清除 缓存表单
25 |
26 | } 27 | } 28 | 29 | 30 | 31 | 32 | 33 | export default Index -------------------------------------------------------------------------------- /example/demo/src/page/input/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | 4 | 5 | 6 | class Index extends React.Component{ 7 | constructor(prop){ 8 | super(prop) 9 | this.state = { 10 | list: [ { id:1 , name: 'xixi' } ,{ id:2 , name: 'haha' },{ id:3 , name: 'heihei' } ], 11 | number:1 12 | } 13 | } 14 | render(){ 15 | const { number }:any = this.state 16 | return
17 | 18 |
19 |