Nedioプロジェクトをするときの悩み(1)
47246 ワード
悩みの内容を整理する
モジュール化による再利用
最初に作成したパターンは,パターンを使いたい場所でパターンの状態を伝える仕組みである.
Modelを使用したい場所にModelを開いたり閉じたりできる関数しか存在しない場合、Modelの状態管理はModelでも良いが、子供の状態を管理できる関数を親に渡す方法は考えられない.
useImperativeHandle
上記の悩みは、hookの種類でuseImperativeHandleを使って解決できます.
Carousel
メリーゴーランド
スライダを移動するときは、curIdxを使用して現在のスライダの位置を表し、transform:translateX()プロパティを使用してスライダを移動します.(margin-leftプロパティなどに移動することもできます)
前に実装したcarouselとは異なり、現在のスライダは画面の真ん中にあり、左右に次のスライダが表示されます.
これらの条件を満たすために、まずスライダ行left:50%を与え、画面を中央から開始します.
これを実現するには、上で使用した現在のスライダ位置curIdxの値を使用して、%を現在のスライダ位置に移動し、スライダの半分を中央に移動すると、次のスライダを移動して現在のスライダを中央に移動できます.
最初の使い方は以下の通りです.
しかし、一つ問題があります.今回のホイールは左右に次のスライダが見えるので、最後の要素が移行すると、一時的に作成したレプリカスライダの横に空の現象が発生します.
この問題は、最後の要素からコピースライダに移動した後、隣にスライダがなくなったため、各エンドポイント要素にスライダをもう1つ作成することで解決できます.
クエリーの管理
キーワード入力→検索→このキーワードを使用してapiを要求する構造
refreshではキーワード初期化URLがそのままになるという問題が発生した.
キーワードではなくURLをグループ化してAPIを要求する
検索ウィンドウにキーワードを入力し、検索時に検索するオプションを使用して検索ページのロールにルーティングします.
検索ページでは、URLをグループ化してkeywordを再メンテナンスすることができますが、すべてのページで共有されるナビゲーションであるため、他のページではkeywordのメンテナンスが困難です.
クエリーの保持(localstorageを使用)
一般的なWebサイトでは、localStorageでsearchHistoryを使用してユーザーの検索結果を管理するサービス検索に似た構造があります.
これを参考に,ユーザは検索結果をlocalStorageに保存する考えを持ち,reduxを用い,redux−persistを用いて状態をlocalStorageに保存することで,所望の結果を得ることができる.
検索ページのほかに、検索項目の初期化を保持
ナビゲーションは、ログインページと展覧ページ以外のすべてのページで表示されるため、localstorageを使用して検索語を保持すると、サービスページに再接続すると、検索ページのほかにキーワードが保持されます.
クライアントがグローバルに管理するキーワードステータスに加えて、検索コンポーネント内で管理できるステータスが追加され、置換キーワード値を使用することで、現在のパスに基づいてサブコンポーネントに降格する問題が解決されます.
まず、各スケジュールスクロールでは、受信データfetchのコードは、コンポーネントに初めてアクセスしたときと同じであるため、上記で使用したコードの一部を以下のように分離する.
いろいろな方法がありますが、私が使っている方法は これは
Reference https://ko.reactjs.org/docs/hooks-reference.html
モジュール化による再利用
最初に作成したパターンは,パターンを使いたい場所でパターンの状態を伝える仕組みである.
const [modalState, setModalState] = useState(false);
return (
<>
<AuthButton tapMenu="로그인" handleClick={onOpen} />
<Modal ref={modal} modalState={modalState} onClose={onClose}>
{/* TODO: modal 내부 구현 */}
<h3>Nedio에 오신 것을 환영합니다!</h3>
<p>모달 내부</p>
</SignInModal>
</>
);
このようにすれば、モードを使用する必要がある場所でそのコンポーネントを管理するために必要な状態に加えて、モードを状態管理する必要があるため、効率が低下する可能性がある.だから、モード素子でモードの状態管理をする方法はないかと思います.Modelを使用したい場所にModelを開いたり閉じたりできる関数しか存在しない場合、Modelの状態管理はModelでも良いが、子供の状態を管理できる関数を親に渡す方法は考えられない.
useImperativeHandle
上記の悩みは、hookの種類でuseImperativeHandleを使って解決できます.
const modalRef = useRef<React.ElementRef<typeof Modal>>(null);
return (
<>
<AuthButton
tapMenu="로그인"
handleClick={() => modalRef.current?.show()}
/>
<Modal ref={modalRef}>
{/* TODO: modal 내부 구현 */}
<h3>Nedio에 오신 것을 환영합니다!</h3>
<p>모달 내부</p>
</Modal>
</>
);
const Modal = React.forwardRef<ModalHandle, Props>((props, ref) => {
const [modalState, setModalState] = useState(false);
useImperativeHandle(ref, () => ({
show() {
setModalState(true);
},
}));
// 생략...
});
userImperativeHandleをforwardRefとともに使用する場合は、refを使用する親コンポーネントでカスタム関数を使用できます.これにより、Modelのステータス管理はModel構成部品で管理でき、Modelを使用する親構成部品は自由に使用できます.Carousel
メリーゴーランド
スライダを移動するときは、curIdxを使用して現在のスライダの位置を表し、transform:translateX()プロパティを使用してスライダを移動します.(margin-leftプロパティなどに移動することもできます)
const [curIdx, setCurIdx] = useState(0);
const CarouselRow = styled.section<{ moveX: number; transitionEffect: string }>`
position: relative;
transform: ${({ moveX }) => `translateX(calc(${moveX}%))`};
`;
carousel中央位置前に実装したcarouselとは異なり、現在のスライダは画面の真ん中にあり、左右に次のスライダが表示されます.
これらの条件を満たすために、まずスライダ行left:50%を与え、画面を中央から開始します.
const CarouselRow = styled.section<{ moveX: number; transitionEffect: string }>`
display: flex;
left: 50%;
`;
左:50%;変換:TranslationX(-50%)と同じ方法でホイールを移動します.これを実現するには、上で使用した現在のスライダ位置curIdxの値を使用して、%を現在のスライダ位置に移動し、スライダの半分を中央に移動すると、次のスライダを移動して現在のスライダを中央に移動できます.
<CarouselRow
moveX={(-100 / paddedItems.length) * (curIdx + 0.5)}
>
// 생략...
</CarouselRow>
const CarouselRow = styled.section<{ moveX: number; transitionEffect: string }>`
position: relative;
display: flex;
left: 50%;
width: fit-content;
transform: ${({ moveX }) => `translateX(calc(${moveX}%))`};
`;
infinite carousel最初の使い方は以下の通りです.
しかし、一つ問題があります.今回のホイールは左右に次のスライダが見えるので、最後の要素が移行すると、一時的に作成したレプリカスライダの横に空の現象が発生します.
この問題は、最後の要素からコピースライダに移動した後、隣にスライダがなくなったため、各エンドポイント要素にスライダをもう1つ作成することで解決できます.
クエリーの管理
キーワード入力→検索→このキーワードを使用してapiを要求する構造
refreshではキーワード初期化URLがそのままになるという問題が発生した.
キーワードではなくURLをグループ化してAPIを要求する
検索ウィンドウにキーワードを入力し、検索時に検索するオプションを使用して検索ページのロールにルーティングします.
navigation(`${PATH.GALLERY_SEARCH}?${selectedOption}=${keyword}`);
実際のAPIリクエストは、検索結果ページに該当するURLを区切って検索しますuseEffect(() => {
const queryParam = decodeURIComponent(query.toString());
const [key, value] = queryParam.split('=');
let queryStr = `/galleries/filtering?page=${page}&perPage=${perPage}&`;
switch (key) {
case 'title': {
queryStr += `${key}=${value}&nickname=&category=`;
break;
}
case 'nickname': {
queryStr += `title=&${key}=${value}&category=`;
break;
}
case 'category': {
queryStr += `title=&nickname=&${key}=${value}`;
break;
}
default:
queryStr += `title=&nickname=&category=`;
}
axiosInstance
.get(queryStr)
.then((res) => setCards(res.data.data))
.catch((e) => {
// console.log(e);
})
.finally(() => {
dispatch(setKeyword(value));
});
}, [page, perPage, query, dispatch]);
参考コーチはparams Serializerの方法を使って、再包装しますuseEffect(() => {
const queryParam = decodeURIComponent(query.toString());
const [queryKey, queryValue] = queryParam.split('=');
const customParams = {
page,
perPage,
title: '',
nickname: '',
category: '',
};
axiosInstance
.get<string, AxiosRequestConfig>('/galleries/filtering', {
params: customParams,
paramsSerializer: (params) =>
combineQuery(params, queryKey, queryValue),
})
.then((res) => setCards(res.data.data))
.catch((e) => {
// console.log(e);
})
.finally(() => {
dispatch(setKeyword(value));
});
}, [page, perPage, query, dispatch]);
ブラウザが予期せぬ終了とリフレッシュを行った場合でも、ユーザーが入力したキーワードを保持したいと考えています.検索ページでは、URLをグループ化してkeywordを再メンテナンスすることができますが、すべてのページで共有されるナビゲーションであるため、他のページではkeywordのメンテナンスが困難です.
クエリーの保持(localstorageを使用)
一般的なWebサイトでは、localStorageでsearchHistoryを使用してユーザーの検索結果を管理するサービス検索に似た構造があります.
これを参考に,ユーザは検索結果をlocalStorageに保存する考えを持ち,reduxを用い,redux−persistを用いて状態をlocalStorageに保存することで,所望の結果を得ることができる.
検索ページのほかに、検索項目の初期化を保持
ナビゲーションは、ログインページと展覧ページ以外のすべてのページで表示されるため、localstorageを使用して検索語を保持すると、サービスページに再接続すると、検索ページのほかにキーワードが保持されます.
クライアントがグローバルに管理するキーワードステータスに加えて、検索コンポーネント内で管理できるステータスが追加され、置換キーワード値を使用することで、現在のパスに基づいてサブコンポーネントに降格する問題が解決されます.
useEffect(() => {
if (pathname !== PATH.GALLERY_SEARCH) {
setSideKeyword('');
}
}, [pathname]);
return (
<>
<SearchBar
keyword={pathname === PATH.GALLERY_SEARCH ? keyword : sideKeyword}
handleKeyword={handleKeyword}
resetKeyword={initKeyword}
handleSubmit={handleSubmit}
/>
<SearchSelect
option={selectedOption}
handleSelectOption={handleSelectOption}
/>
</>
);
無限スクロールまず、各スケジュールスクロールでは、受信データfetchのコードは、コンポーネントに初めてアクセスしたときと同じであるため、上記で使用したコードの一部を以下のように分離する.
const loadCards = useCallback(async () => {
const customParams = {
page: page.current,
perPage: perPage.current,
title: '',
nickname: '',
category: '',
};
if (!isValidQuery(queryKey, queryValue)) {
navigation('/NotFound');
return;
}
try {
setLoading(true);
const res = await axiosInstance.get<string, AxiosRequestConfig>(
'/galleries/filtering',
{
params: customParams,
paramsSerializer: (params) =>
combineQuery(params, queryKey, queryValue),
},
);
const list = listOfDisplayGalleries(res.data.data);
if (res.data.data.length === 0) lastCard.current = true;
return list;
} catch (e) {
// console.log(e);
} finally {
setLoading(false);
dispatch(setKeyword(queryalue));
dispatch(setOption(queryKey));
}
}, [dispatch, navigation, queryKey, queryValue]);
では、上記の関数を検出して呼び出して追加のデータをロードする方法は難題です.いろいろな方法がありますが、私が使っている方法は これは
Intersection Observer API
を利用する方法です.Window Scrollを使用しない理由は、スクロールする特定のポイントを観察するために、短時間で大量の呼び出しが発生するため、パフォーマンスの面で効率が悪く、最も重要なのはそれを使用したいからです.クロスビューポイントで特定の要素を検出し、上記の関数を呼び出すようにデータのロードを継続し、ロード、lastCard状態で不要な呼び出しを防止する.const loadMoreCards = useCallback(async () => {
if (cards && cards.length > 0) {
page.current += 1;
const newCards = await loadCards();
if (newCards !== undefined) setCards([...cards, ...newCards]);
}
}, [cards, loadCards]);
useIntersectionObserver({
target: targetRef.current,
onIntersect: ([{ isIntersecting }]: any) => {
if (isIntersecting && !loading && !lastCard.current) {
loadMoreCards();
}
},
});
要素を感知し続け、内部コードを実行し、ロード可能なデータがない場合は、観察者を解除するように実現したほうがいいです.Reference
Reference
この問題について(Nedioプロジェクトをするときの悩み(1)), 我々は、より多くの情報をここで見つけました https://velog.io/@jiseong/20220222テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol