[Practice] Five, CSS is actually very simple - add styles with CSS-in-JS (Part 2) - React17+React Hook+TS4 best practice, imitating Jira enterprise-level projects (7)


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

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-mCfBi9zv-1688310753663)(images/2023-04-28-15-43-59.png)]

5. Use emotion to customize style components

Different reactfrom functional components, emotionwe 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.Itemthe attribute emotionof 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 emotionits 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 PropcssReact 17@emotionemotionjsxemotioncss/** @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

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-jsxBHLkt-1688310753668)('./../images/software-logo.svg)]

Recommended way to use ReactComponent as SVG

Edit src\authenticated-app.tsx(add Logo, optimize logout, headeradd 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;
`;
...

DropdownThe overlayattribute has been menureplaced by the attribute, note MenuPropsthe introduction of

The results of this beautification:
insert image description here

7. Handling of legacy issues

src\utils\index.ts

Untie @ts-ignorethe "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 of falseor , it will also be recognized, and then there will be , for example , etc.falseisFalsybugcheckedvisible

Install another version jira-dev-tool( apiwith changes):

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

If there is an error, you can node_modulesempty 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. . .

Guess you like

Origin blog.csdn.net/qq_32682301/article/details/131506790