山亭建设局网站,网站建设礻首选金手指,网站先做前台还是后台,公众号怎么制作模版文章目录 一、项目起航#xff1a;项目初始化与配置二、React 与 Hook 应用#xff1a;实现项目列表三、TS 应用#xff1a;JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook… 文章目录 一、项目起航项目初始化与配置二、React 与 Hook 应用实现项目列表三、TS 应用JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook路由与 URL 状态管理八、用户选择器与项目编辑功能1.实现id-select.tsx解决id类型 难题2.抽象user-select组件选择用户3.自定义 Star 组件做项目收藏标记 学习内容来源React React Hook TS 最佳实践-慕课网 相对原教程我在学习开始时2023.03采用的是当前最新版本
项版本react react-dom^18.2.0react-router react-router-dom^6.11.2antd^4.24.8commitlint/cli commitlint/config-conventional^17.4.4eslint-config-prettier^8.6.0husky^8.0.3lint-staged^13.1.2prettier2.8.4json-server0.17.2craco-less^2.0.0craco/craco^7.1.0qs^6.11.0dayjs^1.11.7react-helmet^6.1.0types/react-helmet^6.1.6react-query^6.1.0welldone-software/why-did-you-render^7.0.1emotion/react emotion/styled^11.10.6
具体配置、操作和内容会有差异“坑”也会有所不同。。。 一、项目起航项目初始化与配置 一、项目起航项目初始化与配置 二、React 与 Hook 应用实现项目列表 二、React 与 Hook 应用实现项目列表 三、TS 应用JS神助攻 - 强类型 三、 TS 应用JS神助攻 - 强类型 四、JWT、用户认证与异步请求 四、 JWT、用户认证与异步请求(上) 四、 JWT、用户认证与异步请求(下) 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(上) 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(下) 六、用户体验优化 - 加载中和错误状态处理 六、用户体验优化 - 加载中和错误状态处理(上) 六、用户体验优化 - 加载中和错误状态处理(中) 六、用户体验优化 - 加载中和错误状态处理(下) 七、Hook路由与 URL 状态管理 七、Hook路由与 URL 状态管理(上) 七、Hook路由与 URL 状态管理(中) 七、Hook路由与 URL 状态管理(下) 八、用户选择器与项目编辑功能
1.实现id-select.tsx解决id类型 难题
上一节最后的 bug 可以通过自定义组件来优化掉
新建 src\types\index.ts 存放常用类型
export type SN string | number新建组件 src\components\id-select.tsx
import { Select } from antd
import { SN } from typestype SelectProps React.ComponentPropstypeof Select// 类型不是简单的后来者居上而是寻求最大公约数的方式
interface IdSelectProps extends OmitSelectProps, value | onChange | options{value: SN | null | undefined,onChange: (value?: number) void,defaultOptionName?: string,options?: {name: string, id: number}[]
}/*** value 可以传入多种类型的值* onChange 只会回调 number | undefined 类型* 当isNaN(Number(value)) 为 true 的时候代表选择默认类型* 当选择默认类型时onChange 会回调 undefined* param props */
export const IdSelect (props: IdSelectProps) {const { value, onChange, defaultOptionName, options, ...restProps } propsreturn Selectvalue{toNumber(value)}onChange{value onChange(toNumber(value) || undefined)}{ ...restProps }{defaultOptionName ? Select.Option value{0}{defaultOptionName}/Select.Option : null}{options?.map(option Select.Option key{option.id} value{option.id}{option.name}/Select.Option)}/Select
}const toNumber (value: unknown) isNaN(Number(value)) ? 0 : Number(value)2.抽象user-select组件选择用户
修改 src\screens\ProjectList\components\List.tsx将 Project 中的 id 和 personId 类型统一改为 number
...
export interface Project {id: number;...personId: number;...
}
...修改 src\screens\ProjectList\components\SearchPanel.tsx将 User 中的 id 改为 number 类型使用Utility Types处理 Project 类型 生成 param 的可选子类型
...
import { Project } from ./List;export interface User {id: number;...
}
interface SearchPanelProps {...param: PartialPickProject, name | personId...
}
...Partial将每个子类型转换为可选类型Pick经过 泛型约束 生成一个新类型 由于从 URL 中得到的数据都是 string 类型因此需要特殊处理接下来将这部分单独抽离出来
新建 src\screens\ProjectList\utils.ts
import { useUrlQueryParam } from utils/url;export const useProjectsSearchParams () {const [param, setParam] useUrlQueryParam([name, personId]);return [{...param, personId: Number(param.personId) || undefined},setParam] as const
}在 src\screens\ProjectList\index.tsx 中调用它
...
import { useProjectsSearchParams } from ./utils;export const ProjectList () {useDocumentTitle(项目列表)const [param, setParam] useProjectsSearchParams()...
};
...接下来重头戏来了
新建 src\components\user-select.tsx
import { useUsers } from utils/use-users;
import { IdSelect } from ./id-select;export const UserSelect (props: React.ComponentPropstypeof IdSelect) {const {data: users} useUsers()return IdSelect options{users || []} {...props}/
};在 src\screens\ProjectList\components\SearchPanel.tsx 中调用 UserSelect 组件
...
import { UserSelect } from components/user-select;...
export const SearchPanel ({ users, param, setParam }: SearchPanelProps) {return (Form {...}...Form.ItemUserSelectdefaultOptionName负责人value{param.personId}onChange{(value) setParam({ ...param, personId: value, })}//Form.Item/Form);
};查看页面效果又发生了熟悉的事情。。。无限循环
打开 wdyr 的开关查找原因发现之前的 useUrlQueryParam 中的 param 使用 useMemo 后不再创建新对象但是经过 useProjectsSearchParams 处理每次返回的又是新对象那还是老办法用 useMemo 解决
修改 src\screens\ProjectList\utils.ts
import { useMemo } from react;
...
// 项目列表搜索的参数
export const useProjectsSearchParams () {...return [useMemo(() ({...param, personId: Number(param.personId) || undefined}), [param]),setParam] as const
}查看页面问题解决
还有个特别小的问题一般情况下容易忽略
当切换到某个具体负责人时刷新页面带参链接首次加载时userSelect 组件在 users 数据请求回来之前由于找不到匹配项会短暂显示 personId
接下来解决一下
修改 src\components\id-select.tsx请求到 users 数据之前值为 0即显示默认选项负责人
...
export const IdSelect (props: IdSelectProps) {...return (Selectvalue{options?.length ? toNumber(value) : 0}{...}.../Select);
};
...查看页面效果完美
3.自定义 Star 组件做项目收藏标记
为每个项目新增一个收藏标记
新建组件 Star src\components\star.tsx
import { Rate } from antd;interface StarProps extends React.ComponentPropstypeof Rate {checked: boolean,onCheckedChange?: (checked: boolean) void
}export const Star ({checked, onCheckedChange, ...restProps}: StarProps) {return Ratecount{1}value{checked ? 1 : 0}onChange{num onCheckedChange?.(!!num)}{...restProps}/
}注意组件原有属性的透传评分 Rate - Ant Design 新增 编辑和新增 的 Custom Hook src\utils\project.ts
...
export const useEditProject () {const client useHttp();const { run, ...asyncResult } useAsyncProject[]();const mutate (params: PartialProject) {return run(client(projects/${params.id}, {data: params,method: PATCH}))}return {mutate,...asyncResult};
};export const useAddProject () {const client useHttp();const { run, ...asyncResult } useAsyncProject[]();const mutate (params: PartialProject) {return run(client(projects/${params.id}, {data: params,method: POST}))}return {mutate,...asyncResult};
};这部分在构思时需要考虑到Hook 只能在 函数组件内的最外层使用不能在外面再嵌套其他非组件的普通函数因此需要提前暴露出一个函数来接收参数并处理相关逻辑闭包的应用否则会出现下面的报错 React Hook useEditProject cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function. 编辑 src\screens\ProjectList\components\List.tsx(使用 Star 组件)
...
import { Star } from components/star;
import { useEditProject } from utils/project;
...
// type PropsType OmitListProps, users
export const List ({ users, ...props }: ListProps) {const { mutate } useEditProject()// 函数式编程 柯里化const starProject (id: number) (star: boolean) mutate({id, star})return (Tablepagination{false}columns{[{title: Star checked{true} disabled{true}/,render: (val, record) Starchecked{record.star}// stared starProject(record.id)(stared)onCheckedChange{starProject(record.id)}/},...]}{...props}/Table);
}; 在计算机科学中柯里化英语Currying又译为卡瑞化或加里化是把接受多个参数的函数变换成接受一个单一参数最初函数的第一个参数的函数并且返回接受余下的参数而且返回结果的新函数的技术。柯里化currying又称部分求值。一个柯里化的函数首先会接受一些参数接受了这些参数之后该函数并不会立即求值而是继续返回另外一个函数刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候之前传入的所有参数都会被一次性用于求值。 查看页面点击标记一个但是没有反应控制台 Network 中有网络请求刷新页面再看数据已经更新了这个问题后续解决 部分引用笔记还在草稿阶段敬请期待。。。