公司网站制作高端,iis6无法新建网站,建站大师排名表2021,锦州网站建设公司文章目录 一、项目起航#xff1a;项目初始化与配置二、React 与 Hook 应用#xff1a;实现项目列表三、TS 应用#xff1a;JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook… 文章目录 一、项目起航项目初始化与配置二、React 与 Hook 应用实现项目列表三、TS 应用JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook路由与 URL 状态管理八、用户选择器与项目编辑功能九、深入React 状态管理与Redux机制1.useCallback应用优化异步请求2.状态提升组合组件与控制反转 学习内容来源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 状态管理(下) 八、用户选择器与项目编辑功能 八、用户选择器与项目编辑功能(上) 八、用户选择器与项目编辑功能(下) 九、深入React 状态管理与Redux机制
1.useCallback应用优化异步请求
当前项目中使用 useAsync 进行异步请求但是其中有一个隐藏 bug若是在页面中发起一个请求这个请求需要较长时间3s可以使用开发控制台设置请求最短时间来预设场景在这个时间段内退出登录此时就会有报错
Warning: Can’t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.原因是虽然退出登录组件销毁但是异步函数还在执行当它执行完进行下一步操作 setXXX 或是 更新组件都找不到对应已销毁的组件。
接下来解决一下这个问题。
编辑 src\utils\index.ts
...
/*** 返回组件的挂载状态如果还没有挂载或者已经卸载返回 false; 反之返回 true;*/
export const useMountedRef () {const mountedRef useRef(false)useEffect(() {mountedRef.current truereturn () {mountedRef.current false}}, [])return mountedRef
}在 src\utils\use-async.ts 上应用
...
import { useMountedRef } from utils;
...
export const useAsync D(...) {...const mountedRef useMountedRef()...const run (...) {...return promise.then((data) {if(mountedRef.current)setData(data);return data;}).catch((error) {...});};...
};还有个遗留问题在 useEffect 中使用的变量若是没有在依赖数组中添加就会报错添加上又会造成死循环因此之前用 eslint-disable-next-line 解决
// eslint-disable-next-line react-hooks/exhaustive-deps现在换个方案使用 useMemo 当然可以解决这里推荐使用特殊版本的 useMemo, useCallback
修改 src\utils\use-async.ts
import { useCallback, useState } from react;
...export const useAsync D(...) {...const setData useCallback((data: D) setState({data,stat: success,error: null,}), [])const setError useCallback((error: Error) setState({error,stat: error,data: null,}), [])// run 来触发异步请求const run useCallback((...) {...}, [config.throwOnError, mountedRef, setData, state, setError],)...
};可以按照提示配置依赖React Hook useCallback has missing dependencies: config.throwOnError, mountedRef, setData, and state. Either include them or remove the dependency array. You can also do a functional update setState(s ...) if you only need state in the setState call.e 尽管如此但还是难免会出现在 useCallback 中改变 依赖值的行为比如依赖值 XXX 对应的 setXXX这时需要用到 setXXX 的函数用法这样也可以省去一个依赖
继续修改 src\utils\use-async.ts
...
export const useAsync D(...) {...const run useCallback((...) {...setState(prevState ({ ...prevState, stat: loading }));...}, [config.throwOnError, mountedRef, setData, setError],)...
};修改 src\utils\project.ts
...
import { useCallback, useEffect } from react;
...export const useProjects (...) {...const fetchProject useCallback(() client(projects, { data: cleanObject(param || {})}), [client, param])useEffect(() {run(fetchProject(), { rerun: fetchProject });}, [param, fetchProject, run]);...
};
...修改 src\utils\http.ts
...
import { useCallback } from react;
...
export const useHttp () {...return useCallback((...[funcPath, customConfig]: Parameterstypeof http) http(funcPath, { ...customConfig, token: user?.token }), [user?.token]);
};
总结非状态类型需要作为依赖 就要将其使用 useMemo 或者 useCallback 包裹依赖细化 新旧关联常见于 Custom Hook 中函数类型数据的返回
2.状态提升组合组件与控制反转
接下来定制化一个项目编辑模态框编辑新建项目PageHeader hover 后可以打开新建ProjectList 中可以打开模态框新建里面的 List 的每行也可以打开模态框编辑
在 src\components\lib.tsx 中新增 padding 为 0 的 Button
...
export const ButtonNoPadding styled(Button)padding: 0;新建 src\screens\ProjectList\components\ProjectModal.tsx模态框
import { Button, Drawer } from antdexport const ProjectModal ({isOpen, onClose}: { isOpen: boolean, onClose: () void }) {return Drawer onClose{onClose} open{isOpen} width100%h1Project Modal/h1Button onClick{onClose}关闭/Button/Drawer
}新建 src\screens\ProjectList\components\ProjectPopover.tsx
import styled from emotion/styled
import { Divider, List, Popover, Typography } from antd
import { ButtonNoPadding } from components/lib
import { useProjects } from utils/projectexport const ProjectPopover ({ setIsOpen }: { setIsOpen: (isOpen: boolean) void }) {const { data: projects } useProjects()const starProjects projects?.filter(i i.star)const content ContentContainerTypography.Text typesecondary收藏项目/Typography.TextList {starProjects?.map(project List.ItemList.Item.Meta title{project.name}//List.Item)}/ListDivider/ButtonNoPadding typelink onClick{() setIsOpen(true)}创建项目/ButtonNoPadding/ContentContainerreturn Popover placementbottom content{content}项目/Popover
}const ContentContainer styled.divwidth: 30rem;编辑 src\authenticated-app.tsx(引入 ButtonNoPadding、 ProjectPopover、 ProjectModal 自定义组件并将模态框的状态管理方法传到对应组件 PageHeader 和 ProjectList注意接收方要定义好类型)
...
import { ButtonNoPadding, Row } from components/lib;
...
import { ProjectModal } from screens/ProjectList/components/ProjectModal;
import { useState } from react;
import { ProjectPopover } from screens/ProjectList/components/ProjectPopover;export const AuthenticatedApp () {const [isOpen, setIsOpen] useState(false)...return (ContainerPageHeader setIsOpen{setIsOpen}/MainRouterRoutesRoute path/projects element{ProjectList setIsOpen{setIsOpen}/} /.../Routes/Router/MainProjectModal isOpen{isOpen} onClose{() setIsOpen(false)}//Container);
};
const PageHeader ({ setIsOpen }: { setIsOpen: (isOpen: boolean) void }) {...return (Header between{true}HeaderLeft gap{true}ButtonNoPadding typelink onClick{resetRoute}SoftwareLogo width18rem colorrgb(38,132,255) //ButtonNoPaddingProjectPopover setIsOpen{setIsOpen}/span用户/span/HeaderLeftHeaderRight.../HeaderRight/Header);
};
...由于涉及登录后多个组件会发起调用因此 ProjectModal 组件需要放在 AuthenticatedApp 的 Container 下 编辑 src\screens\ProjectList\index.tsx引入 模态框的状态管理方法
...
import { Row, Typography } from antd;
...
import { ButtonNoPadding } from components/lib;export const ProjectList ({ setIsOpen }: { setIsOpen: (isOpen: boolean) void }) {...return (ContainerRow justifyspace-betweenh1项目列表/h1ButtonNoPadding typelink onClick{() setIsOpen(true)}创建项目/ButtonNoPadding/Row...ListsetIsOpen{setIsOpen}{...}//Container);
};
...编辑 src\screens\ProjectList\components\List.tsx引入 模态框的状态管理方法
import { Dropdown, MenuProps, Table, TableProps } from antd;
...
import { ButtonNoPadding } from components/lib;
...
interface ListProps extends TablePropsProject {...setIsOpen: (isOpen: boolean) void;
}export const List ({ users, setIsOpen, ...props }: ListProps) {...return (Tablepagination{false}columns{[...{render: (text, project) {const items: MenuProps[items] [{key: edit,label: 编辑,onClick: () setIsOpen(true)},];return Dropdown menu{{ items }}ButtonNoPadding typelink onClick{(e) e.preventDefault()}.../ButtonNoPadding/Dropdown}}]}{...props}/Table);
};可以明显看到这种方式的状态提升prop drilling若是间隔层数较多时定义和使用相隔太远不仅有“下钻”问题而且耦合度太高
下面使用 组件组合component composition的方式解耦 组件组合component composition | Context – React 编辑 src\authenticated-app.tsx将 绑定了模态框 打开方法的 ButtonNoPadding 作为属性传给需要用到的组件
...
export const AuthenticatedApp () {...return (ContainerPageHeader projectButton{ButtonNoPadding typelink onClick{() setIsOpen(true)}创建项目/ButtonNoPadding} /MainRouterRoutesRoutepath/projectselement{ProjectList projectButton{ButtonNoPadding typelink onClick{() setIsOpen(true)}创建项目/ButtonNoPadding} /}/.../Routes/Router/Main.../Container);
};
const PageHeader (props: { projectButton: JSX.Element }) {...return (Header between{true}HeaderLeft gap{true}...ProjectPopover { ...props } /.../HeaderLeftHeaderRight.../HeaderRight/Header);
};
...编辑 src\screens\ProjectList\components\ProjectPopover.tsx使用传入的属性组件代替之前的 绑定了模态框 打开方法的 ButtonNoPadding
...
export const ProjectPopover ({ projectButton }: { projectButton: JSX.Element }) {...const content (ContentContainer...{ projectButton }/ContentContainer);...
};
...编辑 src\screens\ProjectList\index.tsx使用传入的属性组件代替之前的 绑定了模态框 打开方法的 ButtonNoPadding 并继续“下钻”
...
export const ProjectList ({ projectButton }: { projectButton: JSX.Element }) {...return (ContainerRow justifyspace-between...{ projectButton }/Row...ListprojectButton{projectButton}{...}//Container);
};
...编辑 src\screens\ProjectList\components\List.tsx使用传入的属性组件代替之前的 绑定了模态框 打开方法的 ButtonNoPadding
...
interface ListProps extends TablePropsProject {...projectButton: JSX.Element
}// type PropsType OmitListProps, users
export const List ({ users, ...props }: ListProps) {...return (Tablepagination{false}columns{[...{render: (text, project) {return (Dropdown dropdownRender{() props.projectButton}ButtonNoPaddingtypelinkonClick{(e) e.preventDefault()}.../ButtonNoPadding/Dropdown);},},]}{...props}/Table);
};编辑按钮这里使用并不恰当不过这不是最终解决方案理解思路即可浅析控制反转 - 知乎 部分引用笔记还在草稿阶段敬请期待。。。