Created
April 23, 2018 14:10
-
-
Save JSila/5c7b23427b90f469a3b913c9d77a49f7 to your computer and use it in GitHub Desktop.
custom router solution for react (experiment)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, {Component, createElement} from 'react' | |
import createHistory from 'history/createBrowserHistory' | |
import classnames from "classnames" | |
export const history = createHistory() | |
/** | |
* Determines if route path matches history location's pathname. If it does, extracts its params. If not, returns null. | |
* | |
* @param routePath | |
* @param locationPath | |
* @returns {object|null} | |
*/ | |
const matchParams = (routePath, locationPath) => { | |
const l = locationPath.split('/').slice(1) | |
const r = routePath.split('/').slice(1) | |
const llen = l.length | |
if (llen !== r.length) { | |
return null | |
} | |
const params = {} | |
for (let i = 0; i < llen; i++) { | |
const [h,] = r[i] | |
if (h === ":" && l[i]) { | |
params[r[i].slice(1)] = l[i] | |
continue | |
} | |
if (r[i] !== l[i]) { | |
return null | |
} | |
} | |
return { | |
match: routePath, | |
params | |
} | |
} | |
const RouterContext = React.createContext() | |
export const Route = ({path, component}) => ( | |
<RouterContext.Consumer> | |
{({state}) => { | |
const data = matchParams(path, state.url) | |
if (!data) return | |
return createElement(component, data) | |
}} | |
</RouterContext.Consumer> | |
) | |
export class Router extends Component { | |
constructor(p) { | |
super(p) | |
this.state = { | |
url: window.location.pathname | |
} | |
this.action = { | |
go: url => { | |
if (this.state.url === url) return | |
this.setState( | |
state => ({...state, url}), | |
() => history.push(url) | |
) | |
} | |
} | |
} | |
componentDidMount() { | |
this.unsubscribe = history.listen((location, action) => { | |
if (action !== "POP") return | |
this.setState({url: location.pathname}) | |
}) | |
} | |
componentWillUnmount() { | |
this.unsubscribe() | |
} | |
render() { | |
const value = { | |
state: this.state, | |
action: this.action | |
} | |
return ( | |
<RouterContext.Provider value={value}> | |
{this.props.children} | |
</RouterContext.Provider> | |
) | |
} | |
} | |
export const Link = ({to, activeClassName = 'active', children}) => ( | |
<RouterContext.Consumer> | |
{({state, action}) => { | |
const className = classnames({ | |
[activeClassName]: state.url === to | |
}) | |
return ( | |
<a className={className} onClick={(e) => {e.preventDefault(); action.go(to)}} href={to}> | |
{children} | |
</a> | |
) | |
}} | |
</RouterContext.Consumer> | |
) | |
const onlyKeys = (keys, obj) => { | |
const res = {} | |
keys.forEach(k => { | |
res[k] = obj[k] | |
}) | |
return res | |
} | |
const arrayDeepEqual = (obj1 = {}, obj2 = {}) => { | |
return Object.keys(obj1).every(k => obj1[k] === obj2[k]) | |
} | |
const ROUTE_CHANGE = 'router/ROUTE_CHANGE' | |
const initialState = onlyKeys(['pathname', 'search', 'hash'], history.location) | |
export const routerReducer = (state = initialState, action) => { | |
if (action.type !== ROUTE_CHANGE) return state | |
return action.payload | |
} | |
export const routerMiddleware = store => { | |
return dispatch => { | |
history.listen(location => { | |
const payload = { | |
pathname: location.pathname, | |
search: location.search, | |
hash: location.hash | |
} | |
if (arrayDeepEqual(payload, store.getState().router)) return | |
dispatch({ | |
type: ROUTE_CHANGE, | |
payload | |
}) | |
}) | |
return action => { | |
return dispatch(action) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment