Commit ccbf3171 by Wee

feat: implement `forceUnmout`

parent dfeb9225
......@@ -73,7 +73,7 @@ There is a item list page, click on the items on this page will enter the item d
## Usage
### livePath: string or array
### livePath: (string | string[])
`livePath` is the path you want to hide the component instead of unmount it. The specific rules of `livePath` are the same as `path` props of Route in react-router-v4. You still can use `component` or `render` props to render a component.
......@@ -90,7 +90,7 @@ import LiveRoute from 'react-live-route'
<LiveRoute path="/list" livePath="/user/:id" component={List} />
```
### alwaysLive: bool
### alwaysLive: boolean
`alwaysLive` is just like `livePath`. The difference is the component will not be unmount on **any other location** after the it's first mount.
......@@ -103,11 +103,13 @@ import LiveRoute from 'react-live-route'
<LiveRoute path="/list" alwaysLive={true} component={Modal} />
```
### onHide: (routeState: {location, livePath, alwaysLive}) => any
### onHide: (location, match, livePath, alwaysLive) => any
This hook will be triggered when LiveRoute will hide in `componentWillReceiveProps` stage (so it happens before re-render).
### onReappear: (routeState: {location, livePath, alwaysLive}) => any
Example of usage is below.
### onReappear: (location, match, livePath, alwaysLive) => any
This hook will be triggered when LiveRoute will reappear from hide in `componentWillReceiveProps` stage (so it happens before re-render).
......@@ -118,21 +120,27 @@ import LiveRoute from 'react-live-route'
component={List}
livePath="/item/:id"
name="items"
onHide={routeState => {
onHide={(location, match, livePath, alwaysLive) => {
console.log('[on hide]')
console.log(routeState)
}}
onReappear={routeState => {
onReappear={(location, match, livePath, alwaysLive) => {
console.log('[on reappear]')
console.log(routeState)
}}
/>
```
## Todo
### forceUnmount: (location, match, livePath, alwaysLive) => boolean
forceUnmount is funtion that return a boolean value to decide weather to forceUnmount the LiveRoute no matter it is matched or should be kept lived.
For example: when the user id equals to `27`, List page will be force unmounted while routing on other value of id will be kept.
- [ ] support render
- [ ] add forceUnmount prop
```jsx
import LiveRoute from 'react-live-route'
<LiveRoute path="/list" livePath="/user/:id" component={List} forceUnmount={(location, match)=> match.params.id === 27}/>
```
## Licence
......
/* tslint:disable */
import * as React from 'react'
import LiveRoute from '../src/index'
import { Route, Link, Switch, Router } from 'react-router-dom'
......@@ -19,17 +21,31 @@ const componentGenerator = (name: string) =>
}
}
const renderPropsGenerator = (name: string) => () => <h1>{textGenerator(name)}</h1>
function App() {
return (
<div>
<LiveRoute path="/a" livePath="/b" component={componentGenerator('live-on-b')} />
<LiveRoute path="/a" livePath={['/b', '/c']} component={componentGenerator(`live-on-bc`)} />
<LiveRoute path="/a" alwaysLive={true} component={componentGenerator('always-live')} />
<LiveRoute
path="/a"
livePath={['/b', '/c', '/d']}
onHide={(location, match, livePath, alwaysLive) => {
// console.log(arguments)
}}
component={renderPropsGenerator('live-on-bcd')}
forceUnmount={(location, match) => {
if (location.pathname === '/d') return true
}}
/>
<LiveRoute path="/a" alwaysLive={true} component={renderPropsGenerator('always-live')} />
<Route path="/b" render={() => <h1>{textGenerator('b')}</h1>} />
<Route path="/c" render={() => <h1>{textGenerator('c')}</h1>} />
<Route path="/d" render={() => <h1>{textGenerator('d')}</h1>} />
<LinkGenerator to="a" />
<LinkGenerator to="b" />
<LinkGenerator to="c" />
<LinkGenerator to="d" />
</div>
)
}
......@@ -60,17 +76,21 @@ test('live route through different urls', () => {
const leftClick = { button: 0 }
const { container, getByTestId } = renderWithRouter(<App />)
routesLives(container, 'not', ['live-on-b', `live-on-bc`, 'always-live', 'b', 'c'])
routesLives(container, 'not', ['live-on-b', 'live-on-bcd', 'always-live', 'b', 'c'])
fireEvent.click(getByTestId('toA'), leftClick)
routesLives(container, 'yes', ['live-on-b', `live-on-bc`, 'always-live'])
routesLives(container, 'yes', ['live-on-b', 'live-on-bcd', 'always-live'])
routesLives(container, 'not', ['b', 'c'])
fireEvent.click(getByTestId('toB'), leftClick)
routesLives(container, 'yes', ['live-on-b', `live-on-bc`, 'always-live', 'b'])
routesLives(container, 'yes', ['live-on-b', 'live-on-bcd', 'always-live', 'b'])
routesLives(container, 'not', ['c'])
fireEvent.click(getByTestId('toC'), leftClick)
routesLives(container, 'yes', [`live-on-bc`, 'always-live', 'c'])
routesLives(container, 'yes', ['live-on-bcd', 'always-live', 'c'])
routesLives(container, 'not', ['live-on-b', 'b'])
fireEvent.click(getByTestId('toD'), leftClick)
routesLives(container, 'yes', ['always-live'])
routesLives(container, 'not', ['live-on-bcd', 'live-on-b', 'b', 'c'])
})
**react-router-v4 Route** 组件的加强版,可以保持路由的组件在路径不匹配时隐藏而不卸载,而在回到返回到匹配路径时完全恢复离开页面时的样子。
## ⚠️
中文文档并不保证同步到最新,以[英文版](../README.md)为准,中文版仅为方便阅读提供一点便利。
---
react-router-v4 Route** 组件的加强版,可以保持路由的组件在路径不匹配时隐藏而不卸载,而在回到返回到匹配路径时完全恢复离开页面时的样子。
## 文档
......@@ -93,12 +99,12 @@ import LiveRoute from 'react-live-route'
这个钩子函数会在 LiveRoute 将要从隐藏状态恢复显示时在 `componentWillReceiveProps` 周期触发。
### forceUnmount (WIP) 🚧
### forceUnmount
可以传入一个函数,当这个函数返回值为真时,可以强制路由对应的组件卸载,对应的函数签名为
```js
(props, params) => boolean
(location, match) => boolean
```
例如:当 user 的 id 为 27 时,List 页对应的组件要卸载掉,而在其他的页面正常渲染。
......
......@@ -20,6 +20,7 @@
"react": ">=15"
},
"dependencies": {
"history": "^4.9.0",
"invariant": "^2.2.4",
"prop-types": "^15.6.1",
"react": "^16.3.2",
......
/* tslint:disable:no-redundant-jsdoc */
// This project was originally written in JavaScript, since there's some Js doc remained.
import { Location } from 'history'
import * as invariant from 'invariant'
import * as PropTypes from 'prop-types'
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { isValidElementType } from 'react-is'
import { matchPath, RouteProps, Router } from 'react-router'
import { match, matchPath, RouteProps } from 'react-router'
import * as warning from 'warning'
const isEmptyChildren = children => React.Children.count(children) === 0
......@@ -19,13 +20,20 @@ enum LiveState {
}
type CacheDom = HTMLElement | null
type OnRoutingHook = (
location: Location,
match: match | null,
livePath: string | string[] | undefined,
alwaysLive: boolean
) => any
interface IProps extends RouteProps {
livePath?: string | string[]
alwaysLive: boolean
onHide?: Function
onReappear?: Function
onHide?: OnRoutingHook
onReappear?: OnRoutingHook
name?: string
forceUnmount?: OnRoutingHook
}
const debugLog = (message: any) => {
......@@ -191,12 +199,13 @@ class LiveRoute extends React.Component<IProps, any> {
const nextPropsWithLivePath = { ...nextProps, paths: livePath }
const prevMatch = this.computeMatch(props, this.context.router)
const livePathMatch = this.computePathsMatch(nextPropsWithLivePath, nextContext.router)
const _location = this.context.router.history.location
// normal matched render
if (match) {
debugLog('--- NORMAL MATCH FLAG ---')
// onReappear hook
if (this.liveState === LiveState.HIDE_RENDER && typeof this.props.onReappear === 'function') {
this.props.onReappear({ location, livePath, alwaysLive })
this.props.onReappear(_location, match, livePath, alwaysLive)
}
this.liveState = LiveState.NORMAL_RENDER_MATCHED
return match
......@@ -208,10 +217,11 @@ class LiveRoute extends React.Component<IProps, any> {
if (prevMatch) {
this._latestMatchedRouter = this.context.router
}
if (typeof this.props.onHide === 'function') {
this.props.onHide({ location, livePath, alwaysLive })
}
debugLog('--- HIDE FLAG ---')
// onHide hook
if (this.liveState === LiveState.NORMAL_RENDER_MATCHED && typeof this.props.onHide === 'function') {
this.props.onHide(_location, match, livePath, alwaysLive)
}
this.liveState = LiveState.HIDE_RENDER
this.saveScrollPosition()
this.hideRoute()
......@@ -325,11 +335,18 @@ class LiveRoute extends React.Component<IProps, any> {
public render() {
const { match } = this.state
const { children, component, render: propRender, livePath, alwaysLive, onHide } = this.props
const { children, component, render: propRender, livePath, alwaysLive, forceUnmount } = this.props
const { history, route, staticContext } = this.context.router
const location = this.props.location || route.location
const props = { match, location, history, staticContext }
// force unmount
if (typeof forceUnmount === 'function' && forceUnmount(location, match, livePath, alwaysLive)) {
this.clearScroll()
this.clearDomData()
return null
}
// only affect LiveRoute
if ((livePath || alwaysLive) && (component || propRender)) {
debugLog('=== RENDER FLAG: ' + this.liveState + ' ===')
......
......@@ -98,7 +98,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4":
"@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4":
version "7.3.4"
resolved "http://registry.npm.taobao.org/@babel/runtime/download/@babel/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83"
integrity sha1-c9ErqBnjZfz3/RUq7VbW35fSHIM=
......@@ -1426,6 +1426,18 @@ history@^4.7.2:
value-equal "^0.4.0"
warning "^3.0.0"
history@^4.9.0:
version "4.9.0"
resolved "https://registry.yarnpkg.com/history/-/history-4.9.0.tgz#84587c2068039ead8af769e9d6a6860a14fa1bca"
integrity sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==
dependencies:
"@babel/runtime" "^7.1.2"
loose-envify "^1.2.0"
resolve-pathname "^2.2.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
value-equal "^0.4.0"
hoist-non-react-statics@^2.5.0:
version "2.5.5"
resolved "http://registry.npm.taobao.org/hoist-non-react-statics/download/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
......@@ -3368,6 +3380,16 @@ throat@^4.0.0:
resolved "http://registry.npm.taobao.org/throat/download/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a"
integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=
tiny-invariant@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.3.tgz#91efaaa0269ccb6271f0296aeedb05fc3e067b7a"
integrity sha512-ytQx8T4DL8PjlX53yYzcIC0WhIZbpR0p1qcYjw2pHu3w6UtgWwFJQ/02cnhOnBBhlFx/edUIfcagCaQSe3KMWg==
tiny-warning@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28"
integrity sha512-rru86D9CpQRLvsFG5XFdy0KdLAvjdQDyZCsRcuu60WtzFylDM3eAWSxEVz5kzL2Gp544XiUvPbVKtOA/txLi9Q==
tmpl@1.0.x:
version "1.0.4"
resolved "http://registry.npm.taobao.org/tmpl/download/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
......
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