七、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 = true
return () => {
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]: Parameters<typeof 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 "antd"
export const ProjectModal = ({isOpen, onClose}: { isOpen: boolean, onClose: () => void }) => {
return <Drawer onClose={onClose} open={isOpen} width="100%">
<h1>Project Modal</h1>
<Button 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/project"
export const ProjectPopover = ({ setIsOpen }: { setIsOpen: (isOpen: boolean) => void }) => {
const { data: projects } = useProjects()
const starProjects = projects?.filter(i => i.star)
const content = <ContentContainer>
<Typography.Text type="secondary">收藏项目</Typography.Text>
<List>
{
starProjects?.map(project => <List.Item>
<List.Item.Meta title={project.name}/>
</List.Item>)
}
</List>
<Divider/>
<ButtonNoPadding type='link' onClick={() => setIsOpen(true)}>创建项目</ButtonNoPadding>
</ContentContainer>
return <Popover placement="bottom" content={content}>
项目
</Popover>
}
const ContentContainer = styled.div`
width: 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 (
<Container>
<PageHeader setIsOpen={setIsOpen}/>
<Main>
<Router>
<Routes>
<Route path="/projects" element={<ProjectList setIsOpen={setIsOpen}/>} />
...
</Routes>
</Router>
</Main>
<ProjectModal isOpen={isOpen} onClose={() => setIsOpen(false)}/>
</Container>
);
};
const PageHeader = ({ setIsOpen }: { setIsOpen: (isOpen: boolean) => void }) => {
...
return (
<Header between={true}>
<HeaderLeft gap={true}>
<ButtonNoPadding type="link" onClick={resetRoute}>
<SoftwareLogo width="18rem" color="rgb(38,132,255)" />
</ButtonNoPadding>
<ProjectPopover setIsOpen={setIsOpen}/>
<span>用户</span>
</HeaderLeft>
<HeaderRight>
...
</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 (
<Container>
<Row justify='space-between'>
<h1>项目列表</h1>
<ButtonNoPadding type='link' onClick={() => setIsOpen(true)}>创建项目</ButtonNoPadding>
</Row>
...
<List
setIsOpen={setIsOpen}
{...}
/>
</Container>
);
};
...
编辑 src\screens\ProjectList\components\List.tsx
(引入 模态框的状态管理方法):
import { Dropdown, MenuProps, Table, TableProps } from "antd";
...
import { ButtonNoPadding } from "components/lib";
...
interface ListProps extends TableProps<Project> {
...
setIsOpen: (isOpen: boolean) => void;
}
export const List = ({ users, setIsOpen, ...props }: ListProps) => {
...
return (
<Table
pagination={false}
columns={[
...
{
render: (text, project) => {
const items: MenuProps["items"] = [
{
key: 'edit',
label: "编辑",
onClick: () => setIsOpen(true)
},
];
return <Dropdown menu={{ items }}>
<ButtonNoPadding type="link" onClick={(e) => e.preventDefault()}>...</ButtonNoPadding>
</Dropdown>
}
}
]}
{...props}
></Table>
);
};
可以明显看到,这种方式的状态提升(prop drilling
)若是间隔层数较多时(定义和使用相隔太远),不仅有“下钻”问题,而且耦合度太高
下面使用 组件组合(component composition)的方式解耦
编辑 src\authenticated-app.tsx
(将 绑定了模态框 打开方法的 ButtonNoPadding
作为属性传给需要用到的组件):
...
export const AuthenticatedApp = () => {
...
return (
<Container>
<PageHeader projectButton={
<ButtonNoPadding type="link" onClick={() => setIsOpen(true)}>
创建项目
</ButtonNoPadding>
} />
<Main>
<Router>
<Routes>
<Route
path="/projects"
element={<ProjectList projectButton={
<ButtonNoPadding type="link" onClick={() => setIsOpen(true)}>
创建项目
</ButtonNoPadding>
} />}
/>
...
</Routes>
</Router>
</Main>
...
</Container>
);
};
const PageHeader = (props: { projectButton: JSX.Element }) => {
...
return (
<Header between={true}>
<HeaderLeft gap={true}>
...
<ProjectPopover { ...props } />
...
</HeaderLeft>
<HeaderRight>...</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
并继续“下钻”):
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Linux运维工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Linux运维全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Linux运维知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加VX:vip1024b (备注Linux运维获取)
为了做好运维面试路上的助攻手,特整理了上百道 【运维技术栈面试题集锦】 ,让你面试不慌心不跳,高薪offer怀里抱!
这次整理的面试题,小到shell、MySQL,大到K8s等云原生技术栈,不仅适合运维新人入行面试需要,还适用于想提升进阶跳槽加薪的运维朋友。
本份面试集锦涵盖了
- 174 道运维工程师面试题
- 128道k8s面试题
- 108道shell脚本面试题
- 200道Linux面试题
- 51道docker面试题
- 35道Jenkis面试题
- 78道MongoDB面试题
- 17道ansible面试题
- 60道dubbo面试题
- 53道kafka面试
- 18道mysql面试题
- 40道nginx面试题
- 77道redis面试题
- 28道zookeeper
总计 1000+ 道面试题, 内容 又全含金量又高
- 174道运维工程师面试题
1、什么是运维?
2、在工作中,运维人员经常需要跟运营人员打交道,请问运营人员是做什么工作的?
3、现在给你三百台服务器,你怎么对他们进行管理?
4、简述raid0 raid1raid5二种工作模式的工作原理及特点
5、LVS、Nginx、HAproxy有什么区别?工作中你怎么选择?
6、Squid、Varinsh和Nginx有什么区别,工作中你怎么选择?
7、Tomcat和Resin有什么区别,工作中你怎么选择?
8、什么是中间件?什么是jdk?
9、讲述一下Tomcat8005、8009、8080三个端口的含义?
10、什么叫CDN?
11、什么叫网站灰度发布?
12、简述DNS进行域名解析的过程?
13、RabbitMQ是什么东西?
14、讲一下Keepalived的工作原理?
15、讲述一下LVS三种模式的工作过程?
16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?
17、如何重置mysql root密码?
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
工作中,运维人员经常需要跟运营人员打交道,请问运营人员是做什么工作的?
3、现在给你三百台服务器,你怎么对他们进行管理?
4、简述raid0 raid1raid5二种工作模式的工作原理及特点
5、LVS、Nginx、HAproxy有什么区别?工作中你怎么选择?
6、Squid、Varinsh和Nginx有什么区别,工作中你怎么选择?
7、Tomcat和Resin有什么区别,工作中你怎么选择?
8、什么是中间件?什么是jdk?
9、讲述一下Tomcat8005、8009、8080三个端口的含义?
10、什么叫CDN?
11、什么叫网站灰度发布?
12、简述DNS进行域名解析的过程?
13、RabbitMQ是什么东西?
14、讲一下Keepalived的工作原理?
15、讲述一下LVS三种模式的工作过程?
16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?
17、如何重置mysql root密码?
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-u6FGYUIc-1712878560069)]