React Hooks と universal-router を雑に使ってみる
練習がてら React Hooks と universal-router を雑に使ってみる話.メモ程度で解説はあまり書かない.
実際のコードは TypeScript で書いたものを CodeSandbox に上げてる.
useLocation
履歴は history
ライブラリで扱う.正直, history
ちょっと大きいので,小さい alternative を探したい.
history.location
の変化を取れる useLocation
を useState
と useEffect
から作ってみる.
function useLocation(history) { const [location, setLocation] = useState(history.location); useEffect( () => { const unlisten = history.listen((location) => setLocation(location)); return () => unlisten(); }, [history], ); return location; }
useRouter
今のルーティングに合う Component を返すやつを useRouter
として作る.もうちょい良い関数名にしたい.
useMemo
で UniversalRouter
をメモ化しておく.あと,router.resolve
は Promise
を返してくるので同期的にコンポーネントとして返せない問題を React.lazy
で雑に解決してみる.
function useRouter(routes, history) { const location = useLocation(history); const router = useMemo(() => new UniversalRouter(routes), [routes]); const [Component, setComponent] = useState('div'); useEffect( () => { const LazyComponent = React.lazy(() => router.resolve(location.path)); setComponent(() => LazyComponent); }, [location], ); return Component; }
HistoryContext
<Link />
のために history
を Context に入れる.
const HistoryContext = React.createContext(null);
<Router />
history
と routes
を渡したら,パスにあった Component を render する. ついでに HistoryContext
も作る.
Component は React.lazy
なので, <React.Suspense/>
で Loading 画面が作れる.
function Router(props) { const Component = useRouter(props.routes, props.history); return ( <HistoryContext.Provider value={props.history}> <React.Suspense fallback={<div>Loading...</div>}> <Component /> </React.Suspense> </HistoryContext.Provider> ); }
<Link />
<a />
をベースにした <Link />
を作る. useContext
で Context からのデータを取る. useCallback
でハンドラーを作る.
function Link(props) { const history = useContext(HistoryContext); const handleClick = useCallback( (ev) => { ev.prevendDefault(); history.push(props.href); }, [history, props.href], ); return <a onClick={handleClick} {...props} />; }
マウント
const history = createBrowserHistory(); const routes = [ { path: '/', action: () => import('./pages/FirstPage'), }, { path: '/second', action: () => import('./pages/SecondPage'), }, ]; ReactDOM.render( <Router history={history} routes={routes} />, document.getElementById('app'), );
結果
最終的にはこんな感じ