狠狠撸

狠狠撸Share a Scribd company logo
ルーター自前実装のお话
自己紹介
KazushiKawamura
https://github.com/kawamurakazushi
https://www.producthunt.com/@kawamurakazushi
--
2020.06~2021.10 AxelspaceCorporation.
GroundSystemEngineer
2021.11~ Henry,Inc.
FrontendEngineer
今日話すこと
Henryでのナビゲーションの課題
他のルーターのライブラリについて
DEMO
なにを実現したいのか?
どう実現したのか?
Henryでのナビゲーションの課題
タブナビゲーションをネストしたい
ページのライフサイクルに合わせた状态管理をしたい
スクロールの位置
選択されたタブの位置
検索内容
Henryでのナビゲーションの課題
タブナビゲーションをネストしたい
Henryでのナビゲーションの課題
タブナビゲーションをネストしたい
Henryでのナビゲーションの課題
タブナビゲーションをネストしたい
Henryでのナビゲーションの課題
タブナビゲーションをネストしたい
Henryでのナビゲーションの課題
タブナビゲーションをネストしたい
Henryでのナビゲーションの課題
ページのライフサイクルに合わせた状态管理をしたい
DEMO
ルーターライブラリ
react-router / reach-router (普通のwebrouter)
queryparameter?parse?
react-navigation
react-native用のライブラリ
実現したい挙動は近いが、reactnativeforwebを利用する必要がある。
https://reactnavigation.org/docs/web-support/
react-stack-nav
https://github.com/tuckerconnelly/react-stack-nav
ネストしたタブがサポートされてない
運用されてない
自前で実装する
何を実現したいのか?
通常のルーター機能
タブナビゲーションをネストしたい
ページのライフサイクルに合わせた状态管理をしたい
どう実現したのか?
ナビゲーションに必要な全ての情報をContextに持たせることによって実現
Context
interface CoordinatorContextState {

coordinator: CoordinatorState;

setCoordinator: (coordinator: CoordinatorState) => void;

}

const CoordinatorContext = createContext<CoordinatorContextState | null>(null);
Type
export type CoordinatorState<T extends string = string> = {

name: string;

currentStack: T;

stacks: Record<T, PageState[]>;

};

type PageState = {

states: Record<string, any>;

coordinators: Record<string, CoordinatorState>;

} & Page;

type Page =

| { type: "reception" }

| { type: "sessionDetail"; sessionUuid: string }

| { type: "patientDetail"; patientUuid: string }

| { type: "settingIndex" };

// ...
Type
Type
Type
Example
{

name: "ROOT",

currentStack: "reception",

stacks: {

reception: [

{

type: "reception",

states: {

sessionListTab: "all",

},

coordinators: {},

},

],

patients: [

{

type: "patientIndex",

states: {},

coordinators: {},

},

],

settings: [

{

type: "settingIndex",

states: {},

coordinators: {},

},

],

},

};
CoordinatorState から画面を表示
CoordinatorState から画面を表示
CoordinatorContext のselectorやmutation用の関数などを定義する useCoordinator
export const useCoordinator = () => {

const context = useContext(CoordinatorContext);

if (!context) {

throw new Error("Context is not initialized yet.");

}

const { coordinator, setCoordinator } = context;
const currentPage = useMemo(() => getCurrentPage(coordinator), [coordinator]);

// ...

return { ...context, currentPage };

};

const getCurrentPage = (coordinator: CoordinatorState): PageState => {

// coordinatorStateのcurrentStackの配列の最後のページを取得する

return last(coordinator.stacks[coordinator.currentStack]);

};
CoordinatorState から画面を表示
currentPage から場合分けでページを表示をする
const CurrentPage: React.FC = () => {

const { currentPage } = useCoordinator();

if (!currentPage) return <NotFound />;

switch (currentPage.type) {

case "reception":

return <ReceptionPage />;

case "sessionDetail":

return <SessionDetailPage uuid={currentPage.sessionUuid} />;

case "patientIndex":

return <PatientIndex />;

case "settingIndex":

return <SettingIndexPage />;

case "editorTemplateDetail":

return <EditorTemplateDetailPage uuid={currentPage.editorTemplateUuid} />;

//...

}

return null;

};
CoordinatorState から画面を表示
function App() {

return (

<RootCoordinatorProvider>

<CurrentPage />

</RootCoordinatorProvider>

);

}
URLからCoordinatorState を初期化する
ルーターなのでURLから期待する CoordinatorState を初期化する必要がある。
RootCoordinatorProvider に初期化する処理を行う
export const RootCoordinatorProvider: React.FC = ({ children }) => {

const [coordinator, setCoordinator] = useState(routerInitialState);

useEffect(() => {

const path = window.location.pathname;

// `/reception` だったら

// { type: "reception",

// states: {},

// coordinators: {} }

const pageState = pathToPageState(path);

if (pageState) {

setCoordinator((c) => {

const newCoordinator = // pageStateを使って、ゴニョゴニョ

return newCoordinator;

});

}

}, []);

return (

<CoordinatorContext.Provider value={{ coordinator, setCoordinator }}>{children}</CoordinatorContext.Provider>

);

};

pathToPageState はURLPatternAPIを用いてる
https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API
ページの遷移
useCoordinator に遷移用の関数を定義する
export const useCoordinator = () => {

// ...

const push = useCallback(

(page: Page) => {

const newCoordinator = produce(coordinator, (c) => {

c.stacks[c.currentStack].push({

...page,

coordinators: {},

states: {},

});

});

setCoordinator(newCoordinator);

},

[setCoordinator, coordinator]

);

const openPage = useCallback(

(page: Page, e?: React.MouseEvent) => {

push(page);

},

[push]

);

return { ...context, currentPage, openPage };

};
BrowserBack/Fowardの対応
history.replaceState
各historyに対してstateobjectを保存することできる
https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState
popstate event
historyが変更されたときに発火される
https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event
export const RootCoordinatorProvider: React.FC = ({ children }) => {

//...

useEffect(() => {

window.history.replaceState(coordinator, "");

}, [coordinator]);

useEffect(() => {

window.addEventListener("popstate", (e) => {

if (!e.state) {

return;

}

setCoordinator(e.state);

});

}, [setCoordinator]);

//...

};

coordinator が変更されたら、 history.replaceState で新しい状態に置き換える
popState のイベントが発行されてたら e.state から coordinator を取得してセット
Coordinatorをネストする方法
Contextをネストすることで実現
使ってる Context の型はRootで利用してるものと一緒なので
同様に coordinator と setCoordinator を用意する必要がある。
export const CoordinatorProvider: React.FC<{

coordinator: CoordinatorState;

}> = ({ children, coordinator: initialCoordinator }) => {

//...

return (

<CoordinatorContext.Provider value={{ coordinator, setCoordinator }}>

{children}

</CoordinatorContext.Provider>

);

};
親のContextから現在のページのcoordinatorを返す
export const CoordinatorProvider: React.FC = ({

children,

coordinator: initialCoordinator,

}) => {

const parentCoordinator = useCoordinator();

const coordinator: CoordinatorState | null = useMemo(() => {

const name = initialCoordinator.name;

const page = getCurrentPage(parentCoordinator.coordinator);

if (page && page.coordinators[name]) {

return page.coordinators[name];

}

return initialCoordinator;

}, [parentCoordinator.coordinator]);

//...

return (

<CoordinatorContext.Provider value={{ coordinator, setCoordinator }}>

{children}

</CoordinatorContext.Provider>

);

};
親のContextを変更する
export const CoordinatorProvider: React.FC<{

coordinator: CoordinatorState;

}> = ({ children, coordinator: initialCoordinator }) => {

const parentCoordinator = useCoordinator();

// ...

const setCoordinator = useCallback(

(coordinator: CoordinatorState) => {

const newCoordinator = produce(parentCoordinator.coordinator, (draft) => {

draft.stacks[draft.currentStack][

draft.stacks[draft.currentStack].length - 1

].coordinators = { [name]: coordinator };

});

parentCoordinator.setCoordinator(newCoordinator);

},

[parentCoordinator]

);

return (

<CoordinatorContext.Provider value={{ coordinator, setCoordinator }}>

{children}

</CoordinatorContext.Provider>

);

};
Context をネストすることにより
子CoordinatorProvider の下で useCoordinator を使えば、同じ関数でも
子の CoordinatorState でのページ遷移が実現する
ページでの使用例
const SettingIndexPage: React.FC<{}> = () => {

const initialCoordinator: CoordinatorState = {

name: "SETTINGS",

currentStack: "editorTemplate",

stacks: {

editorTemplate: [

{

type: "editorTemplateIndex",

states: {},

coordinators: {},

},

],

nonHealthcareSystemAction: [

{

type: "nonHealthcareSystemActionIndex",

states: {},

coordinators: {},

},

],

},

};

return (

<Coordinator coordinator={initialCoordinator}>

<SettingsTabNav />

<CurrentPage />

</Coordinator>

);

};
ページのライフサイクルに合わせた状态管理をしたい
各Pageに states を用意しており、そこに必要な情報を保存が可能。
{

name: "ROOT",

currentStack: "reception",

stacks: {

reception: [

{

type: "reception",

states: {

sessionListTab: "all", // <-- ここ!

},

coordinators: {},

},

],

patients: [

{

type: "patientIndex",

states: {},

coordinators: {},

},

],

settings: [

{

type: "settingIndex",

states: {},

coordinators: {},

},

],

},

};
選択されたリストタブをPageState に保存し参照する
const ReceptionPage: React.FC<{}> = () => {

const { state, setState } = useStates<SessionListTab>(

"sessionListTab",

"all"

);

return (

<Page name="Reception | 受付">

<Tabs index={tabToIndex(state)} onChange={(i) => setState(indexToTab(i))}>

<TabList>

<Tab>すべて</Tab>

<Tab>診察まち</Tab>

<Tab>検査まち</Tab>

</TabList>

<TabPanels>

<TabPanel>すべて</TabPanel>

<TabPanel>診察まち</TabPanel>

<TabPanel>検査まち</TabPanel>

</TabPanels>

</Tabs>

</Page>

);

};
export function useStates<T>(id: string, defaultValue: T) {

const { currentPage, setCoordinator, coordinator } = useCoordinator();

const state: T = useMemo(() => {

return currentPage?.states[id] ?? defaultValue;

}, [currentPage, id, defaultValue]);

const setState = useCallback((newState: T) => {

const newCoordinator = produce(coordinator, (draft) => {

const currentPage = last(draft.stacks[draft.currentStack]);

currentPage.states = { ...currentPage.states, [id]: newState };

});

setCoordinator(newCoordinator);

}, []);

return { state, setState };

}
今日話したこと
Henryでのナビゲーションの課題
他のルーターのライブラリについて
DEMO
なにを実現したいのか?
どう実現したのか?
Thanks

More Related Content

Similar to ルーター自前実装の话 (20)

Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...
Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...
Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...
Shotaro Suzuki
?
Knockout を用いた Firefox OS アプリケーションの開発
Knockout を用いた Firefox OS アプリケーションの開発Knockout を用いた Firefox OS アプリケーションの開発
Knockout を用いた Firefox OS アプリケーションの開発
Kentaro Iizuka
?
搁别诲耻虫と厂飞颈蹿迟の组み合わせ:改订版
搁别诲耻虫と厂飞颈蹿迟の组み合わせ:改订版搁别诲耻虫と厂飞颈蹿迟の组み合わせ:改订版
搁别诲耻虫と厂飞颈蹿迟の组み合わせ:改订版
Fumiya Sakai
?
モバイル开発@蝉测尘蹿辞苍测
モバイル开発@蝉测尘蹿辞苍测モバイル开発@蝉测尘蹿辞苍测
モバイル开発@蝉测尘蹿辞苍测
Daichi Kamemoto
?
LabVIEW NXG Web Module Training 狠狠撸
LabVIEW NXG Web Module Training 狠狠撸LabVIEW NXG Web Module Training 狠狠撸
LabVIEW NXG Web Module Training 狠狠撸
Yusuke Tochigi
?
はじめてのコンテナーDocker & Windows & Linux
はじめてのコンテナーDocker & Windows & LinuxはじめてのコンテナーDocker & Windows & Linux
はじめてのコンテナーDocker & Windows & Linux
Kazushi Kamegawa
?
「Windows 8 ストア アプリ開発 tips」 hokuriku.net vol.11 (2013年1月26日)
「Windows 8 ストア アプリ開発 tips」  hokuriku.net vol.11 (2013年1月26日)「Windows 8 ストア アプリ開発 tips」  hokuriku.net vol.11 (2013年1月26日)
「Windows 8 ストア アプリ開発 tips」 hokuriku.net vol.11 (2013年1月26日)
Fujio Kojima
?
VSCodeで始めるAzure Static Web Apps開発
VSCodeで始めるAzure Static Web Apps開発VSCodeで始めるAzure Static Web Apps開発
VSCodeで始めるAzure Static Web Apps開発
Yuta Matsumura
?
ASP. NET Core 汎用ホスト概要
ASP. NET Core 汎用ホスト概要ASP. NET Core 汎用ホスト概要
ASP. NET Core 汎用ホスト概要
TomomitsuKusaba
?
Ec cube開発合宿 プラグインセミナー
Ec cube開発合宿 プラグインセミナーEc cube開発合宿 プラグインセミナー
Ec cube開発合宿 プラグインセミナー
Ayumu Kawaguchi
?
20130924 Picomon CRH勉強会
20130924 Picomon CRH勉強会20130924 Picomon CRH勉強会
20130924 Picomon CRH勉強会
Yukihiro Kitazawa
?
アメブロ2016 アメブロフロント刷新にみる ひかりとつらみ
アメブロ2016 アメブロフロント刷新にみる ひかりとつらみアメブロ2016 アメブロフロント刷新にみる ひかりとつらみ
アメブロ2016 アメブロフロント刷新にみる ひかりとつらみ
Kazunari Hara
?
【BS4】時は来たれり。今こそ .NET 6 へ移行する時。
【BS4】時は来たれり。今こそ .NET 6 へ移行する時。 【BS4】時は来たれり。今こそ .NET 6 へ移行する時。
【BS4】時は来たれり。今こそ .NET 6 へ移行する時。
日本マイクロソフト株式会社
?
础辫辫颈耻尘の奥别产痴颈别飞アプリテストの仕组みとハマりどころ
础辫辫颈耻尘の奥别产痴颈别飞アプリテストの仕组みとハマりどころ础辫辫颈耻尘の奥别产痴颈别飞アプリテストの仕组みとハマりどころ
础辫辫颈耻尘の奥别产痴颈别飞アプリテストの仕组みとハマりどころ
Masayuki Wakizaka
?
文脈を操る美しきZenjectプロジェクトからの眺め ?Contextの扱い方と活用方法?
文脈を操る美しきZenjectプロジェクトからの眺め ?Contextの扱い方と活用方法?文脈を操る美しきZenjectプロジェクトからの眺め ?Contextの扱い方と活用方法?
文脈を操る美しきZenjectプロジェクトからの眺め ?Contextの扱い方と活用方法?
Mikito Yoshiya
?
20091030cakephphandson 01
20091030cakephphandson 0120091030cakephphandson 01
20091030cakephphandson 01
Yusuke Ando
?
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
Jumpei Ogawa
?
笔濒补测贵谤补尘别飞辞谤办1.2.4における奥别产厂辞肠办别迟
笔濒补测贵谤补尘别飞辞谤办1.2.4における奥别产厂辞肠办别迟笔濒补测贵谤补尘别飞辞谤办1.2.4における奥别产厂辞肠办别迟
笔濒补测贵谤补尘别飞辞谤办1.2.4における奥别产厂辞肠办别迟
Kazuhiro Hara
?
レスポンシブ奥别产デザイン【発展编】
レスポンシブ奥别产デザイン【発展编】レスポンシブ奥别产デザイン【発展编】
レスポンシブ奥别产デザイン【発展编】
Yasuhito Yabe
?
Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...
Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...
Developing .NET 6 Blazor WebAssemby apps with Radzen Blazor component library...
Shotaro Suzuki
?
Knockout を用いた Firefox OS アプリケーションの開発
Knockout を用いた Firefox OS アプリケーションの開発Knockout を用いた Firefox OS アプリケーションの開発
Knockout を用いた Firefox OS アプリケーションの開発
Kentaro Iizuka
?
搁别诲耻虫と厂飞颈蹿迟の组み合わせ:改订版
搁别诲耻虫と厂飞颈蹿迟の组み合わせ:改订版搁别诲耻虫と厂飞颈蹿迟の组み合わせ:改订版
搁别诲耻虫と厂飞颈蹿迟の组み合わせ:改订版
Fumiya Sakai
?
モバイル开発@蝉测尘蹿辞苍测
モバイル开発@蝉测尘蹿辞苍测モバイル开発@蝉测尘蹿辞苍测
モバイル开発@蝉测尘蹿辞苍测
Daichi Kamemoto
?
LabVIEW NXG Web Module Training 狠狠撸
LabVIEW NXG Web Module Training 狠狠撸LabVIEW NXG Web Module Training 狠狠撸
LabVIEW NXG Web Module Training 狠狠撸
Yusuke Tochigi
?
はじめてのコンテナーDocker & Windows & Linux
はじめてのコンテナーDocker & Windows & LinuxはじめてのコンテナーDocker & Windows & Linux
はじめてのコンテナーDocker & Windows & Linux
Kazushi Kamegawa
?
「Windows 8 ストア アプリ開発 tips」 hokuriku.net vol.11 (2013年1月26日)
「Windows 8 ストア アプリ開発 tips」  hokuriku.net vol.11 (2013年1月26日)「Windows 8 ストア アプリ開発 tips」  hokuriku.net vol.11 (2013年1月26日)
「Windows 8 ストア アプリ開発 tips」 hokuriku.net vol.11 (2013年1月26日)
Fujio Kojima
?
VSCodeで始めるAzure Static Web Apps開発
VSCodeで始めるAzure Static Web Apps開発VSCodeで始めるAzure Static Web Apps開発
VSCodeで始めるAzure Static Web Apps開発
Yuta Matsumura
?
ASP. NET Core 汎用ホスト概要
ASP. NET Core 汎用ホスト概要ASP. NET Core 汎用ホスト概要
ASP. NET Core 汎用ホスト概要
TomomitsuKusaba
?
Ec cube開発合宿 プラグインセミナー
Ec cube開発合宿 プラグインセミナーEc cube開発合宿 プラグインセミナー
Ec cube開発合宿 プラグインセミナー
Ayumu Kawaguchi
?
アメブロ2016 アメブロフロント刷新にみる ひかりとつらみ
アメブロ2016 アメブロフロント刷新にみる ひかりとつらみアメブロ2016 アメブロフロント刷新にみる ひかりとつらみ
アメブロ2016 アメブロフロント刷新にみる ひかりとつらみ
Kazunari Hara
?
础辫辫颈耻尘の奥别产痴颈别飞アプリテストの仕组みとハマりどころ
础辫辫颈耻尘の奥别产痴颈别飞アプリテストの仕组みとハマりどころ础辫辫颈耻尘の奥别产痴颈别飞アプリテストの仕组みとハマりどころ
础辫辫颈耻尘の奥别产痴颈别飞アプリテストの仕组みとハマりどころ
Masayuki Wakizaka
?
文脈を操る美しきZenjectプロジェクトからの眺め ?Contextの扱い方と活用方法?
文脈を操る美しきZenjectプロジェクトからの眺め ?Contextの扱い方と活用方法?文脈を操る美しきZenjectプロジェクトからの眺め ?Contextの扱い方と活用方法?
文脈を操る美しきZenjectプロジェクトからの眺め ?Contextの扱い方と活用方法?
Mikito Yoshiya
?
20091030cakephphandson 01
20091030cakephphandson 0120091030cakephphandson 01
20091030cakephphandson 01
Yusuke Ando
?
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
Jumpei Ogawa
?
笔濒补测贵谤补尘别飞辞谤办1.2.4における奥别产厂辞肠办别迟
笔濒补测贵谤补尘别飞辞谤办1.2.4における奥别产厂辞肠办别迟笔濒补测贵谤补尘别飞辞谤办1.2.4における奥别产厂辞肠办别迟
笔濒补测贵谤补尘别飞辞谤办1.2.4における奥别产厂辞肠办别迟
Kazuhiro Hara
?
レスポンシブ奥别产デザイン【発展编】
レスポンシブ奥别产デザイン【発展编】レスポンシブ奥别产デザイン【発展编】
レスポンシブ奥别产デザイン【発展编】
Yasuhito Yabe
?

ルーター自前実装の话