Skip to content

Instantly share code, notes, and snippets.

@xialvjun
Created August 9, 2016 09:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xialvjun/f6f1006bba2dd4a3015be2383676c01e to your computer and use it in GitHub Desktop.
Save xialvjun/f6f1006bba2dd4a3015be2383676c01e to your computer and use it in GitHub Desktop.
Wrap a component class and get the CachedComponent. This CachedComponent will calculate a key from props and use the key to decide whether to construct a new instance and cache the old or recover the old component from cache and update props. We can use it with react-router easily to save up the time of constructing the RouteComponent you alread…
import React from 'react'
export default function cache(mapPropsToKey, option) {
mapPropsToKey = mapPropsToKey || (() => 'NoCache')
option = option || {}
// 默认最高5层,1000年
const {
count=5,
expire=3153600000000,
defaultContainerStyle,
defaultContainerClassName,
defaultEnterStyle,
defaultEnterClassName,
defaultShowingStyle={display: 'initial'},
defaultShowingClassName,
defaultLeaveStyle,
defaultLeaveClassName,
defaultHiddenStyle={display: 'none'},
defaultHiddenClassName,
defaultLeaveDuration=0,
beforeInit,
afterInit,
beforeSwitch,
afterSwitch,
} = option
return function(ToCacheComponent) {
class Cached extends React.Component {
constructor(props) {
super(props)
let {
containerStyle=defaultContainerStyle,
containerClassName=defaultContainerClassName,
enterStyle=defaultEnterStyle,
enterClassName=defaultEnterClassName
} = beforeInit ? beforeInit({prevProps: props}) : {}
this.state = {
items: [{key: mapPropsToKey(props), props}],
containerStyle,
containerClassName,
style_one: enterStyle,
className_one: enterClassName,
// style_two: {},
// className_two: '',
// style_rest: {},
// className_rest: '',
}
}
componentDidMount() {
let {containerStyle:sCS, containerClassName:sCC, style_one:sSO, className_one:sCO, items } = this.state
let {
containerStyle = sCS || defaultContainerStyle,
containerClassName = sCC || defaultContainerClassName,
showingStyle = sSO || defaultShowingStyle,
showingClassName = sCO || defaultShowingClassName
} = afterInit ? afterInit({prevProps: this.props, prevWrapper: this.refs[items[0].key]}) : {}
this.setState({
containerStyle,
containerClassName,
style_one: showingStyle,
className_one: showingClassName,
})
}
componentWillReceiveProps(nextProps) {
let items = this.state.items
let now = new Date().getTime()
let nextKey = mapPropsToKey(nextProps)
items = items.filter(item => !item.expire || item.expire>now)
this.switching = items[0].key !== nextKey
this.recover = items.slice(1).filter(item => item.key===nextKey).length > 0
this.prevProps = this.props
this.nextProps = nextProps
items = items.filter(item => item.key!==nextKey)
items.forEach(item => item.expire = item.expire || now+expire)
items = [{key: nextKey, props: nextProps}].concat(items).slice(0, count)
if (this.switching) {
let {containerStyle: sCS, containerClassName: sCC, style_one: sSO, className_one: sCO, style_two: sST, className_two: sCT, style_rest: sSR, className_rest: sCR } = this.state
let {
containerStyle = sCS || defaultContainerStyle,
containerClassName = sCC || defaultContainerClassName,
enterStyle = this.recover ? (sSR || defaultEnterStyle) : defaultEnterStyle,
enterClassName = this.recover ? (sCR || defaultEnterClassName) : defaultEnterClassName,
showingStyle = sSO || defaultShowingStyle,
showingClassName = sCO || defaultShowingClassName,
// leaveStyle = defaultLeaveStyle,
// leaveClassName = defaultLeaveClassName,
hiddenStyle = sSR || defaultHiddenStyle,
hiddenClassName = sCR || defaultHiddenClassName,
} = beforeSwitch ? beforeSwitch({prevProps: this.prevProps, nextProps: this.nextProps, prevWrapper: this.refs[this.state.items[0].key], nextWrapper: this.refs[nextKey], recover: this.revocer}) : {}
this.setState({
containerStyle,
containerClassName,
style_one: enterStyle,
className_one: enterClassName,
style_two: showingStyle,
className_two: showingClassName,
style_rest: hiddenStyle,
className_rest: hiddenClassName,
})
}
this.setState({items})
}
componentDidUpdate(prevProps, prevState) {
if (this.switching) {
delete this.switching
let {containerStyle: sCS, containerClassName: sCC, style_one: sSO, className_one: sCO, style_two: sST, className_two: sCT, style_rest: sSR, className_rest: sCR } = this.state
let {
containerStyle = sCS || defaultContainerStyle,
containerClassName = sCC || defaultContainerClassName,
// enterStyle = sSR || defaultEnterStyle,
// enterClassName = sCR || defaultEnterClassName,
showingStyle = sST || defaultShowingStyle,
showingClassName = sCT || defaultShowingClassName,
leaveStyle = defaultLeaveStyle,
leaveClassName = defaultLeaveClassName,
hiddenStyle = sSR || defaultHiddenStyle,
hiddenClassName = sCR || defaultHiddenClassName,
leaveDuration = defaultLeaveDuration,
} = afterSwitch ? afterSwitch({prevProps: this.prevProps, nextProps: this.nextProps, prevWrapper: this.refs[this.state.items[1].key], nextWrapper: this.refs[this.state.items[0].key], recover: this.revocer}) : {}
this.setState({
containerStyle,
containerClassName,
style_one: showingStyle,
className_one: showingClassName,
style_two: leaveStyle,
className_two: leaveClassName,
style_rest: hiddenStyle,
className_rest: hiddenClassName,
})
this.timeout = setTimeout(() => {
this.setState({
style_two: hiddenStyle,
className_two: hiddenClassName,
})
}, leaveDuration);
}
}
componentWillUnmount() {
clearTimeout(this.timeout)
}
render() {
// 不直接操作 item.props 里的 style 是因为 display 不止有 none、initial 也可能是 flex
let {containerStyle, containerClassName, style_one, className_one, style_two, className_two, style_rest, className_rest } = this.state
return (
<div style={containerStyle} className={containerClassName}>
{this.state.items.map((item, index) => (
<div key={item.key} ref={item.key} style={index===0 ? style_one : (index===1 ? style_two : style_rest)} className={index===0 ? className_one : (index===1 ? className_two : className_rest)}
>
{React.createElement(ToCacheComponent, item.props)}
</div>
))}
</div>
)
}
}
Cached.displayName = `Cached(${ToCacheComponent.displayName || ToCacheComponent.name || 'Component'})`
Cached.Component = ToCacheComponent
return Cached
}
}
export const EasyCacheRouteOfPathName = cache(
props => props.location.pathname
)(function Route(props) {
return React.Children.only(props.children)
})
export const EasyCacheRouteOfComponent = cache(
props => React.Children.only(props.children).type.displayName || React.Children.only(props.children).type.name
)(function Route(props) {
return React.Children.only(props.children)
})
@xialvjun
Copy link
Author

xialvjun commented Aug 9, 2016

Wrap a component class and get the CachedComponent.

This CachedComponent will calculate a key from props and use the key to decide whether to construct a new instance and cache the old or to recover the old component from cache and update props.

We can use it with react-router easily to save up the time of constructing the RouteComponent you already constructed, and achieve an much more fluent routing experience like the native apps.

And besides, this tool also offers some switching component hooks. You can use these hooks to make something like animation and so on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment