首页 前端知识 【实战】 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(下) —— React17 React Hook TS4 最佳实践,仿 Jira 企业级项目(七)

【实战】 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(下) —— React17 React Hook TS4 最佳实践,仿 Jira 企业级项目(七)

2024-04-29 12:04:57 前端知识 前端哥 843 341 我要收藏

文章目录

    • 一、项目起航:项目初始化与配置
    • 二、React 与 Hook 应用:实现项目列表
    • 三、TS 应用:JS神助攻 - 强类型
    • 四、JWT、用户认证与异步请求
    • 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式
      • 1~3
      • 4.用Grid和Flexbox布局优化项目列表页面
      • 5.使用 emotion 自定义样式组件
      • 6.完善项目列表页面样式
      • 7.遗留问题处理


学习内容来源:React + React Hook + TS 最佳实践-慕课网


相对原教程,我在学习开始时(2023.03)采用的是当前最新版本:

版本
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
prettier2.8.4
json-server0.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

具体配置、操作和内容会有差异,“坑”也会有所不同。。。


一、项目起航:项目初始化与配置

  • 【实战】 一、项目起航:项目初始化与配置 —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(一)

二、React 与 Hook 应用:实现项目列表

  • 【实战】 二、React 与 Hook 应用:实现项目列表 —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二)

三、TS 应用:JS神助攻 - 强类型

  • 【实战】三、 TS 应用:JS神助攻 - 强类型 —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(三)

四、JWT、用户认证与异步请求

  • 【实战】四、 JWT、用户认证与异步请求(上) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(四)

  • 【实战】四、 JWT、用户认证与异步请求(下) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(五)

五、CSS 其实很简单 - 用 CSS-in-JS 添加样式

1~3

  • 【实战】 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(上) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(六)

4.用Grid和Flexbox布局优化项目列表页面

编辑 src\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;
`;
复制

grid 和 flex 各自的应用场景

1.要考虑,是一维布局 还是 二维布局

  • 一般来说,一维布局用flex,二维布局用grid

2.是从内容出发还是从布局出发?

  • 从内容出发:你先有一组内容(数量一般不固定),然后希望他们均匀的分布在容器中,由内容自己的大小决定占据的空间
  • 从布局出发:先规划网格(数量一般比较固定),然后再把元素往里填充
  • 从内容出发,用flex
  • 从布局出发,用grid

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mCfBi9zv-1688310753663)(images/2023-04-28-15-43-59.png)]

  • CSS Grid: Holy Grail Layout | DigitalOcean

5.使用 emotion 自定义样式组件

区别于 react 的功能组件 emotion 组件我们称其为 样式组件

新建 src\components\lib.tsx(emotion 自定义样式组件库):

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 };
}
`
复制

上一节代码是为了学习 grid,为实现后续效果,清除无用代码并使用自定义样式组件(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.完善项目列表页面样式

编辑 src\screens\ProjectList\components\SearchPanel.tsx(使用Form.Itememotioncss 属性):

// /** @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>
);
};
复制

在使用 emotioncss 属性时 需要注意,由于 React 17 的自动导入破坏了 @emotion 自身运行时的支持,从而将导致 emotionjsx 运行时导入后未使用,也就无法使用 emotioncss 属性将 /** @jsx jsx */ 改为 /** @jsxImportSource @emotion/react */ 即可
截止2023.05.04,官方文档中依旧是 /** @jsx jsx */ 的导入方式:Emotion – The css Prop

编辑 src\screens\ProjectList\index.tsx(调整与外部间距):

...
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
`
复制

安装 dayjs 库:

npm i dayjs --force
复制

截止 2020.9 moment 库已停止开发

编辑 src\screens\ProjectList\components\List.tsx(表中新增部门和创建时间字段):

...
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>
);
};
复制

将预置 svg 文件(software-logo.svg) 放入src\assets

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jsxBHLkt-1688310753668)('./../images/software-logo.svg)]

使用方式推荐 ReactComponent as SVG

编辑 src\authenticated-app.tsx(添加 Logo、优化登出、header 添加底部阴影)(部分未修改内容省略):

...
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;
`;
...
复制

Dropdownoverlay 属性已被 menu 属性取代,注意 MenuProps 的引入

本次美化成果:
在这里插入图片描述

7.遗留问题处理

src\utils\index.ts

解开 @ts-ignore "封印"的报错

...
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
}, []);
...
复制
  • object 类型涵盖很广(functionnew RegExp('')…),若只是想用键值对的形式可以使用上面所示的形式 { [key: string]: unknown }
  • val = res[key] 的值是 false 或是 false 的字面量,isFalsy 也会识别,然后就会有 bug,比如 checkedvisible

安装另一个版本的 jira-dev-toolapi 有更改):

npm i jira-dev-tool # --force (可能需要强制安装)
复制

若有报错,可以将 node_modules 清空再装或是按照报错提示操作

修改项目入口文件 src\index.tsx(部分未修改内容省略):

...
import { loadServer, DevTools } from "jira-dev-tool";
...
loadServer(() => {
root.render(
// <React.StrictMode>
<AppProvider>
<DevTools/>
<App />
</AppProvider>
// </React.StrictMode>
);
});
...
复制

修改 src\context\index.tsx(部分未修改内容省略):

...
import { QueryClient, QueryClientProvider } from 'react-query'
export const AppProvider = ({ children }: { children: ReactNode }) => {
return <QueryClientProvider client={new QueryClient()}>
<AuthProvider>{children}</AuthProvider>
</QueryClientProvider>;
};
复制


部分引用笔记还在草稿阶段,敬请期待。。。

转载请注明出处或者链接地址:https://www.qianduange.cn//article/6399.html
标签
评论
发布的文章

JQuery中的load()、$

2024-05-10 08:05:15

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!