Skip to content

Commit

Permalink
* replace navigation guard onBeforeRouteUpdate() with deep watcher …
Browse files Browse the repository at this point in the history
…to fix the timing of query cannot get the `fetchedPage` as route hasn't changed at that time

+ `queryStartedAtSSR` & `isQueriedBySSR` to fix not timing from the point of fetch started while SSR
* rename variable `startTime` to `queryStartedAt`, const `isCached` to `isQueryCached`, `(network|render)Time` to `-Duration`
- ref `isRouteNewQuery` as moved scrolling to top from watcher of query timing to waterch of route
@ pages/posts.vue

* fix ref of `data.pages` fetched from `useInfiniteQuery()` may contain nesting refs at root level: TanStack/query#6657 @ `<RendererList>`
* using media query in css instead of js before hydrate like 9603faf for noscript user @ `<PostNav>`

* partial revert 8283197 as only scroll to `savedPosition` when it's not scrolling to top
* fix regression of `isPathsFirstDirectorySame()` from d5fcc95
@ app/router.options.ts
@ fe
  • Loading branch information
n0099 committed Jun 25, 2024
1 parent 9603faf commit 9046c33
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 21 deletions.
7 changes: 5 additions & 2 deletions fe/src/app/router.options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ export default {
];
},
async scrollBehavior(to, from, savedPosition) {
if (savedPosition !== null && savedPosition.top !== 0)
return savedPosition;

const routeScrollBehavior = useRouteScrollBehaviorStore().get;
if (routeScrollBehavior !== undefined) {
const ret: ReturnType<RouterScrollBehavior> | undefined =
Expand All @@ -73,10 +76,10 @@ export default {
assertRouteNameIsStr(to.name);
assertRouteNameIsStr(from.name);

if (isPathsFirstDirectorySame(to.path, from.path))
if (!isPathsFirstDirectorySame(to.path, from.path))
return { top: 0 };
}

return savedPosition ?? false;
return false;
}
} as RouterConfig;
5 changes: 5 additions & 0 deletions fe/src/components/Post/PostNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ const noScriptStyle = `<style>
}
</style>`; // https://github.com/nuxt/nuxt/issues/13848
useHead({ noscript: [{ innerHTML: noScriptStyle }] });
const postNavDisplay = ref('none'); // using media query in css instead of js before hydrate
onMounted(() => { postNavDisplay.value = 'unset' });
const threadMenuKey = (cursor: Cursor, tid: Tid) => `c${cursor}-t${tid}`;
const routeHash = (tid: Tid | string | null, pid?: Pid | string) => `#${pid ?? (tid === null ? '' : `t${tid}`)}`;
Expand Down Expand Up @@ -216,6 +218,9 @@ watchEffect(() => {
}
@media (max-width: 900px) {
.post-nav {
display: v-bind(postNavDisplay);
}
.post-nav[aria-expanded=true], .post-nav[aria-expanded=true] + .post-nav-expand {
position: fixed;
z-index: 1040;
Expand Down
5 changes: 3 additions & 2 deletions fe/src/components/Post/renderers/list/RendererList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ provideUsers(props.initialPosts.users);
export type ThreadWithGroupedSubReplies<AdditionalSubReply extends SubReply = never> =
Thread & { replies: Array<Reply & { subReplies: Array<AdditionalSubReply | SubReply[]> }> };
const posts = computed(() => {
// https://github.com/microsoft/TypeScript/issues/33591
const newPosts = refDeepClone(props.initialPosts) as
// https://github.com/TanStack/query/pull/6657
// eslint-disable-next-line unicorn/prefer-structured-clone
const newPosts = _.cloneDeep(props.initialPosts) as // https://github.com/microsoft/TypeScript/issues/33591
Modify<ApiPosts['response'], { threads: Array<ThreadWithGroupedSubReplies<SubReply>> }>;
newPosts.threads = newPosts.threads.map(thread => {
thread.replies = thread.replies.map(reply => {
Expand Down
53 changes: 36 additions & 17 deletions fe/src/pages/posts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ const route = useRoute();
const queryClient = useQueryClient();
const queryParam = ref<ApiPosts['queryParam']>();
const shouldFetch = ref(false);
const isRouteNewQuery = ref(false);
const initialPageCursor = ref<Cursor>('');
const { data, error, isPending, isFetching, isFetched, dataUpdatedAt, errorUpdatedAt, fetchNextPage, isFetchingNextPage, hasNextPage } =
useApiPosts(queryParam, { initialPageParam: initialPageCursor });
Expand Down Expand Up @@ -69,26 +68,37 @@ useHead({
})
});
let startTime = 0;
watch(isFetching, () => {
if (isFetching.value)
startTime = Date.now();
const queryStartedAtSSR = useState('postsQuerySSRStartTime', () => 0);
let queryStartedAt = 0;
watchEffect(() => {
if (!isFetching.value)
return;
if (import.meta.server)
queryStartedAtSSR.value = Date.now();
if (import.meta.client)
queryStartedAt = Date.now();
});
watch([dataUpdatedAt, errorUpdatedAt], async (updatedAt: UnixTimestamp[]) => {
const maxUpdatedAt = Math.max(...updatedAt);
if (maxUpdatedAt === 0) // just starts to fetch, defer watching to next time
return;
const isCached = maxUpdatedAt < startTime;
const networkTime = isCached ? 0 : maxUpdatedAt - startTime;
const isQueriedBySSR = queryStartedAtSSR.value < queryStartedAt;
if (isQueriedBySSR) {
queryStartedAt = queryStartedAtSSR.value;
queryStartedAtSSR.value = Infinity;
}
const isQueryCached = maxUpdatedAt < queryStartedAt;
const networkDuration = isQueryCached ? 0 : maxUpdatedAt - queryStartedAt;
await nextTick(); // wait for child components to finish dom update
if (isRouteNewQuery.value)
window.scrollTo({ top: 0 });
const fetchedPage = data.value?.pages.find(i => i.pages.currentCursor === getRouteCursorParam(route));
const renderDuration = Date.now() - queryStartedAt - networkDuration;
const fetchedPage = data.value?.pages.find(i =>
i.pages.currentCursor === getRouteCursorParam(route));
const postCount = _.sum(Object.values(fetchedPage?.pages.matchQueryPostCount ?? {}));
const renderTime = (Date.now() - startTime - networkTime) / 1000;
notyShow('success', `已加载${postCount}条记录
前端耗时${renderTime.toFixed(2)}s
${isCached ? '使用前端本地缓存' : `后端+网络耗时${_.round(networkTime / 1000, 2)}s`}`);
前端耗时${_.round(renderDuration / 1000, 2)}s
${isQueriedBySSR ? '使用服务端渲染预请求' : ''}
${isQueryCached ? '使用前端本地缓存' : `后端+网络耗时${_.round(networkDuration / 1000, 2)}s`}`);
});
watch(isFetched, async () => {
if (isFetched.value && renderType.value === 'list') {
Expand All @@ -115,11 +125,20 @@ const parseRouteThenFetch = async (newRoute: RouteLocationNormalized) => {
setQueryParam({ query: JSON.stringify(flattenParams) });
};
onBeforeRouteUpdate(async (to, from) => {
/** {@link onBeforeRouteUpdate()} fires too early for allowing navigation guard to cancel updating */
// but we don't need cancelling and there's no onAfterRoute*() events instead of navigation guard available
/** watch on {@link useRoute()} directly won't get reactive since it's not returning {@link ref} but a plain {@link Proxy} */
// https://old.reddit.com/r/Nuxt/comments/15bwb24/how_to_watch_for_route_change_works_on_dev_not/jtspj6b/
/** watch deeply on object {@link route.query} and {@link route.params} for nesting params */
/** and allowing reconstruct partial route to pass it as a param of {@link compareRouteIsNewQuery()} */
/** ignoring string {@link route.name} or {@link route.path} since switching root level route */
// will unmounted the component of current page route and unwatch this watcher
watchDeep(() => [route.query, route.params], async (_discard, oldQueryAndParams) => {
const [to, from] = [route, { query: oldQueryAndParams[0], params: oldQueryAndParams[1] } as RouteLocationNormalized];
const isTriggeredByQueryForm = useTriggerRouteUpdateStore()
.isTriggeredBy('<QueryForm>@submit', { ...to, force: true });
isRouteNewQuery.value = to.hash === ''
&& (isTriggeredByQueryForm || compareRouteIsNewQuery(to, from));
.isTriggeredBy('<QueryForm>@submit', _.merge(to, { force: true }));
if (to.hash === '' && (isTriggeredByQueryForm || compareRouteIsNewQuery(to, from)))
void nextTick(() => { window.scrollTo({ top: 0 }) });
await parseRouteThenFetch(to);
/** must invoke {@link parseRouteThenFetch()} before {@link queryClient.resetQueries()} */
Expand Down

0 comments on commit 9046c33

Please sign in to comment.