From e8934121fe6a41192bae3c941bd2b56dc92a1dd9 Mon Sep 17 00:00:00 2001 From: Wu-kung <1434246346@qq.com> Date: Thu, 7 Nov 2024 16:59:28 +0800 Subject: [PATCH] example: fix typescript type and use loader to ensure data is loaded (#16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 修复 TypeScript 类型问题 * example: Resolved TypeScript type and Using Loaders to ensure data is loaded --- .../src/components/comment/index.tsx | 18 ++++-- .../src/components/item-list/index.tsx | 63 +++++++------------ .../src/components/item-page/index.tsx | 9 ++- .../hackernews/src/components/item/index.tsx | 19 +++++- .../src/components/loading/index.tsx | 11 +++- .../src/components/user-page/index.tsx | 16 ++--- examples/hackernews/src/context/index.tsx | 17 ----- examples/hackernews/src/pages/__root.tsx | 9 +-- examples/hackernews/src/pages/ask/$page.tsx | 10 ++- examples/hackernews/src/pages/index.tsx | 12 +++- .../hackernews/src/pages/item/$itemId.tsx | 14 ++--- examples/hackernews/src/pages/job/$page.tsx | 10 ++- examples/hackernews/src/pages/new/$page.tsx | 10 ++- examples/hackernews/src/pages/show/$page.tsx | 10 ++- examples/hackernews/src/pages/top/$page.tsx | 12 +++- .../hackernews/src/pages/user/$userId.tsx | 9 ++- examples/hackernews/src/services/api.ts | 34 ++++++++++ examples/hackernews/src/types/index.ts | 26 ++++++++ 18 files changed, 199 insertions(+), 110 deletions(-) delete mode 100644 examples/hackernews/src/context/index.tsx create mode 100644 examples/hackernews/src/services/api.ts create mode 100644 examples/hackernews/src/types/index.ts diff --git a/examples/hackernews/src/components/comment/index.tsx b/examples/hackernews/src/components/comment/index.tsx index 19b15e4..db42449 100644 --- a/examples/hackernews/src/components/comment/index.tsx +++ b/examples/hackernews/src/components/comment/index.tsx @@ -4,9 +4,19 @@ import { fetchItem } from '../../services'; import { pluralize, timeAgo } from '../../utils'; import styles from './index.module.less'; -export default function Comment({ id, itemsById }) { +interface CommentProps { + id: number; +} + +interface CommentType { + by: string; + time: number; + text: string; + kids?: number[]; +} +export default function Comment({ id }: CommentProps) { const [open, setOpen] = useState(true); - const [comment, setComment] = useState(null); + const [comment, setComment] = useState(null); useEffect(() => { async function fetchComments() { @@ -45,9 +55,7 @@ export default function Comment({ id, itemsById }) { />
{comment.kids && open - ? comment.kids.map((id) => ( - - )) + ? comment.kids.map((id) => ) : null}
diff --git a/examples/hackernews/src/components/item-list/index.tsx b/examples/hackernews/src/components/item-list/index.tsx index c0ef584..87e5f1b 100644 --- a/examples/hackernews/src/components/item-list/index.tsx +++ b/examples/hackernews/src/components/item-list/index.tsx @@ -1,49 +1,29 @@ -import { useContext, useEffect, useMemo, useState } from 'react'; -import { Link } from '@umijs/tnf/router'; -import { Context } from '../../context'; -import { fetchIdsByType, fetchItems } from '../../services'; +import React, { Link } from '@umijs/tnf/router'; +import type { ItemProps } from '../../types'; import Item from '../item'; -import Loading from '../loading'; import styles from './index.module.less'; -export default function ItemList({ type, page }) { - const { dispatch } = useContext(Context); - const [lists, setLists] = useState({ - top: [], - new: [], - show: [], - ask: [], - job: [], - }); - const [items, setItems] = useState([]); - const [loading, setLoading] = useState(true); - - const itemsPerPage = 20; - const maxPage = useMemo( - () => Math.ceil(lists[type].length / itemsPerPage), - [lists], - ); - - useEffect(() => { - async function fetchList() { - const ids = await fetchIdsByType(type); - setLists({ ...lists, [type]: ids }); - const items = await fetchItems( - ids.slice(itemsPerPage * (page - 1), itemsPerPage * page), - ); - const itemsById = items.reduce((_memo, item) => { - const memo = _memo; - memo[item.id] = item; - return memo; - }, {}); - setLoading(false); - setItems(items); - dispatch({ type: 'saveItems', payload: itemsById }); - } +interface ItemListProps { + type: keyof Lists; + page: number; + maxPage: number; + items: ItemProps[]; +} - fetchList(); - }, [page]); +interface Lists { + top: number[]; + new: number[]; + show: number[]; + ask: number[]; + job: number[]; +} +export default function ItemList({ + type, + items, + page, + maxPage, +}: ItemListProps) { return ( <>
@@ -65,7 +45,6 @@ export default function ItemList({ type, page }) {
- {items.map((item) => ( ))} diff --git a/examples/hackernews/src/components/item-page/index.tsx b/examples/hackernews/src/components/item-page/index.tsx index 8053a72..c28469a 100644 --- a/examples/hackernews/src/components/item-page/index.tsx +++ b/examples/hackernews/src/components/item-page/index.tsx @@ -1,9 +1,10 @@ -import { Link } from '@umijs/tnf/router'; +import React, { Link } from '@umijs/tnf/router'; +import type { ItemProps } from '../../types'; import { host, timeAgo } from '../../utils'; import Comment from '../comment'; import styles from './index.module.less'; -export default function ItemPage({ item, itemsById }) { +export default function ItemPage({ item }: { item: ItemProps }) { if (!item) return null; return ( <> @@ -26,9 +27,7 @@ export default function ItemPage({ item, itemsById }) {

{item.kids - ? item.kids.map((id) => ( - - )) + ? item.kids.map((id) => ) : null}
diff --git a/examples/hackernews/src/components/item/index.tsx b/examples/hackernews/src/components/item/index.tsx index 12dfbe9..7052d58 100644 --- a/examples/hackernews/src/components/item/index.tsx +++ b/examples/hackernews/src/components/item/index.tsx @@ -1,8 +1,21 @@ -import { Link } from '@umijs/tnf/router'; +import React, { Link } from '@umijs/tnf/router'; import { host, timeAgo } from '../../utils'; import styles from './index.module.less'; -export default function Item({ item }) { +interface ItemProps { + item: { + score: number; + title: string; + url?: string; + type: string; + id: number; + by: string; + descendants: number; + time: number; + }; +} + +export default function Item({ item }: ItemProps) { const { score, title, url, type, id, by, descendants, time } = item; return ( @@ -17,7 +30,7 @@ export default function Item({ item }) { ({host(url)}) ) : ( - + {title} )} diff --git a/examples/hackernews/src/components/loading/index.tsx b/examples/hackernews/src/components/loading/index.tsx index 1c64ecd..d069772 100644 --- a/examples/hackernews/src/components/loading/index.tsx +++ b/examples/hackernews/src/components/loading/index.tsx @@ -1,10 +1,15 @@ +import React from 'react'; import classnames from 'classnames'; import styles from './index.module.less'; -function Spinner({ loading }) { +interface SpinnerProps { + loading: boolean; +} + +function Spinner({ loading }: SpinnerProps) { const svgCls = classnames({ - [styles.spinner]: true, - [styles.show]: loading, + [styles.spinner as string]: true, + [styles.show as string]: loading, }); return (
diff --git a/examples/hackernews/src/components/user-page/index.tsx b/examples/hackernews/src/components/user-page/index.tsx index 35607fe..a357a4f 100644 --- a/examples/hackernews/src/components/user-page/index.tsx +++ b/examples/hackernews/src/components/user-page/index.tsx @@ -1,13 +1,15 @@ -import { useEffect, useState } from 'react'; -import { fetchUser } from '../../services'; +import React from 'react'; import { timeAgo } from '../../utils'; import styles from './index.module.less'; -export default function UserPage({ id }) { - const [user, setUser] = useState(null); - useEffect(() => { - fetchUser(id).then((user) => setUser(user)); - }, []); +interface User { + id: string; + created: number; + karma: number; + about?: string; +} + +export default function UserPage({ user }: { user: User }) { if (!user) return null; return (
diff --git a/examples/hackernews/src/context/index.tsx b/examples/hackernews/src/context/index.tsx deleted file mode 100644 index 081dce4..0000000 --- a/examples/hackernews/src/context/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { createContext } from 'react'; - -export const initialState = { - itemsById: {}, -}; - -export const Context = createContext(initialState); - -export const reducer = (state, action) => { - switch (action.type) { - case 'saveItems': - return { - ...state, - itemsById: action.payload, - }; - } -}; diff --git a/examples/hackernews/src/pages/__root.tsx b/examples/hackernews/src/pages/__root.tsx index 8ec5f12..82743c5 100644 --- a/examples/hackernews/src/pages/__root.tsx +++ b/examples/hackernews/src/pages/__root.tsx @@ -1,12 +1,9 @@ -import React, { useReducer } from 'react'; -import { Link, Outlet, createRootRoute } from '@umijs/tnf/router'; -import { Context, initialState, reducer } from '../context'; +import React, { Link, Outlet, createRootRoute } from '@umijs/tnf/router'; import '../global.less'; import styles from './index.module.less'; export const Route = createRootRoute({ component: () => { - const [state, dispatch] = useReducer(reducer, initialState); return ( <>
@@ -66,9 +63,7 @@ export const Route = createRootRoute({
- - - +
); diff --git a/examples/hackernews/src/pages/ask/$page.tsx b/examples/hackernews/src/pages/ask/$page.tsx index 23c55d4..7c36db7 100644 --- a/examples/hackernews/src/pages/ask/$page.tsx +++ b/examples/hackernews/src/pages/ask/$page.tsx @@ -1,11 +1,19 @@ import React, { createFileRoute } from '@umijs/tnf/router'; import ItemList from '../../components/item-list'; +import { fetchList } from '../../services/api'; +import type { Params } from '../../types'; export const Route = createFileRoute('/ask/$page')({ component: AskComponent, + loader: async ({ params }: { params: Params }) => + await fetchList('ask', Number(params.page)), }); function AskComponent() { const { page } = Route.useParams(); - return ; + const { items, maxPage } = Route.useLoaderData(); + + return ( + + ); } diff --git a/examples/hackernews/src/pages/index.tsx b/examples/hackernews/src/pages/index.tsx index b85da2f..b8f76f4 100644 --- a/examples/hackernews/src/pages/index.tsx +++ b/examples/hackernews/src/pages/index.tsx @@ -1,12 +1,18 @@ -import { createFileRoute } from '@umijs/tnf/router'; +import React, { createFileRoute } from '@umijs/tnf/router'; import ItemList from '../components/item-list'; +import { fetchList } from '../services/api'; +import type { Params } from '../types'; export const Route = createFileRoute('/')({ component: TopComponent, + loader: async ({ params }: { params: Params }) => + await fetchList('top', Number(params?.page ?? '1') ?? 1), }); function TopComponent() { - const page = Number(Route.useParams()?.page ?? '1') ?? 1; + const params = Route.useParams(); + const page = Number(params?.page ?? '1') ?? 1; + const { items, maxPage } = Route.useLoaderData(); - return ; + return ; } diff --git a/examples/hackernews/src/pages/item/$itemId.tsx b/examples/hackernews/src/pages/item/$itemId.tsx index 58000d0..5acc82e 100644 --- a/examples/hackernews/src/pages/item/$itemId.tsx +++ b/examples/hackernews/src/pages/item/$itemId.tsx @@ -1,16 +1,14 @@ -import React, { useContext } from 'react'; -import { createFileRoute } from '@umijs/tnf/router'; +import React, { createFileRoute } from '@umijs/tnf/router'; import ItemPage from '../../components/item-page'; -import { Context } from '../../context'; +import { fetchItem } from '../../services'; export const Route = createFileRoute('/item/$itemId')({ component: Item, + loader: async ({ params }: { params: { itemId: string } }) => + await fetchItem(params.itemId), }); function Item() { - const { itemId } = Route.useParams(); - const { state } = useContext(Context); - return ( - - ); + const item = Route.useLoaderData(); + return ; } diff --git a/examples/hackernews/src/pages/job/$page.tsx b/examples/hackernews/src/pages/job/$page.tsx index 762688f..e40fc85 100644 --- a/examples/hackernews/src/pages/job/$page.tsx +++ b/examples/hackernews/src/pages/job/$page.tsx @@ -1,11 +1,19 @@ import React, { createFileRoute } from '@umijs/tnf/router'; import ItemList from '../../components/item-list'; +import { fetchList } from '../../services/api'; +import type { Params } from '../../types'; export const Route = createFileRoute('/job/$page')({ component: JobComponent, + loader: async ({ params }: { params: Params }) => + await fetchList('job', Number(params.page)), }); function JobComponent() { const { page } = Route.useParams(); - return ; + const { items, maxPage } = Route.useLoaderData(); + + return ( + + ); } diff --git a/examples/hackernews/src/pages/new/$page.tsx b/examples/hackernews/src/pages/new/$page.tsx index 8b6060f..9229a22 100644 --- a/examples/hackernews/src/pages/new/$page.tsx +++ b/examples/hackernews/src/pages/new/$page.tsx @@ -1,11 +1,19 @@ import React, { createFileRoute } from '@umijs/tnf/router'; import ItemList from '../../components/item-list'; +import { fetchList } from '../../services/api'; +import type { Params } from '../../types'; export const Route = createFileRoute('/new/$page')({ component: NewComponent, + loader: async ({ params }: { params: Params }) => + await fetchList('new', Number(params.page)), }); function NewComponent() { const { page } = Route.useParams(); - return ; + const { items, maxPage } = Route.useLoaderData(); + + return ( + + ); } diff --git a/examples/hackernews/src/pages/show/$page.tsx b/examples/hackernews/src/pages/show/$page.tsx index 3f20297..a32a708 100644 --- a/examples/hackernews/src/pages/show/$page.tsx +++ b/examples/hackernews/src/pages/show/$page.tsx @@ -1,11 +1,19 @@ import React, { createFileRoute } from '@umijs/tnf/router'; import ItemList from '../../components/item-list'; +import { fetchList } from '../../services/api'; +import type { Params } from '../../types'; export const Route = createFileRoute('/show/$page')({ component: ShowComponent, + loader: async ({ params }: { params: Params }) => + await fetchList('show', Number(params.page)), }); function ShowComponent() { const { page } = Route.useParams(); - return ; + const { items, maxPage } = Route.useLoaderData(); + + return ( + + ); } diff --git a/examples/hackernews/src/pages/top/$page.tsx b/examples/hackernews/src/pages/top/$page.tsx index 7b94421..d3bdc05 100644 --- a/examples/hackernews/src/pages/top/$page.tsx +++ b/examples/hackernews/src/pages/top/$page.tsx @@ -1,13 +1,19 @@ import React, { createFileRoute } from '@umijs/tnf/router'; import ItemList from '../../components/item-list'; +import { fetchList } from '../../services/api'; +import type { Params } from '../../types'; export const Route = createFileRoute('/top/$page')({ component: TopComponent, - validateSearch: (search) => {}, - loader: async () => {}, + loader: async ({ params }: { params: Params }) => + await fetchList('top', Number(params.page)), }); function TopComponent() { const { page } = Route.useParams(); - return ; + const { items, maxPage } = Route.useLoaderData(); + + return ( + + ); } diff --git a/examples/hackernews/src/pages/user/$userId.tsx b/examples/hackernews/src/pages/user/$userId.tsx index dfe18fe..7bdc0be 100644 --- a/examples/hackernews/src/pages/user/$userId.tsx +++ b/examples/hackernews/src/pages/user/$userId.tsx @@ -1,12 +1,15 @@ import React, { createFileRoute } from '@umijs/tnf/router'; import UserPage from '../../components/user-page'; +import { fetchUser } from '../../services'; export const Route = createFileRoute('/user/$userId')({ component: User, + loader: async ({ params }: { params: { userId: string } }) => + await fetchUser(params.userId), }); function User() { - const { userId } = Route.useParams(); - console.log('userId', userId); - return ; + const user = Route.useLoaderData(); + + return ; } diff --git a/examples/hackernews/src/services/api.ts b/examples/hackernews/src/services/api.ts new file mode 100644 index 0000000..b2ff8d4 --- /dev/null +++ b/examples/hackernews/src/services/api.ts @@ -0,0 +1,34 @@ +import type { FetchListResult, ItemProps, ListData } from '../types'; +import { fetchIdsByType, fetchItems } from './index'; + +export async function fetchList( + type: string, + page: number = 1, +): Promise { + const itemsPerPage = 20; + let lists: ListData = {}; + if (!lists[type]) { + lists[type] = []; + } + const ids = await fetchIdsByType(type); + lists[type] = [...(lists[type] || []), ...ids]; + const maxPage = Math.ceil((lists[type]?.length || 0) / itemsPerPage); + const items = await fetchItems( + ids.slice(itemsPerPage * (page - 1), itemsPerPage * page), + ); + const itemsById = items.reduce( + (_memo, item) => { + const memo = _memo; + memo[item.id] = item; + return memo; + }, + {} as Record, + ); + + return { + items, + maxPage, + lists, + itemsById, + }; +} diff --git a/examples/hackernews/src/types/index.ts b/examples/hackernews/src/types/index.ts new file mode 100644 index 0000000..26b7398 --- /dev/null +++ b/examples/hackernews/src/types/index.ts @@ -0,0 +1,26 @@ +export interface ListData { + [type: string]: number[]; +} + +export interface ItemProps { + id: number; + score: number; + title: string; + url?: string; + type: string; + by: string; + descendants: number; + time: number; + kids?: number[]; +} + +export interface FetchListResult { + items: ItemProps[]; + maxPage: number; + lists: ListData; + itemsById: Record; +} + +export interface Params { + page: string; +}