vite创建react项目

456 阅读13分钟

vite创建react项目

搭建项目

create-vite创建

1、默认创建react18

yarn create vite [project-name] --template react-ts

2、第二种创建项目

npm init vite@latest

选择react typescript

配置resolve.alias

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve:{
    alias:{
      '@': path.resolve(__dirname, './src')
    }
  }
})

配置tsconfig.json

// tsconfig.json
{
    "compilerOptions":{
        "paths": {
            "@/*": ["./src/*"]
        },
        "types":["vite/client"]
    },
    "include": ["src/**/*.ts", "src/**/*.tsx"],
    "exclude": ["node_modules"]
}

添加scss配置

添加sass依赖

yarn add sass -D

引入index.scss文件

// 打开vite.config.ts,添加scss的预编译选项
export default defineConfig({
    css:{
        preprocessorOptions:{
            scss: {
                additionalData: '@import "./asset/styles/index.scss;"'
            }
        }
    }
})

useState钩子

1、useState案例

import React, {useState} from 'react';

function example(){
    const [count, setCount] = useState(0)
    
    return (
    <div>
       <p>{count}</p>
     	<button onClick={() => setCount(count+1)}>click</button>       
     </div>
    )
}

2、class案例

class example extends React.Component {
    constructor(props){
        super(props)
        this.state = {
            count:0
        }
    }
    
    render(){
        return (
        	<div>
           	<p>{this.state.count}</p> 
            <button onClick={() => this.setState({count: this.state.count+1})}>click</button>
           </div>
        )
    }
}

legacy.reactjs.org/docs/hooks-…

路由

安装路由

yarn add react-router-dom

HashRouter路由

// 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>
            <Link to="/user">用户</Link>
            <button onClick={() => navigate('/login')}>登录</button>
            {/* 路由出口: 路由对应的组件会在这里渲染 */}
            <Routes>
            {/* 指定路由路径和组件的对应关系,path代表路径,element代表对应的组件,成对出现 */}
                <Route path="/" element={<Home />}></Route>
                <Route path="/user" element={<User />}></Route>
                <Route path="/login" element={<Login></Login>}></Route>
            </Routes>
        </div>
    )
}

export default App
// 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;

BrowserRouter路由

// 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></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>
            <Link to="/user">用户</Link>
            <button onClick={() => navigate('/login')}>登录</button>
            {/* 路由出口: 路由对应的组件会在这里渲染 */}
            <Routes>
            {/* 指定路由路径和组件的对应关系,path代表路径,element代表对应的组件,成对出现 */}
                <Route path="/" element={<Home />}></Route>
                <Route path="/user" element={<User />}></Route>
                <Route path="/login" element={<Login></Login>}></Route>
            </Routes>
        </div>
    )
}

export default App

嵌套路由

// App.tsx

 <Routes>
            {/* 指定路由路径和组件的对应关系,path代表路径,element代表对应的组件,成对出现 */}
                <Route path="/" element={<Home />}>
    
    
                <Route path="user" element={<User />}></Route>
                <Route path="login" element={<Login></Login>}>
                    </Route>
    </Route>
            </Routes>

// home.tsx
import {Outlet} from 'react-router-dom'
function Home(){
    return (
    <div>
        <div>home页面</div>
            <Outlet />
        </div>
    )
}
export default Home

默认子级路由

在嵌套路由下的子级路由,如果要设置一个默认路由,那么只要在路由上添加 index 属性

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

重定向路由

import {Navigate} from 'react-router-dom'
<Route path="/" element={<Navigate to="/layout"></Navigate>}></Route>

在做权限验证的时候可以使用重定向,

yarn add react-router-dom redux react-redux axios 

blog.csdn.net/bobo7894561…

zhuanlan.zhihu.com/p/518339176…

image-20230625161420822转存失败,建议直接上传图片文件

1、搭建项目

1.1 create-vite 创建

 yarn create vite react-vite --template react-ts

默认创建react18

image-20230625151335521转存失败,建议直接上传图片文件

执行yarn

1.2 配置 reslove.alias

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias:{
      '@': path.resolve(__dirname, './src')
    }
  }
})

配置tsconfig.json

// tsconfig.json
{
    "compilerOptions":{
        "baseUrl": ".",
        "paths": {
          "@/*": ["src/*"]
        }
    }
}

1.3 Sass LessCss

yarn add sass less

1.4 css module

文件名设置 *.module.less, *.module.scss等

image-20230625153602063转存失败,建议直接上传图片文件

2、集成 react-router

yarn add react-router-dom

使用BrowserRouter组件

2.1 useRoutes 配置路由

react router dom V6 支持配置路由 useRoutes (hook) 实现

// 官方示例
import { useRoutes } from "react-router-dom";

function Router() {
  let element = useRoutes([
    {
      path: "/",
      element: <Dashboard />,
      children: [
        {
          path: "messages",
          element: <DashboardMessages />,
        },
        { path: "tasks", element: <DashboardTasks /> },
      ],
    },
    { path: "team", element: <AboutPage /> },
  ]);

  return element;
}

2.2 Redirect 替代方案 Navigate

react router dom v6 中已经抛弃了 Redirect。但是可以使用 Navigate(组件) 或 useNavigate(hook)作为替代方案,简单的实现一个 Redirect。

import { useEffect } from 'react';
import { useNavigate, Navigate } from 'react-router-dom';

export function Redirect({ to, replace, state }: { to: string, replace: boolean, state: object }): null {
    const navigate = useNavigate();

    useEffect(() => {
        navigate(to, { replace, state });
    });

    return null;
}

怎么使用,判断一下如果是重定向

<Route path="/" element={<Redirect to="/home" />} />

2.3 路由传参

react router v6 获取传参需要用两个 hook,分别是 useParams(params)和 useSearchParams(search)

(1)useParams

params 传参

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

{/* 路由定义 /article/:id */}
<NavLink to={`/article/1`}>文章1</NavLink>

接收参数

import { useParams } from 'react-router-dom'

/* params */
const params = useParams();
const { id } = params;

(2)useSearchParams

search 传参

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

<NavLink to={`/article?id=1`}>文章1</NavLink>

接收参数

import { useSearchParams } from 'react-router-dom'

/* search */
let [searchParams, setSearchParams] = useSearchParams();
const { id } = searchParams;

(3)useLocation

state 传参

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

<NavLink to="/article" state={{ id: 1 }}>文章1</NavLink>

接收参数

import { useLocation } from 'react-router-dom'

let location = useLocation();
const { id } = location.state;

2.4 编程式路由跳转 useNavigate

useHistory 已废弃,而是使用 useNavigate

import { FC, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button } from 'antd';

interface IndexProps{}

const Index: FC<IndexProps> = () => {
    const navigate = useNavigate();
    // 返回
    const handleBack = () => navigate(-1);
    // 前进
    const handleForward = () => navigate(1);
    // 刷新
    const handleRefresh = () => navigate(0);
    //

    return <>
        <Button type="primary" onClick={handleBack}>返回</Button>
        <Button type="primary" onClick={handleForward}>前进</Button>
        <Button type="primary" onClick={handleRefresh}>刷新</Button>
        {/* 跳转路由 */}
        <Button type="primary" onClick={() => navigate('/article/1', { replace: true })}>params</Button>
        <Button type="primary" onClick={() => navigate('/article?id=1', { replace: true })}>search</Button>
        <Button type="primary" onClick={() => navigate('/article', { replace: true, state: { id: 1 } })}>state</Button>
    </>
}

export default Index;

简单测试一下

img转存失败,建议直接上传图片文件

// Acticle.tsx
import { useSearchParams, useParams, useLocation } from "react-router-dom";

export default function Index(){
    // params
    const pId = useParams()?.id;

    // search
    const [search] = useSearchParams();
    const sId = search?.get('id');

    // state
    const location = useLocation();
    const stateId = location?.state?.id;

    return <>
        <div>params: {pId}</div>
        <div>search: {sId}</div>
        <div>locationState: {stateId}</div>
    </>
}

编程式跳转路由

2.5 Outlet

子路由组件不再使用 props.default 传递;而是通过 Outlet 组件显示(中文意思是“插座”);其实和 props.default 的作用是一样的。

// layouts/Base.tsx
import { Outlet, useNavigate } from "react-router-dom";
import { Layout, Menu } from 'antd';

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

    return <Layout.Header style={{ position: 'fixed', zIndex: 1, width: '100%' }}>
        <div className="logo" />
        <Menu
            mode="horizontal"
            items={
                [
                    {
                        key: "home",
                        label: "首页",
                        onClick: () => navigate('/home')
                    },
                    {
                        key: "about",
                        label: "关于",
                        onClick: () => navigate('/about')
                    },
                ]
            } />
    </Layout.Header>
}

function GlobalContent() {
    return <Layout.Content className="site-layout" style={{ padding: '0 50px', marginTop: 64 }}>
        <div className="site-layout-background" style={{ padding: 24, minHeight: 380 }}>
            {/* outlet */}
            <Outlet />
        </div>
    </Layout.Content>;
}

function GlobalFooter() {
    return <Layout.Footer style={{ textAlign: 'center' }}>用 vite 创建 react18 项目 @带只拖鞋去流浪</Layout.Footer>

}

export default function Index() {

    return <Layout>
        <GlobalHeader />
        <GlobalContent />
        <GlobalFooter />
    </Layout>
}

Outlet

2.6 useOutletContext 子路由状态共享

// 父路由组件
function Parent() {
  const [count, setCount] = React.useState(0); 
  return <Outlet context={[count, setCount]} />;
}

// 子路由组件
import { useOutletContext } from "react-router-dom";

function Child() {
  const [count, setCount] = useOutletContext();
  const increment = () => setCount((c) => c + 1);
  return <button onClick={increment}>{count}</button>;
}

2.7 路由拦截

思路:在路由组件外层套一个 BeforeEach 组件进行路由拦截。代码展示在 2.8

2.8 路由组件代码

img转存失败,建议直接上传图片文件

/*  router/index.tsx  */

import React from 'react';
import { useRoutes, useNavigate, Navigate } from 'react-router-dom';
import routes, { routeType } from './routes';
import { Spin } from 'antd';
import _ from 'lodash';

export default function Routes() {
    const element = useRoutes(renderRoutes(routes));
    return element;
}

function renderRoutes(routes: Array<routeType>) {
    return _.map(routes, (item: routeType) => {

        interface resType extends routeType{
            element?: any
        }

        let res: resType = { ...item };
        if (!item?.path) return;

        // component
        if (item?.component) {
            const Component = React.lazy(item.component);
            res.element = (<React.Suspense fallback={<Spin size="large" />}>
                <BeforeEach route={item}>
                    <Component />
                </BeforeEach>
            </React.Suspense>);
        }

        // children
        if (item?.children) {
            res.children = renderRoutes(item.children);
        }

        // 重定向
        if (item?.redirect) {
            res.element = (
                <Navigate to={item.redirect} replace />
            )
        }

        return res;
    })
}

function BeforeEach(props: { route: routeType, children: any }) {
    if (props?.route?.meta?.title) {
        document.title = props.route.meta.title;
    }

    if(props?.route?.meta?.needLogin){
        // 看是否登录
        // const navigate = useNavigate();
        // navigate('/login');
    }

    return <div>
        {props.children}
    </div>
}

/*  router/routes.ts  */

export interface routeType {
    path: string
    component?: any
    children?: Array<routeType>
    meta?: {
        title?: string
        needLogin?: boolean
    }
    redirect?: string
}

const routes: Array<routeType> = [
    {
        path: '/',
        component: () => import('@/layouts/Base'),
        children: [
            {
                path: '/',
                redirect: '/home',
            },
            {
                path: '/home',
                component: () => import('@/pages/Home'),
                meta: {
                    title: "首页",
                }
            },
            {
                path: '/about',
                component: () => import('@/pages/About'),
                meta: {
                    title: "关于",
                }
            },
        ]
    }
]

export default routes;

/*  main.ts  */

import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import Routes from '@/router/index';
import 'antd/dist/antd.css';


ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <BrowserRouter>
      <Routes/>
    </BrowserRouter>
  </React.StrictMode>
);

2.9 权限管理

2.10 keep-alive

3. 集成 react-redux

Redux 中文官网 - JavaScript 应用的状态容器,提供可预测的状态管理。

react 状态、管理的库有不少, RecoilReduxMobXXState。react-redux 也有很多库,例如 redux-saga、dva、Redux ToolKit(RTK)。

yarn add @reduxjs/toolkit react-redux

RTK 完全够用,压根就不需要再去选择使用其他状态管理。

3.1 redux 工作流

img转存失败,建议直接上传图片文件

3.2 redux 三大原则

三大原则 | Redux 中文官网

  1. 单一数据源
  2. state 是只读的
  3. 使用纯函数来执行修改

3.3 添加 store 到工程中

其实使用 Redux Toolkit 简化之后,使用起来很简单的,只是官网表述不清晰。用过 vuex 的完全可以把它当做 vuex 来使用,slice 就相当于命名空间 modules

img转存失败,建议直接上传图片文件

/* store/index.ts */

import { configureStore } from "@reduxjs/toolkit";
import * as reducer from './modules';

// configureStore 创建一个 redux 数据
const store = configureStore({
  // 合并多个Slice
  reducer: {
      ...reducer,
  },
});

export default store;

/* store/modules/index.ts */

export { default as about } from './about'

/* store/modules/about.ts */

import { createSlice } from '@reduxjs/toolkit';

export interface AboutState {
  counter: number;
  title: string
}

const initialState: AboutState = {
  counter: 0,
  title: "redux toolkit pre"
};

// 创建一个 Slice
export const about = createSlice({
  // 命名空间
  name: 'about',

  // 初始化状态值
  initialState,

  // 定义 reducers 并生成关联的操作
  reducers: {
    setCounter(state, { payload }){
      console.log(payload);
      state.counter = payload.counter;
    }
  },
});

// 导出 reducers 方法
export const { setCounter } = about.actions;

// 默认导出
export default about.reducer;

main.ts

/* main.ts */

import React from 'react';
import ReactDOM from 'react-dom/client';

import { BrowserRouter } from 'react-router-dom';
import Routes from '@/router';

import { Provider } from 'react-redux';
import store from '@/store'

import 'antd/dist/antd.css';


ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <Provider store={store}>
      <BrowserRouter>
        <Routes />
      </BrowserRouter>
    </Provider>
  </React.StrictMode>
)

3.4 组件与 rtk 数据交互

还是以 About 组件为例

/* pages/About/index.ts */

import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button, InputNumber } from 'antd';
import { useSelector, useDispatch } from 'react-redux';
import { setCounter } from '@/store/modules/about';

const Index: React.FC<{}> = () => {
    const navigate = useNavigate();
    // 返回
    const handleBack = () => navigate(-1);
    // 前进
    const handleForward = () => navigate(1);
    // 刷新
    const handleRefresh = () => navigate(0);

    const actions = <div style={{ marginBottom: '16px' }}>
        <Button type="primary" style={{ marginRight: '8px' }} onClick={handleBack}>返回</Button>
        <Button type="primary" style={{ marginRight: '8px' }} onClick={handleForward}>前进</Button>
        <Button type="primary" style={{ marginRight: '8px' }} onClick={handleRefresh}>刷新</Button>
        {/* 跳转路由 */}
        <Button type="primary" style={{ marginRight: '8px' }} onClick={() => navigate('/article/1')}>params</Button>
        <Button type="primary" style={{ marginRight: '8px' }} onClick={() => navigate('/article?id=1')}>search</Button>
        <Button type="primary" style={{ marginRight: '8px' }} onClick={() => navigate('/article', { state: { id: 1 } })}>state</Button>
    </div>;

    // 通过useDispatch 派发事件
    const dispatch = useDispatch();

    // 通过useSelector直接拿到store中定义的value
    const { counter } = useSelector((store: any) => store.about);

    const [value, setValue] = useState(counter);

    useEffect(() => {
        // 监听 counter 变化
        console.log(counter);
    }, [counter])

    return <>
        {actions}
        <div>
            <InputNumber value={value} onChange={value => setValue(value)} />
            <Button onClick={() => dispatch(setCounter({ counter: value }))}>保存</Button>
        </div>
    </>
}

export default Index;

组件与 rtk 数据交互

3.5 异步 actions

Using createAsyncThunk | Redux 中文官网

前面写的 actions 是同步的(也就是 reducers 里面的方法),下面用 createAsyncThunk 来创建异步的 actions

/* store/modules/article.ts */

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import _ from 'lodash';

// 异步 actions
export const getList = createAsyncThunk('article/getList',
    async ({ currentPage = 1, pageSize = 5 }: { currentPage?: number, pageSize?: number, type?: string }) => {
        // 模拟异步请求
        const promise = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve({ data: { list: [1, 2, 3, 4, 5], total: 5 } });
            }, 500);
        });

        try {
            var [res] = await Promise.all([promise]);
        } catch (error) {
            console.error(error);
        }

        const payload = _.get(res, 'data', { list: [], total: 0 });
        console.log(payload);
        return payload;
    },
);

export const article = createSlice({
    // 命名空间
    name: "article",
    // state
    initialState: {
        list: [],
        total: 0,
    },
    // 同步 actions
    reducers: {},
    extraReducers: (builder) => {
        builder.addCase(getList.fulfilled, (state, { payload }) => {
            state.list = payload.list;
            state.total = payload.total;
        })
    }
});

// 导出 reducers 方法
export const { } = article.actions;

// 默认导出
export default article.reducer;

在 Article 页面组件中进行测试

/* pages/Article/index.tsx */

import { useDispatch, useSelector } from 'react-redux';
import { getList } from "@/store/modules/article";
import _ from 'lodash';
import { useEffect } from "react";
import type { AnyAction } from "@reduxjs/toolkit";

export default function Index() {
    const dispatch = useDispatch();
    useEffect(() => {
        dispatch(getList({ currentPage: 2, pageSize: 10}) as unknown as AnyAction);
    }, [])

    const { list, total } = useSelector((store: any) => store.article);

    return <>
        <section>
            <h3>测试异步 actions</h3>
            <div>total: {total}</div>
            <ul style={{ padding: 0 }}>list:
                {_.map(list, (item: number, key: number) => <li key={key}>{item}</li>)}
            </ul>
        </section>
    </>
}

img转存失败,建议直接上传图片文件

4. 全局配置文件

打包部署之后可能需要修改配置,为了避免多次重新打包,我们创建一个 config.json 文件

img转存失败,建议直接上传图片文件

4.1 fetch 获取 config.json

<script>
    fetch('./config.json').then(res => res.json()).then(data => {
        window.globalConfig = data;
        console.log(window.globalConfig);
    }).catch(err => {
        console.error(err);
    })
</script>

img转存失败,建议直接上传图片文件

执行 yarn build 打包之后,我们将 config.json 也拷贝到 dist 文件夹中。

4.2 nginx 测试部署

phpStudy 集成了 nginx,我们这里使用 phpStudy

img转存失败,建议直接上传图片文件

然后在浏览器中访问

nginx 测试部署

4.3 Caddy VS Nginx

Caddy是一款功能强大,扩展性高的Web服务器。Caddy采用Go语言编写,可用于静态资源托管和反向代理。

超越 Nginx!号称下一代 Web 服务器,用起来够优雅!

Caddy具有如下主要特性:

  • 对比Nginx复杂的配置,其独创的Caddyfile配置非常简单;
  • 可以通过其提供的Admin API实现动态修改配置;
  • 默认支持自动化HTTPS配置,能自动申请HTTPS证书并进行配置;
  • 能够扩展到数以万计的站点;
  • 可以在任意地方执行,没有额外的依赖;
  • 采用Go语言编写,内存安全更有保证。

5. 拥抱 fetch

使用 Fetch - Web API 接口参考 | MDN

fetch 基于 Promise 作为 XMLHttpRequest 的替代方案,或者说是下一代 Ajax。使用 fetch 默认不存在跨域问题。

5.1 eggjs 写测试接口

侧重点不在于 eggjs,仅用于我们进行接口调试。

img转存失败,建议直接上传图片文件

img转存失败,建议直接上传图片文件

5.2 配置 nginx 代理解决 cors

我们这里不配置 vite 的 server 选项,因为打包之后“开发服务器代理”就没有用了

img转存失败,建议直接上传图片文件

# nginx.conf

http {
    # 不要去代理当前跑前端项目的端口(port 3000)
    # port 8888
    server{
        listen 8888;

        location ^~ / {
            proxy_pass http://127.0.0.1:3000/;
        }

        location ^~ /appServer/ {
            proxy_pass http://127.0.0.1:7001/;
        }
    }
}

改配置之后需要重启 nginx

img转存失败,建议直接上传图片文件

img转存失败,建议直接上传图片文件

/* pages/Home/index.tsx */
const testRequest = async () => {
    try {
        const res = await fetch(`${window.globalConfig?.appServer}`);
        const data = await res.json();
        console.log(data);
    } catch (error) {
        console.error(error);
    }
}

useEffect(()=>{
    testRequest();
},[])

通过 nginx 代理,有效避免了跨域问题

/* config.json */

{
    "appServer": "http://localhost:8888/appServer"
}

5.3 Window 对象声明

我们是通过获取 config.json 来获取配置的,然后将 globalConfig 定义为全局变量。

img转存失败,建议直接上传图片文件

img转存失败,建议直接上传图片文件

/* declare/index.d.ts */

declare interface Window {
    globalConfig: {
        appServer: string,
    }
}

5.4 简单封一下 fetch

Response - Web API 接口参考 | MDN

Request - Web API 接口参考 | MDN

Headers - Web API 接口参考 | MDN

FormData - Web API 接口参考 | MDN

img转存失败,建议直接上传图片文件

6. eslint

6.1 添加远程存储库

git remote add origin 你的远程地址

6.2 添加 eslint

ESLint - 插件化的 JavaScript 代码检测工具

安装

yarn add eslint -D

初始化

npx eslint init

6.3 commit 检测

(1)husky

husky

Husky - Git hooks

typicode.github.io/husky/#/

(2)lint-stage

www.npmjs.com/package/lin…

www.npmjs.com/package/lin…

(3)完整流程

react 代码提交github前进行eslint检测

img转存失败,建议直接上传图片文件

7. 使用 git 的一些问题

git 命令大全

7.1 文件名大小写不会识别

git config --get core.ignorecase 
git config core.ignorecase false

7.2 commit 之后回退

回退到 commit 之前,并保留代码

git reset --soft HEAD^

8. 集成 antd

Ant Design - 一套企业级 UI 设计语言和 React 组件库

我前面以前用到了 ant design 的组件,这里提一下

yarn add antd

然后在 main.ts 中引入一下样式文件即可

/* main.ts */
import 'antd/dist/antd.css';

9. 什么是BFC,如何触发BFC?

9.1 BFC

BFC(Block formatting context)直译为“块级格式化上下文”。它是一个独立的渲染区域,只有Block-level box(块)参与, 它规定了内部的Block-level Box如何布局,并且与这个区域外部毫不相干。

9.2 BFC的布局规则

  1. 内部的Box会在垂直方向,一个接一个地放置。
  2. Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠(按照最大margin值设置)
  3. 每个元素的margin box的左边, 与包含块border box的左边相接触
  4. BFC的区域不会与float box重叠。
  5. BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。
  6. 计算BFC的高度时,浮动元素也参与计算

9.3 哪些元素或属性能触发BFC

  1. 根元素( html )
  2. float 属性不为 none
  3. position 为 absolute 或 fixed
  4. display 为 inline-block, table-cell, table-caption, flex, inline-flex
  5. overflow 不为 visible

9.4 BFC的应用

  1. 自适应两栏布局
  2. 清除内部浮动
  3. 防止margin上下重叠

10. react-query

React Query is often described as the missing data-fetching library for React, but in more technical terms, it makesfetching, caching, synchronizing and updating server statein your React applications a breeze.

img转存失败,建议直接上传图片文件

[React Query](link.zhihu.com/?target=htt…

支持 restful 和 graphql。

11. vite3 搭建 vue3 项目

看到下面这篇文章不错,可以参照配置创建项目

从 0 搭建 Vite 3 + Vue 3 前端工程化项目