Article Directory
Source of learning content: React + React Hook + TS Best Practice - MOOC
Compared with the original tutorial, I used the latest version at the beginning of my study (2023.03):
item | Version |
---|---|
react & react-dom | ^18.2.0 |
react-router & react-router-dom | ^6.11.2 |
antd | ^4.24.8 |
@commitlint/cli & @commitlint/config-conventional | ^17.4.4 |
eslint-config-prettier | ^8.6.0 |
husky | ^8.0.3 |
lint-staged | ^13.1.2 |
prettier | 2.8.4 |
json-server | 0.17.2 |
craco-less | ^2.0.0 |
@craco/craco | ^7.1.0 |
qs | ^6.11.0 |
dayjs | ^1.11.7 |
react-helmet | ^6.1.0 |
@types/react-helmet | ^6.1.6 |
react-query | ^6.1.0 |
@welldone-software/why-did-you-render | ^7.0.1 |
@emotion/react & @emotion/styled | ^11.10.6 |
The specific configuration, operation and content will be different, and the "pit" will also be different. . .
1. Project launch: project initialization and configuration
2. React and Hook application: implement the project list
3. TS Application: JS God Assist - Strong Type
4. JWT, user authentication and asynchronous request
5. CSS is actually very simple - add styles with CSS-in-JS
1~3
4. Optimize the project list page with Grid and Flexbox layout
editsrc\authenticated-app.tsx
import styled from "@emotion/styled";
import {
useAuth } from "context/auth-context";
import {
ProjectList } from "screens/ProjectList";
/**
* grid 和 flex 各自的应用场景
* 1. 要考虑,是一维布局 还是 二维布局
* 一般来说,一维布局用flex,二维布局用grid
* 2. 是从内容出发还是从布局出发?
* 从内容出发:你先有一组内容(数量一般不固定),然后希望他们均匀的分布在容器中,由内容自己的大小决定占据的空间
* 从布局出发:先规划网格(数量一般比较固定),然后再把元素往里填充
* 从内容出发,用flex
* 从布局出发,用grid
*/
export const AuthenticatedApp = () => {
const {
logout } = useAuth();
return (
<Container>
<Header>
<HeaderLeft>
<h3>Logo</h3>
<h3>项目</h3>
<h3>用户</h3>
</HeaderLeft>
<HeaderRight>
<button onClick={
logout}>登出</button>
</HeaderRight>
</Header>
<Nav>Nav</Nav>
<Main>
<ProjectList />
</Main>
<Aside>Aside</Aside>
<Footer>Footer</Footer>
</Container>
);
};
const Container = styled.div`
display: grid;
grid-template-rows: 6rem 1fr 6rem; // 3行每行高度(fr 单位是一个自适应单位,表示剩余空间中所占比例)
grid-template-columns: 20rem 1fr 20rem;
grid-template-areas:
"header header header"
"nav main aside"
"footer footer footer";
/* grid-gap: 10rem; // 每部分之间的间隔 */
height: 100vh;
`;
// grid-area 用来给 grid 子元素起名字
const Header = styled.header`
grid-area: header;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
`;
const HeaderLeft = styled.div`
display: flex;
align-items: center;
`;
const HeaderRight = styled.div``;
const Main = styled.main`
grid-area: main;
/* height: calc(100vh - 6rem); */
`;
const Nav = styled.nav`
grid-area: nav;
`;
const Aside = styled.aside`
grid-area: aside;
`;
const Footer = styled.footer`
grid-area: footer;
`;
The respective application scenarios of grid and flex
1. To consider whether it is a one-dimensional layout or a two-dimensional layout
- Generally speaking, flex is used for one-dimensional layout, and grid is used for two-dimensional layout.
2. Do you start from the content or from the layout?
- Starting from the content: you first have a set of content (the number is generally not fixed), and then you want them to be evenly distributed in the container, and the space occupied is determined by the size of the content itself
- Starting from the layout: plan the grid first (the number is generally fixed), and then fill the elements in
- Starting from the content, use flex
- Starting from the layout, use grid
5. Use emotion to customize style components
Different
react
from functional components,emotion
we call them style components
New src\components\lib.tsx
(emotion custom style component library):
import styled from '@emotion/styled'
export const Row = styled.div<{
gap?: number | boolean,
butween?: boolean,
marginBottom?: number
}>`
display: flex;
align-items: center;
justify-content: ${
props => props.butween ? 'space-between' : undefined };
margin-bottom: ${
props => props.marginBottom + 'rem' };
> * {
/* 直接子元素强制控制样式 */
margin-top: 0 !important;
margin-bottom: 0 !important;
margin-right: ${
props => typeof props.gap === 'number' ? props.gap + 'rem' : props.gap ? '2rem' : undefined };
}
`
The code in the previous section is for learning grid
, and to achieve subsequent effects, clear useless code and use custom style components ( src\authenticated-app.tsx
):
import styled from "@emotion/styled";
import {
Row } from "components/lib";
import {
useAuth } from "context/auth-context";
import {
ProjectList } from "screens/ProjectList";
export const AuthenticatedApp = () => {
const {
logout } = useAuth();
return (
<Container>
<Header butween={
true }>
<HeaderLeft gap={
true }>
<h2>Logo</h2>
<h2>项目</h2>
<h2>用户</h2>
</HeaderLeft>
<HeaderRight>
<button onClick={
logout}>登出</button>
</HeaderRight>
</Header>
<Main>
<ProjectList />
</Main>
</Container>
);
};
const Container = styled.div`
display: grid;
grid-template-rows: 6rem 1fr;
height: 100vh;
`;
// grid-area 用来给 grid 子元素起名字
const Header = styled(Row)``;
const HeaderLeft = styled(Row)``;
const HeaderRight = styled.div``;
const Main = styled.main``;
6. Improve the item list page style
EDIT src\screens\ProjectList\components\SearchPanel.tsx
(using Form.Item
the attribute emotion
of css
):
// /** @jsx jsx */
// import { jsx } from '@emotion/react'
/** @jsxImportSource @emotion/react */
...
export const SearchPanel = ({
users, param, setParam }: SearchPanelProps) => {
return (
<Form css={
{
marginBottom: '2rem', '>*': '' }} layout="inline">
<Form.Item>
<Input placeholder='项目名' ... />
</Form.Item>
<Form.Item>
<Select>...</Select>
</Form.Item>
</Form>
);
};
When using the attribute, you need to pay attention. Since the automatic import of destroys the support of
emotion
its own runtime, the runtime of will be unused after being imported, and the attribute of cannot be used. Change to to and the deadline will be 2023.05.04. In the official document Still importing from : Emotion – The css Propcss
React 17
@emotion
emotion
jsx
emotion
css
/** @jsx jsx */
/** @jsxImportSource @emotion/react */
/** @jsx jsx */
EDIT src\screens\ProjectList\index.tsx
(adjusted with outer spacing):
...
export const ProjectList = () => {
...
return (
<Container>
<h1>项目列表</h1>
<SearchPanel users={
users} param={
param} setParam={
setParam} />
<List users={
users} list={
list} />
</Container>
);
};
const Container = styled.div`
padding: 3.2rem
`
Install the dayjs library:
npm i dayjs --force
As of 2020.9 moment library has stopped development
Edit src\screens\ProjectList\components\List.tsx
(new department and creation time fields in the table):
...
import dayjs from 'dayjs'
interface Project {
...
created: number;
}
...
export const List = ({
users, list }: ListProps) => {
return (
<Table
pagination={
false}
columns={
[
{
title: "名称",
...
},
{
title: "部门",
dataIndex: "organization"
},
{
title: "负责人",
...
},
{
title: "创建时间",
render: (text, project) => (
<span>
{
project.created ? dayjs(project.created).format('YYYY-MM-DD') : '无'}
</span>
),
},
]}
dataSource={
list}
></Table>
);
};
Put the preset svg file (software-logo.svg) insrc\assets
Recommended way to use ReactComponent as SVG
Edit src\authenticated-app.tsx
(add Logo, optimize logout, header
add bottom shadow) (some unmodified content omitted):
...
import {
ReactComponent as SoftwareLogo } from 'assets/software-logo.svg'
import {
Button, Dropdown } from "antd";
import type {
MenuProps } from 'antd';
export const AuthenticatedApp = () => {
const {
logout, user } = useAuth();
const items: MenuProps['items'] = [{
key: 1,
label: '登出',
onClick: logout
}]
return (
<Container>
<Header between={
true}>
<HeaderLeft gap={
true}>
<SoftwareLogo width='18rem' color='rgb(38,132,255)'/>
<h2>项目</h2>
<h2>用户</h2>
</HeaderLeft>
<HeaderRight>
<Dropdown menu={
{
items }}>
<Button type='link' onClick={
e => e.preventDefault()}>
Hi, {
user?.name }
</Button>
</Dropdown>
</HeaderRight>
</Header>
<Main>... </Main>
</Container>
);
};
const Container = styled.div`...`;
// grid-area 用来给 grid 子元素起名字
const Header = styled(Row)`
padding: 3.2rem;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.1);
z-index: 1;
`;
...
Dropdown
Theoverlay
attribute has beenmenu
replaced by the attribute, noteMenuProps
the introduction of
The results of this beautification:
7. Handling of legacy issues
src\utils\index.ts
Untie @ts-ignore
the "seal" error
...
export const isVoid = (val: unknown) => val === undefined || val === null || val === ''
export const cleanObject = (obj: {
[key: string]: unknown }) => {
const res = {
...obj };
Object.keys(res).forEach((key) => {
const val = res[key];
if (isVoid(val)) {
delete res[key];
}
});
return res;
};
export const useMount = (cbk: () => void) =>
useEffect(() => {
// TODO 依赖项里加上callback 会造成无限循环,这个和 useCallback 以及 useMemo 相关
cbk();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
...
- The object type covers a wide range (
function
,new RegExp('')
...), if you just want to use the form of key-value pairs, you can use the form shown above{ [key: string]: unknown }
- If
val = res[key]
the value of is a literal offalse
or , it will also be recognized, and then there will be , for example , etc.false
isFalsy
bug
checked
visible
Install another version jira-dev-tool
( api
with changes):
npm i jira-dev-tool # --force (可能需要强制安装)
If there is an error, you can
node_modules
empty and reinstall or follow the error prompt
Modify the project entry file src\index.tsx
(some unmodified content is omitted):
...
import {
loadServer, DevTools } from "jira-dev-tool";
...
loadServer(() => {
root.render(
// <React.StrictMode>
<AppProvider>
<DevTools/>
<App />
</AppProvider>
// </React.StrictMode>
);
});
...
Modifications src\context\index.tsx
(some unmodified content omitted):
...
import {
QueryClient, QueryClientProvider } from 'react-query'
export const AppProvider = ({
children }: {
children: ReactNode }) => {
return <QueryClientProvider client={
new QueryClient()}>
<AuthProvider>{
children}</AuthProvider>
</QueryClientProvider>;
};
Some reference notes are still in draft stage, so stay tuned. . .