React+Antd+Vite+TypeScript Project Practical Tutorial (1)

reactThis tutorial belongs to the introductory tutorial of react. The course revolves around how to build a project framework. It will take you to quickly understand how to use these common technologies. The project source code will be attached at the end of the tutorial redux.redux-devtoolreact-router-domaxiox

1. Create a project

When building a project, we usually use the cli tool to build it, which is convenient, fast, and efficient. A complete scaffolding project can be generated with one line of command. Here we use viteto react+vite+typescriptwebsite address

  1. initialization command
npm init vite
//或者
yarn create vite
  1. project name
    insert image description here
  2. Choose a frame
    only supportsVue3
    insert image description here
  3. Choose a language
    insert image description here
  4. Install dependencies and start
cd react-vite-project
npm install
npm run dev

After the startup is complete, the console prints the following:
insert image description here
Open:http://localhost:5173
insert image description here

2. Directory structure

According to different functions, the project will generally be divided according to the following structure:

项目目录:
├─node_modules		//第三方依赖
├─public			//静态资源(不参与打包)
└─src
    ├─assets		//静态资源
    ├─components	//组件
    ├─config		//配置
    ├─http			//请求方法封装
    ├─layout		//页面布局
    ├─pages			//页面
    ├─routes		//路由
    ├─service		//请求
    ├─store			//状态管理
    └─util			//通用方法
    └─App.css
    └─App.tsx
    └─index.css
    └─main.tsx
    └─vite-env.d.ts
├─.eslinttrc.cjs
├─.gitignore		
├─index.html		//项目页面总入口
├─package.json	
├─tsconfig.json		//ts配置文件
├─tsconfig.node.json
├─vite.config.ts	//vite配置文件

3. Support scss

  1. download
npm i sass -D
  1. create assets/styles/index.scssfile
$red:red;
  1. import index.scssfile
//打开vite.config.ts,添加scss的预编译选项
export default defineConfig({
  ...
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "./assets/styles/index.scss";`
      },
    }
  }
})
  1. transfer
//1、修改App.css > App.scss
//2、添加scs语法设置字体颜色
h1{
  color:$red;
}
//3、修改引入的文件名
//import './App.css'
修改为:
import './App.scss'

The final effect is as follows:
insert image description here

Four, useState hook

1. useState case

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

2. The Class writing method of the same function as above

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

Official website description: Using the State Hook

5. Routing

1. Download

npm install react-router-dom -S
//npm i @types/react-router-dom	//默认是带代码提示的(非必须)

2. HashRouter routing

main.tsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { HashRouter as Router } from 'react-router-dom';
import './index.css'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <Router>
      <App />
    </Router>
  </React.StrictMode>,
)

App.tsx

import { useState } from 'react'
import { HashRouter, Route, Routes, Link, useNavigate } from "react-router-dom";
import Login from "./pages/login";
import Home from "./pages/home";
import User from "./pages/user";
import './App.scss'

function App() {
  const [count, setCount] = useState(0)
  const navigate=useNavigate();
  return (
      <div className="App">
      {/* 指定跳转的组件,to 用来配置路由地址 */}
      <Link to="/">首页</Link><br />
      <Link to="/user">用户</Link><br />
      <button onClick={() => navigate('/login')}> 登录 </button>
      {/* 路由出口:路由对应的组件会在这里进行渲染 */}
      <Routes>
        {/* 指定路由路径和组件的对应关系:path 代表路径,element 代表对应的组件,它们成对出现 */}
        <Route path='/' element={<Home />}></Route>
        <Route path='/user' element={<User />}></Route>
        <Route path='/login' element={<Login />}></Route>
      </Routes>
    </div>
  )
}

export default App

login.tsx + home.tsx + user.tsx

//login.tsx
function Login() {
    
    
    return (
        <div>login页面</div>
    );
}
export default Login;

//home.tsx
function Home() {
    
    
    return (
        <div>home页面</div>
    );
}
export default Home;

//user.tsx
function User() {
    
    
    return (
        <div>user页面</div>
    );
}
export default User;

At this time, open it http://localhost:5173/, as shown in the figure:
Please add a picture description

3. BrowserRouter routing

main.tsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import { BrowserRouter as Router } from 'react-router-dom';
import './index.css'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <Router>
      <App />
    </Router>
  </React.StrictMode>,
)

App.tsx

import { useState } from 'react'
import { Route, Routes, Link, useNavigate } from 'react-router-dom';

import Login from "./pages/login";
import Home from "./pages/home";
import User from "./pages/user";
import './App.scss'

function App() {
  const navigate = useNavigate();
  return (
    <div className="App">
      {/* 指定跳转的组件,to 用来配置路由地址 */}
      <Link to="/">首页</Link><br />
      <Link to="/user">用户</Link><br />
      <button onClick={() => navigate('/login')}> 登录 </button>
      {/* 路由出口:路由对应的组件会在这里进行渲染 */}
      <Routes>
        {/* 指定路由路径和组件的对应关系:path 代表路径,element 代表对应的组件,它们成对出现 */}
        <Route path='/' element={<Home />}></Route>
        <Route path='/user' element={<User />}></Route>
        <Route path='/login' element={<Login />}></Route>
      </Routes>
    </div>
  )
}

export default App

At this time, open http://localhost:5173/, as shown in the figure

Please add a picture description

4. Nested routing

App.tsx

import { useState } from 'react'
import { Route, Routes, Link, useNavigate } from 'react-router-dom';

import Login from "./pages/login";
import Home from "./pages/home";
import User from "./pages/user";
import './App.scss'

function App() {
  const navigate = useNavigate();
  return (
    <div className="App">
      {/* 指定跳转的组件,to 用来配置路由地址 */}
      <Link to="/home">首页</Link><br />
      <Link to="/home/user">用户</Link><br />
      <button onClick={() => navigate('/home/login')}> 登录 </button>
      {/* 路由出口:路由对应的组件会在这里进行渲染 */}
      <Routes>
        {/* 指定路由路径和组件的对应关系:path 代表路径,element 代表对应的组件,它们成对出现 */}
        <Route path='/home' element={<Home />}>
          <Route path='user' element={<User />}></Route>
          <Route path='login' element={<Login />}></Route>
        </Route>
      </Routes>
    </div>
  )
}

export default App

home.tsx

import { Outlet } from "react-router-dom";
function Home() {
    return (
        <div>
            <div>home页面</div>
            <Outlet />
        </div>
    );
}

export default Home;

The page is displayed as follows :
Please add a picture description

5. Default child routing

indexIn the child route under the nested route, if we want to set a default route, we only need to add attributes to the route .

<Routes>
    <Route path='/home' element={<Home />}>
        <Route path='user' element={<User />}></Route>
        <Route index element={<Login />}></Route>
    </Route>
</Routes>

6. Redirection

import { Navigate } from 'react-router-dom';

<Route path='/' element={<Navigate to="/layout" />}></Route>

When doing permission verification, we can use redirection. If you have logged in, you will enter the home page, and if you have not logged in, you will enter the login page.

7. useRoutes routing configuration

We write the above routes App.tsxmanually, which is not convenient. In the actual project, our routes will be uniformly configured in the configuration file. At this time, we can use it useRoutesto realize the routing configuration;

App.tsx

import GetRoutes from "@/routes/index";
function App() {
  return (
    <GetRoutes></GetRoutes>
  )
}
export default App

/routes/index.tsx

import { useRoutes, Navigate, RouteObject } from "react-router-dom";

import Layout from "@/layout/index";
import Login from "@/pages/login";
import Home from "@/pages/home";
import User from "@/pages/user";

export const router_item: Array<object> = [
    { path: "/", label: "首页", element: <Navigate to="/layout/home" /> },
    {
        path: "/layout",
        label: "控制台",
        element: <Layout />,
        children: [
            {
                path: "home",
                label: "首页",
                element: <Home />
            },
            {
                path: "login",
                label: "登录页",
                element: <Login />,
            },
            {
                path: "user",
                label: "用户页",
                element: <User />
            },
        ],
    },
];

function GetRoutes() {
    const routes: RouteObject[] = useRoutes(router_item);

    return routes;
}

export default GetRoutes;

The actual effect is as follows:
Please add a picture description

8. Routing lazy loading

Some pages are relatively large, we can use lazy loading to improve page loading performance and avoid page freezes; react official website provides a complete example of route lazy loading: react route lazy loading . Lazy loading is mainly achieved by lazyusing suspense组件.

lazyCode that lets you delay loading components until they are rendered for the first time. <Suspense> Allows you to display a temporary component (typically a loading state) until its children have finished loading.

Example demonstration:

import { lazy } from 'react';
const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));

<Suspense fallback={<Loading />}>
  <h2>Preview</h2>
  <MarkdownPreview />
 </Suspense>

MarkdownPreview.jsIt is the component that needs to be loaded, use to lazy()generate an LazyExoticComponentobject, and then wrap it in suspensethe component, fallback={<Loading />}which Loadingis a component that displays the page during loading.


Knowing the above usage, we can use lazy loading in the project like this.

  1. Encapsulation lazyLoad(), used to pass in the lazy()generated LazyExoticComponentobject and return a suspensecomponent
  2. Modify the import method of components in the route, change it tolazyLoad(lazy(() => import("@/pages/home")))

The specific code is as follows:

/routes/index.tsx (please check how the home and user components are introduced)

import { useRoutes, Navigate, RouteObject } from "react-router-dom";
import { lazy } from "react";
//页面
import Layout from "@/layout/index";
import Login from "@/pages/login";
import Error404 from "@/pages/error/404";

//公共
import lazyLoad from "./lazyLoad";

// 添加一个固定的延迟时间,以便你可以看到加载状态
function delayForDemo(promise: Promise<any>) {
    return new Promise(resolve => {
        setTimeout(resolve, 2000);
    }).then(() => promise);
}

export const router_item: Array<object> = [
    {
        path: "/",
        key: "/",
        label: "首页",
        hidden: true,
        element: <Navigate to="/login" />
    },
    {
        path: "/login",
        key: "login",
        label: "登录",
        hidden: true,
        element: <Login />,
        meta: {
            noAuth: true    //不需要检验
        }
    },
    {
        path: "/layout",
        key: "layout",
        label: "控制台",
        element: <Layout />,
        children: [
            {
                path: "home",
                key: "home",
                label: "首页",
                element: lazyLoad(lazy(() => delayForDemo(import("@/pages/home")))) //故意延迟2s,这里是延迟加载
            },
            {
                path: "user",
                key: "user",
                label: "用户",
                element: lazyLoad(lazy(() => import("@/pages/user"))),	//这里是延迟加载
                children: [
                    {
                        path: "home1",
                        key: "home1",
                        label: "首页1",
                        element: lazyLoad(lazy(() => import("@/pages/home"))),//这里是延迟加载
                    },
                    {
                        path: "user1",
                        key: "user1",
                        label: "用户1",
                        element: lazyLoad(lazy(() => import("@/pages/user"))),//这里是延迟加载
                    },
                ],
            },
        ],
    },
    {
        path: "/404",
        hidden: true,
        element: <Error404 />,
        meta: {
            noAuth: true    //不需要检验
        }
    },
    {
        path: "*",
        hidden: true,
        element: <Navigate to="/404" />
    },
];

function GetRoutes() {
    const routes: RouteObject[] = useRoutes(router_item);

    return routes;
}

export default GetRoutes;

/routes/lazyLoad.tsx

import { LazyExoticComponent, Suspense } from "react";
import Spinner from "@/components/spinner";
/**
 * 实现路由懒加载
 * @param Comp 懒加载组件
 * @returns 
 */
function lazyLoad(Comp: LazyExoticComponent<() => JSX.Element>) {
    return (
        <Suspense fallback={<Spinner />}>
            <Comp />
        </Suspense>
    );
}

export default lazyLoad;

/compoments/spinner.tsx

function Spinner() {
    return (
        <>
            loading...
        </>
    );
}

export default Spinner;

Since there is a 2s delay in home loading, let's look at the loading effect of the home page, and we can see the loading...loading status, as shown in the figure:
Please add a picture description

6. Layout

1. Framework

insert image description here

2. Code

/layout/index.tsxcode show as below:

import "./index.scss";
import { Outlet } from "react-router-dom";
import Aside from "./aside"

function Layout() {
    return (
        <section id="container">
            <aside>
                <Aside></Aside>
            </aside>
            <section>
                <header>header</header>
                <main>
                    <Outlet></Outlet>
                </main>
            </section>
        </section>
    );
}

export default Layout;

7. Menu

Since we have already set up the routing, when generating the menu, we can use the routing data to generate it directly, the structure is consistent, but we need to add key, label, hiddenattributes;

When producing menus, we need to consider multi-level menus, and automatically expand the last selected menu after refreshing;

1. Install antd

npm i antd --save

2. Code

The routing data is as follows:

/routes/index.tsx

import { useRoutes, Navigate, RouteObject } from "react-router-dom";

import Layout from "@/layout/index";
import Login from "@/pages/login";
import Home from "@/pages/home";
import User from "@/pages/user";

export const router_item: Array<object> = [
    {
        path: "/",
        key: "/",
        label: "首页",
        hidden: true,
        element: <Navigate to="/layout/home" />
    },
    {
        path: "/layout",
        key: "layout",
        label: "控制台",
        element: <Layout />,
        children: [
            {
                path: "login",
                key: "login",
                label: "登录",
                element: <Login />,
            },
            {
                path: "home",
                key: "home",
                label: "首页",
                element: <Home />
            },
            {
                path: "user",
                key: "user",
                label: "用户",
                element: <User />,
                children: [
                    {
                        path: "home1",
                        key: "home1",
                        label: "首页1",
                        element: <Home />
                    },
                    {
                        path: "user1",
                        key: "user1",
                        label: "用户1",
                        element: <User />
                    },
                ],
            },
        ],
    },
];

function GetRoutes() {
    const routes: RouteObject[] = useRoutes(router_item);

    return routes;
}

export default GetRoutes;

aside.tsx

// react hook
import { useState } from "react";
import { router_item } from "@/routes/index";
import { Menu } from "antd";
import { useNavigate } from "react-router-dom";

function aside() {
    const navigate = useNavigate();

    //菜单
    const [routes] = useState<any[]>(router_item);

    //打开和选中
    const defaultOpenKeys = (localStorage.getItem("openKeys") || "")?.split(",");
    const defaultSelectKeys = localStorage.getItem("selectKeys") || "";
    const [selectKeys, setSelectKeys] = useState<string[]>([defaultSelectKeys]);
    const [openKeys, setOpenKeys] = useState<string[]>(defaultOpenKeys);
    //点击菜单
    const menuHandler = (e: any) => {
        let path = "/" + e.keyPath.reverse().join("/");
        path = path.replace("//", "/");
        navigate(path);

        // 缓存打开和选中的keys
        const selectKeys = e.key;
        e.keyPath.pop();
        const openKeys = e.keyPath.join(",");
        setSelectKeys(selectKeys);
        setOpenKeys(openKeys);
        localStorage.setItem("selectKeys", selectKeys);
        localStorage.setItem("openKeys", openKeys);
    }

    return (
        <>
            <Menu
                mode="inline"
                theme="dark"
                defaultOpenKeys={openKeys}
                defaultSelectedKeys={selectKeys}
                items={routes}
                onClick={menuHandler}></Menu>
        </>
    );
}

export default aside;

3. Menu effect

Please add a picture description

8. Login page

This is the simplest login page, the interaction is very simple, enter the user name and password, pass the verification, click login, and jump to the internal console page if the login is successful, or give an error prompt if the login is unsuccessful.

1. Install @ant-design/icons

npm i @ant-design/icons --save

2. Code

/pages/login/index.tsx

import "./index.scss";
import reactIcon from "@/assets/react.svg";

import { Button, Checkbox, Form, Input, message } from 'antd';
import { UserOutlined, LockOutlined } from "@ant-design/icons";
import { useNavigate } from "react-router-dom";

const styles = {
    login: {
        background: `linear-gradient(blue, pink)`,
        width: "100vw",
        height: "100vh",
    }
};
interface loginData {
    username: string;
    password: string;
    remember: string;
}

function Login() {
    const navigate = useNavigate();
    const [messageApi, contextHolder] = message.useMessage();

    const onFinish = (values: loginData) => {
        messageApi.open({
            type: 'loading',
            content: '正在登录..',
            duration: 0,
        });

        setTimeout(() => {
            messageApi.destroy();
            //默认请求延时
            if (values.username == "admin" && values.password == "123") {
                messageApi.open({
                    type: 'success',
                    content: '登录成功!',
                    onClose() {
                        navigate("/layout/home");
                    }
                });
                return;
            } else {
                messageApi.open({
                    type: 'error',
                    content: '登录失败!',
                });
            }
        }, 500);
    };

    const onFinishFailed = (errorInfo: any) => {
        // console.log('Failed:', errorInfo);
    };
    return (
        <div className="login_container" style={styles.login}>
            {contextHolder}
            <div className="title_big">Ant Design后台管理系统 <img src={reactIcon} style={
   
   { marginLeft: "5px" }} /></div>
            <div className="login_panel">
                <Form
                    name="basic"
                    labelCol={
   
   { span: 0 }}
                    wrapperCol={
   
   { span: 24 }}
                    style={
   
   { maxWidth: 600 }}
                    initialValues={
   
   { remember: true }}
                    onFinish={onFinish}
                    onFinishFailed={onFinishFailed}
                    autoComplete="off"
                >
                    <Form.Item
                        name="username"
                        rules={[
                            { required: true, message: '请输入用户名!' },
                            ({ getFieldValue }) => ({
                                validator(_, value) {
                                    if (value === "admin") {
                                        return Promise.resolve();
                                    }
                                    return Promise.reject(new Error('用户名不存在!'));
                                },
                            }),
                        ]}
                    >
                        <Input prefix={<UserOutlined />} placeholder="用户名" autoComplete="off" />
                    </Form.Item>

                    <Form.Item
                        name="password"
                        rules={[{ required: true, message: '请输入密码!' }]}
                    >
                        <Input.Password prefix={<LockOutlined />} placeholder="密码" />
                    </Form.Item>

                    <Form.Item wrapperCol={
   
   { span: 24 }} name="remember" valuePropName="checked">
                        <Checkbox>记住密码</Checkbox>
                        {/* <Button type="link" htmlType="button" style={
   
   { float: "right" }}>
                            忘记密码?
                        </Button> */}
                    </Form.Item>

                    <Form.Item wrapperCol={
   
   { span: 24 }}>
                        <Button type="primary" htmlType="submit" block>
                            登录
                        </Button>
                    </Form.Item>
                </Form>
            </div>
        </div>
    );
}

export default Login;

pages/login/index.scss

.login_container {
    
    
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;

    .title_big {
    
    
        width: 400px;
        height: 50px;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 2rem;
        // letter-spacing: 0.1rem;
        margin-bottom: 1rem;
        background: #fff;
        border-radius: 5px;
        position: relative;
        color: #0052b6;
    }

    .login_panel {
    
    
        width: 400px;
        background-color: white;
        padding: 15px;
        box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
        border-radius: 5px;

        .title {
    
    
            height: 40px;
            line-height: 40px;
            font-size: 1.5rem;
            border-bottom: 1px solid #eee;
            margin-bottom: 10px;
        }
    }
}

ANTDThe login page uses the Form form , form custom validation , message prompts , and navigate jumps .

Successful login effect:
Please add a picture description

Login failure effect:
Please add a picture description

9. Permission verification

The actual project needs to be verified before entering a certain page: token verification, permission verification, login status verification, etc.

1. Token verification

When entering login页面a page other than excluding, we need to intercept the page and perform token verification to determine whether the token exists, and enter if it exists; if it does not exist, jump to the login page to log in;

/components/AutoRouter.tsx

//这个组件用于拦截判断token是否存在。
import { useLocation, Navigate } from "react-router-dom";
function AutoRouter(props: { children: JSX.Element }) {
    const { pathname } = useLocation();
    //校验
    if (pathname.startsWith("/login")) {
        return props.children;
    }
    const token = localStorage.getItem("token");
    if (token) {
        // 1、存在token,则进入主页
        return props.children;
    } else {
        // 2、如果不存在token,则进入登录页
        return <Navigate to="/login" />
    }
}

export default AutoRouter;

App.tsx

//AutoRouter作为父组件,包裹所有子组件
import GetRoutes from "@/routes/index";
import AutoRouter from "@/components/autoRouter";

function App() {
  return (
    <AutoRouter>
      <GetRoutes></GetRoutes>
    </AutoRouter>
  )
}

export default App

/pages/login/index.tsx

//修改onFinish回调方法,加入token设置逻辑
const onFinish = (values: loginData) => {
        ...
        setTimeout(() => {
            //默认请求延时
            if (values.username == "admin" && values.password == "123") {
                localStorage.setItem("token","djalkdjadjlasj3123123");    //----登录成功后,设置token
               ...
                return;
            } else {
                ...
            }
        }, 500);
    };

Demonstration of token loss effect:
Please add a picture description

2. Routing authority configuration

Above, when we enter loginthe page, we don’t do permission verification for the path autoRoute页面in , but a large number of pages in normal projects need to be exempted from verification. If not, we have to write in this way, which is not very convenient to use. loginWhether to check can be directly written in the routing list. Then when the page jumps, intercept the routing configuration corresponding to the target page to check whether the statement needs to be verified.

The previous code in autoRoute.tsx about the verification-free login page:

function AutoRouter(props: { children: JSX.Element }) {
    const { pathname } = useLocation();
    //校验
    if (pathname.startsWith("/login")) {
        return props.children;//-----手动适配,不是很合理
    }
    ...
}

We can change it to the following form:

//login路由配置noAuth
    {
        path: "/login",
        key: "login",
        label: "登录",
        hidden: true,
        element: <Login />,
        meta: {
            noAuth: true    //不需要检验
        }
    },
    
//autoRoute.tsx中的拦截逻辑
function AutoRouter(props: { children: JSX.Element }) {
    const { pathname } = useLocation();
    const token = localStorage.getItem("token");
    //1、获取当前路径对应的路由配置
    const route = matchRoute(pathname, router_item);
    //2、如果noAuth为true,则直接跳过校验
    if (route && route.meta && route.meta.noAuth) {
        // 路由配置noAuth,则不需要校验
        return props.children;
    }
    if (token) {
        // 1、存在token,则进入主页
        return props.children;
    } else {
        // 2、如果不存在token,则进入登录页
        return <Navigate to="/login" />
    }
}

/util/util.ts

export interface MetaProp {
    title: string;
    key: string;
    noAuth: boolean;
}
export interface RouteObject {
    children?: RouteObject[];
    element?: React.ReactNode;
    path?: string;
    meta?: Partial<MetaProp>
}

/**
 * 获取路径对应的路由配置,没有则返回null
 * @param path 路由完整路径
 * @param routes 路由数组
 * @returns 路由配置项
 */
export function matchRoute(path: string, routes: RouteObject[] = []): any {
    const pathArr = path.split("/");
    pathArr.shift();
    const curPath = pathArr.shift();
    let result: any = null;
    for (let i = 0; i < routes.length; i++) {
        const item = routes[i];
        if ([curPath, `/${curPath}`].includes(item.path)) {
            if (!pathArr.length) {
                return item;
            }
            if (item.children) {
                const res = matchRoute(`/${pathArr.join("/")}`, item.children);
                if (res) {
                    return res;
                }
            }
        }
    }
    return result;
}

In this way, if we want to make verification-free settings for a certain page, we can directly /routes/index.tsxadd it to the corresponding routing configuration meta.noAuth:true, for example: 404 pages, forgotten passwords, and login pages can all be added.

10. Redux state management

1、redux

Similar to Vuethat in VuexReact, it can be used Reduxfor state management.
Please add a picture description
Below we try to create a reduxstate instance, and then get or update the state value on the page, and the value on the page will change accordingly.

create/redux/index.ts

import { legacy_createStore as createStote } from "redux";

//定义数据
const initState = {
    count: 0,
    name: "IT飞牛"
};

// 关联action
function countReducer(state = initState, action: any) {
    switch (action.type) {
        case "ADD_COUNT":
            return { ...state, count: state.count + action.number };
        case "UPDATE_NAME":
            return { ...state, name: action.name };
        default:
            return state;
    }
}
const store = createStote(countReducer);

export default store;

Modify /pages/home.tsxthe code , click the button to modify the name value

import { Outlet } from "react-router-dom";
import store from "@/redux";
function Home() {

    const updateCount = () => {
        store.dispatch({ type: "UPDATE_NAME", name: "IT飞牛,前端行业的一个小学生!" });
    }
    return (
        <>
            <button onClick={updateCount}>修改name</button>
            <Outlet />
        </>
    );
}

export default Home;

Modify /layout/index.tsxthe code , monitor the changes of the store, and modify the name synchronously, and display it on the page

import "./index.scss";
import { Outlet } from "react-router-dom";
import Aside from "./aside"
import store from "@/redux";
import { useState } from "react";

function Layout() {
    const [name, setName] = useState<string>(store.getState().name);    //获取store.name作为默认值
    store.subscribe(() => {
        const store_data = store.getState();
        setName(store_data.name);   //更新store.name值
    });
    return (
        <section id="container">
            <aside>
                <Aside></Aside>
            </aside>
            <section>
                <header>header-{name}</header>  {/* store.name显示到页面 */}

                <main>
                    <Outlet></Outlet>
                </main>
            </section>
        </section>
    );
}

export default Layout;

final effect:

Please add a picture description

2. Action splitting

There are often a lot of state data in the project, and each module may have a batch of states. At this time, we need to split and manage the state. Here we can use it to achieve combineReducers.

The role of combineReducers:

Converts an object whose values ​​are different reducer functions into a single reducer function. It will call each of the child reducers and collect their results into a state object whose keys correspond to the keys of the reducer function passed.

Specific steps:

  1. Prepare countReducer, nameReducertwo reducers
  2. Combine combineReducersthe above two reducers intomixReducer
  3. mixReducerCreate an storeinstance using
  4. renew:store.dispatch()
  5. Value:store.getState().nameReducer[state值]

The specific code is as follows:

/redux/action.ts

//定义数据
const initState1 = {
  count: 0,
};

export function countReducer(state = initState1, action: any) {
  switch (action.type) {
    case "ADD_COUNT":
      return { ...state, count: state.count + action.number };
    default:
      return state;
  }
}

const initState2 = {
  name: "IT飞牛",
};

export function nameReducer(state = initState2, action: any) {
  switch (action.type) {
    case "UPDATE_NAME":
      return { ...state, name: action.name };
    default:
      return state;
  }
}

/redux/index.ts

import {
  legacy_createStore as createStote,
  combineReducers,
} from "redux";
import { countReducer, nameReducer } from "./action";

const reducer = combineReducers({ countReducer, nameReducer });//合并reducer

const store = createStote(reducer);

export default store;

/pages/home.tsxThe code remains unchanged, the main code is as follows:

    //更新state值
    store.dispatch({
      type: "UPDATE_NAME",
      name: "IT飞牛,前端行业的一个小学生!",
    });

/page/layout.tsxMedium value code fine-tuning:

//老代码
const [name, setName] = useState<string>(store.getState().name);
store.subscribe(() => {
    const store_data = store.getState();
    setName(store_data.name);
});

//新代码
const [name, setName] = useState<string>(store.getState().nameReducer.name); //获取store.name作为默认值
store.subscribe(() => {
    const store_data = store.getState();
    setName(store_data.nameReducer.name); //更新store.name值
});

At this time, storethe structure of the data in has reducerbeen divided according to different modules, as follows:
Please add a picture description

3. redux-devtoolDebugging tools

redux-devtoolreduxIt is a chrome browser plug-in. After installation, you can view the status changes directly in the browser .

  1. install plugin

    Enter the Google App Store , search redux-devtooland install

Please add a picture description

  1. Project installation third-party dependencies
npm i redux-thunk redux-devtools-extension --save-dev
  1. Adjust /redux/indx.tsxthe code and use middleware createStotewhen creating a storethunk

    import {
      legacy_createStore as createStote,
      applyMiddleware,
      combineReducers,
    } from "redux";
    import thunk from "redux-thunk";
    import { composeWithDevTools } from "redux-devtools-extension";
    import { countReducer, nameReducer } from "./action";
    
    const reducer = combineReducers({ countReducer, nameReducer });
    
    const store = createStote(reducer, composeWithDevTools(applyMiddleware(thunk)));
    
    export default store;
    

final effect:
Please add a picture description

11. Axios package

1. Installation

npm i axios --save

2. Package axios

Create /util/request.ts, the code is as follows:

import axios, { AxiosInstance } from "axios";
import { HttpError } from "./HttpError";

const isDev = process.env.NODE_ENV === "development";
const baseUrl = isDev ? "/api" : "/";
export const request: AxiosInstance = axios.create({
  baseURL: baseUrl,
  timeout: 30000,
  headers: {
    "Content-Type": "application/json;charset=utf-8",
  },
});

request.interceptors.request.use(
  (req) => {
    req.headers.authorization =
      "Bearer " + localStorage.getItem("ACCESS_TOKEN");
    return req;
  },
  (err) => {
    // err.message
    throw err;
  }
);

request.interceptors.response.use(
  (res) => {
    const { code } = res.data;
    if (code === 401) {
      // window.location.replace("/");
    }
    if (code != "200") {
      // Message.error(res.data.message)
      throw new HttpError(
        res.data?.message || "网络错误!",
        Number(res.data.status)
      );
    }
    return res.data;
  },
  (err) => {
    const { response } = err;
    const { code } = response.data;
    if (code === 401) {
      // window.location.replace("/");
    }
    // err.message
    throw err;
  }
);

export const http = {
  post<T>(url: string, data?: any, config?: any) {
    return request.post(url, data, config) as Promise<T>; //使用范型,代码提示更简便
  },
  get<T>(url: string, config?: any) {
    return request.get(url, config) as Promise<T>; //使用范型,代码提示更简便
  },
};

class HttpError extends Error {
    code: number;
    constructor(message: string, code: number) {
        super(message);
        this.code = code;
    }
}

3. Define the request

Create api/homeApi.ts, the code is as follows:

import { request, http } from "@/util/request";

//写法1
export function getInfo1() {
  return http.post<InfoRes>("/api/common/getInfo", {
    url: "/api/common/getInfo",
    method: "GET",
  });
}

//写法2
export function getInfo2(): Promise<InfoRes> {
  return request.request({
    url: "/api/common/getInfo",
    method: "GET",
  });
}

interface InfoRes {
  token: string;
}

4. Initiate a request

import { getInfo1, getInfo2 } from "@/api/homeApi";

const res1 = await getInfo1();
const res2 = await getInfo2();

5. Supplementary description of Api interceptor

In an actual project, the request initiated by the front end will fail. There are two reasons for the failure: HTTP status code exception and business exception. Then we can do preliminary unified request.tsprocessing of these two types of failures in .

For example, for abnormal http status codes , common 4xxerrors 5xx, we will intercept them uniformly, and directly give similar **server/network errors, please contact the administrator! ** Such a message error message prompt.

If the HTTP status code is normal but the business status code codeis abnormal, we can also perform unified interception and pre-process business exceptions according to requirements.

12. Cross-domain configuration

1. Cross-domain solutions

Common cross-domain solutions include local proxy, CORS, server reverse proxy, etc.;

2. Configure vite local proxy

Open vite.config.tsthe file:

  server: {
    // 是否自动打开浏览器
    open: true,
    // 服务器主机名,如果允许外部访问,可设置为"0.0.0.0"
    host: '0.0.0.0',
    // 服务器端口号
    port: 3000,
    // 代理
    proxy: {
      '/v1/apigateway': {
        target: `http://12.12.12.12/`,
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/v1_redirect\/apigateway/, '')
      },
    }
  }

The principle of the local proxy is similar to the reverse proxy of the server. It is to use Express to start an http service locally, and then all local requests will be forwarded to the actual api server through the local Express service, so that the same origin of the browser can be bypassed. Strategy.

As configured above, we initiate a request on the page http://localhost/v1/apigateway/getInfo, and after being processed by the local proxy, the address actually forwarded to the api server is http://12.12.12.12/v1_redirect/apigateway/getInfo.

For more cross-domain configurations of vite, please check: server.proxy

common problem

1. Cannot find module "react-router-dom"

After the installation is complete react-router-dom, when App.tsximporting react-router-dom, the following error is prompted:

找不到模块“react-router-dom”。你的意思是要将 "moduleResolution" 选项设置为 "node",还是要将别名添加到 "paths" 选项中?ts(2792)

In fact react-router, there is a self-contained declaration file, as shown in the figure:
insert image description here

Solution :

Modify tsconfig.json, will be targetmodified to es5:

{
  "compilerOptions": {
  	"target": "es5",
  }
}

2. UseNavigate reports an error

When using programmatic navigation, the console reports the following error:

caught Error: useNavigate() may be used only in the context of a <Router> component.

In fact, it is necessary useNavigateto ensure that the outer layer of the dom node where the event starts is Routerwrapped by the node when using it.

Modify main.tsx as follows :

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import {
    
    BrowserRouter as Router} from 'react-router-dom';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
     <Router>//------添加
       <App />
     </Router>
  </React.StrictMode>
);

3. Delete invalid dom nodes

Sometimes the dom hierarchy is too complicated, and we need to clear an unnecessary dom node, then normally just delete the divlabel directly;

Then jsxthe syntax can also be implemented using empty tags <></>, and the div node will not be rendered.

Project source code

react-vite-project

Guess you like

Origin blog.csdn.net/bobo789456123/article/details/130591757