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'), );
結果
最終的にはこんな感じ