Skip to content

Instantly share code, notes, and snippets.

@zaaack
Created November 23, 2016 08:16
Show Gist options
  • Save zaaack/e740c855d6adb844ea40e40d6586d2df to your computer and use it in GitHub Desktop.
Save zaaack/e740c855d6adb844ea40e40d6586d2df to your computer and use it in GitHub Desktop.
SimpleReactRouter using hash
/**
* SimpleRouter
*
*```js
* const routes = [{
* path: '/',
* component: Main,
* }, {
* path: '/user/:id',
* component: User,
* }]
*
* function User(props) {
* const { params, query, path } = props.router
* return <div>This user's id is {params.id}!</div>
* }
*
* const NotMatch = () => (<h1>404!!</h1>)
*
* ReacDOM.render((
* <div>
* <a href="#!/user/1">User 1</a>
* <SimpleRouter routes={routes} notMatch={NotMatch}/>
* </div>
* ), dom)
*
* ```
*/
function autoBind(instance) {
const unBindMethods = {}
const filter = key =>
(instance[key] instanceof Function)
&& !(key.match(/^component|^render$|^constructor$/))
let methods = [...Object.keys(instance).filter(filter)]
let parent = instance
while (parent = Object.getPrototypeOf(parent)) {
const ownProperties = Object.getOwnPropertyNames(parent)
if (ownProperties.indexOf('setState') >= 0) { // React.Component
break
}
methods = methods.concat(
ownProperties.filter(filter))
}
methods.forEach(key => {
unBindMethods[key] = instance[key]
instance[key] = instance[key].bind(instance)
})
return unBindMethods
}
class SimpleRouter extends React.Component {
static propTypes = {
routes: PropTypes.arrayOf(PropTypes.shape({
path: PropTypes.string.isRequired,
component: PropTypes.func.isRequired
})),
notMatch: PropTypes.func, // 404 component
// if cached is true, component won't be removed
// from document when navigate to other page
cached: PropTypes.bool
}
constructor(props) {
super(props)
this.routesPaths = props.routes.map(route => this._parsePath(route.path))
this.cachedElements = {}
this.state = {}
autoBind(this)
}
_parseQuery(path) {
const qs = path.substring(path.indexOf('?') + 1)
return qs.split('&').reduce((obj, kv) => {
const kvs = kv.split('=')
if (kvs.length === 2) {
obj[kvs[0]] = decodeURIComponent(kvs[1])
}
return obj
}, {})
}
_parsePath(path) {
const query = this._parseQuery(path)
const paths = path
.replace(/(?:^\/|\/$|\?.*$)/g, '')
.split('/')
.map(p => `/${p}`)
return { query, paths }
}
getPath() {
return location.hash.replace('#!', '')
}
getPage() {
const { routes, notMatch, cached } = this.props
const path = this.getPath()
if (cached && this.cachedElements[path]) {
return this.cachedElements[path]
}
const { query, paths } = this._parsePath(path)
const params = {}
const router = { query, params, path }
let Cpt
for (let i in routes) {
const { paths: routePaths } = this.routesPaths[i]
const match = routePaths.every((p, i) => {
if (p[1] === ':') {
params[p.substring(2)] = paths[i].substring(1)
return true
}
return p === paths[i]
})
if (match) {
Cpt = routes[i].component
break
}
}
if (!Cpt && notMatch) {
Cpt = notMatch
}
const cpt = Cpt && <Cpt router={router} />
if (cached) {
this.cachedElements[path] = cpt
}
return cpt
}
update() {
this.setState({
path: this.getPath(),
page: this.getPage()
})
}
componentDidMount() {
$(window).on('hashchange', this.update)
this.update()
}
render() {
const { page } = this.state
if (!this.props.cached) {
return page || null
}
return (
<div>
{Object.keys(this.cachedElements).map(k => {
const el = this.cachedElements[k]
return (
<div key={k} style={{ display: el === page ? 'block' : 'none' }}>
{el}
</div>
)
})}
</div>
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment