搭建React项目,安装配置 react-router-dom、Tailwind CSS、postcss-px-to-viewport等

581 阅读10分钟

一、前言

这是非常基础的学习笔记, 写得不对的地方请大佬指点一二。

我所待过的公司都是使用 Vue 作为前端技术栈, 曾在两三年前也学习过 React 的知识, 那时候 React 官网的文档我个人认为写得太烂了, 看的是在线版的 React.js 小书 , 只以为还是学到了蛮多东西得, 但是工作项目用不上 React, 自己也没有花更多时间去折腾一些项目, 慢慢就荒废了, 哈...

二、计划

我又回来了, 前些天把 React 官网文档大概过了一遍, 新官网比以前太好多了, 知识点写得很详细。

我计划边学边做边记录: 用 React 仿【广西人才网】手机网页端做一个项目,鉴于初次使用,暂不考虑服务端渲染的方式开发。

我的想法是看完文档就直接搭建项目, 这样才有动力一直往下学习, 然后找别人开源的项目看别人怎么写代码, 学习别人的写法, 用在项目里面, 遇到问题就记录下来, 记录学习笔记是非常有必要的, 记录某个知识点的时候会扩展到其他的知识点, 同时会查阅大量的资料, 这个过程有助于学习积累和加深记忆, 我相信收获会很大。

三、项目搭建

使用 Vite 创建项目

最新搭建指南参考 Vite官方文档

npm create vite@latest

# 输入项目名称
? Project name: » gxrcw

# 通过键盘上下键控制选择 React, 如果没反应, 你可能需要使用 Window PowerShell 来执行命令
? Select a framework: » - Use arrow-keys. Return to submit.
    Vanilla
    Vue
>   React
    Preact
    Lit
    Svelte
    Solid
    Qwik
    Others

# 通过键盘上下键控制选择 TypeScript + SWC, 我这里计划使用 TS
? Select a variant: » - Use arrow-keys. Return to submit.
    TypeScript
>   TypeScript + SWC
    JavaScript
    JavaScript + SWC
    Remix ↗

关于 TypeScript + SWC 和其他的区别, 文心一言的回复:

TypeScript + SWC

项目初始化完成, 我这里用 VsCode 打开

# 安装依赖
npm install

# 启动项目
npm run dev

运行成功

运行界面

四、项目配置

(一)安装 react-router-dom

react-routerreact-router-dom 的区别?

react-router: 实现了路由的核心功能
react-router-dom: 基于react-router,加入了在浏览器运行环境下的一些功能,例如:Link组件,会渲染一个a标签; BrowserRouterHashRouter组件,前者使用pushStatepopState事件构建路由,后者使用window.location.hashhashchange事件构建路由。

看起来 react-router-dom 更加常用,好的!安装它:

# 安装
npm i react-router-dom

1. 在 vite.config.ts 配置 /src 别名为 @

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'

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

2. Router 配置

新建 /src/router/router.tsx

看了教程有两种写法:JSX 的方式和[路由数组对象]

JSX 嵌套结构:

JSX 配置路由

我觉得路由数组的方法和 Vue 中类似,我比较熟悉,所以我采用路由数组对象的方式配置:

// 导入创建路由的函数
import { createHashRouter } from 'react-router-dom';
import Home from '@/pages/home/index'
import Login from '@/pages/login/index'
import NotFound from '@/pages/notFound/404'

// 创建router路由实例对象,并配置路由对应关系(路由数组)
const router = createHashRouter([
  {
    path: '/',
    element: <Home />,
  },
  {
    path: '/home',
    Component: Home,
  },
  {
    path: '/login',
    Component: Login
  },
  {
    path: '*',
    Component: NotFound
  },
]);

export default router;

3. 导入路由

App.tsx

// /src/App.tsx
import { RouterProvider } from 'react-router-dom';
import router from './router';
function App() {
  return (
    <RouterProvider router={router} />
  )
}

export default App

main.tsx

// /src/main.tsx
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <App />
)

新建 /src/pages/login/index.tsx 页面

// /src/pages/login/index.tsx
function Login() {
  return <div>登录页</div>;
}

export default Login;

搞定噻!访问路由成功了

登录页

4. React-Router 的常用方法

(1) 可通过 Link 组件的 to 或者利用 useNavigate() 实现页面间的跳转。

// /src/pages/login/index.tsx
import { Link, useNavigate } from 'react-router-dom';

const Login = () => {
    const navigate = useNavigate();
    return (
        <>
            {/* 声明式 */}
            <Link to="/home">Link - 跳转首页</Link>
            {/* 编程式 */}
            <div onClick={() => navigate('/home')}>
              跳转首页
            </div>
        </>
    );
};

export default Login;

(2)跳转路由携带查询参数,通过 useSearchParams 获取参数

// /src/pages/login.tsx
// ...
navigate('/home?a=1'); // 跳转到 Home 页面,并传递参数 a = 1
// /src/pages/home.tsx
import { useSearchParams } from 'react-router-dom';

const Home = () => {
  // useSearchParams用于获取url中的查询参数,即?后的部分
  const [searchParams, setSearchParams] = useSearchParams();
  console.log('searchParams.get("a") =>', searchParams.get('a')); // 获取参数 a 的值
  return (
      <div onClick={() => { setSearchParams({ a: '100' }) }}>修改路由参数</div>
  );
};

export default Home;

useSearchParams

(3)跳转路由携带隐藏的 state 参数,通过 useLocation() 获取参数

// /src/pages/login.tsx
// ...
navigate('/home', {
    state: {
        c: 3,
    },
});
// /src/pages/home.tsx
import { useLocation } from 'react-router-dom';

const Home = () => {
  // useLocation用于获取state传参
  const location = useLocation();
  console.log('location.state =>', location.state)
  return (
      <div>
          首页
      </div>
  );
};

export default Home;

useLocation

(4)通过动态路由方式传参,通过 useParams() 获取参数

/src/router/router.tsx 新增路由:

// ...
{
    path: '/test/:id',
    Component: Home,
},
// /src/pages/login.tsx
// ...
navigate('/test/BXd43435435434');
// /src/pages/home.tsx
import { useParams } from 'react-router-dom';

const Home = () => {
  // useParams钩子用来获取动态路由的参数
  const params = useParams();
  console.log('params =>', params)
  return (
    <div>测试动态路由</div>
  );
};

export default Home;

useParams

(二)安装 Ant Design Mobile

1. ant-design-mobile 的安装和使用

参考Ant Design Mobile官网提供的文档即可,不同的版本会有些许的配置差异。

npm install --save antd-mobile

2. 组件使用

/src/pages/login/index.tsx 中引入组件即可生效

import { Button } from 'antd-mobile';
const Login = () => {
  return (
    <Button color='primary' fill='solid'>Solid</Button>
  );
};

export default Login;

(三)安装 Axios

# 安装
npm install axios

1. 配置 Axios

新建文件 /src/api/config.ts

import axios from "axios";

const newAxios = axios.create({
    baseURL: "https://m.gxrc.com",
    timeout: 5000,
});

// 设置请求拦截器, 请求拦截器后续再根据需求增加
// newAxios.interceptors.request.use();

// 响应拦截器
export default newAxios;

2. 接口请求方法

新建文件 /src/api/common.ts

import axios from "./config";

export const getDistrictId = (params: { url: string }) => {
    // axios得到结果 Promise
    return axios.get("/api/Data/GetDistrictId", { params });
}

3. 做接口测试

/src/pages/home/index.tsx

import { useEffect } from 'react';
import { getDistrictId } from '@/api/common';

const Home = () => {
  const navigate = useNavigate();

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

  const loadData = async () => {
    const res = await getDistrictId({ url: "m.gxrc.com" });
    console.log(res);
  };

  return (
    <div>...</div>
  );
};

export default Home;

这里使用到一个重要的知识点 useEffect, 它是一个 React Hook

Hook 的含义

Hook 这个单词的意思是"钩子"。

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。  React Hooks 就是那些钩子。

你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。

所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用use前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。

React Hooks 入门教程-阮一峰

4. “副作用钩子” useEffect()

React 中,我们有两种定义组件的方式: 函数组件(Function Component) 和 类组件(React Class Component)。

Function Component 就是以 Function 的形式创建的 React 组件,通常函数内部是返回一个 JSX

// /src/pages/login/index.tsx
function Login() {
  return <div>登录页</div>;
}
export default Login;

上面的组件代码就是通过【函数组件】的方式定义组件,Hooks 是辅助 Function Component 的工具,而 useEffect 就是一个常用的 React Hook

useEffect() 可以执行副作用操作,通常用来处理订阅事件、请求数据、更新 DOM 等操作

用法说明:useEffect(setup, dependencies?)

import { useEffect, useState } from "react";
import { Space, Button } from "antd-mobile";
import { useNavigate } from "react-router-dom";

const Login = () => {
  const [count, setCount] = useState(0);
  const navigate = useNavigate();

  useEffect(() => {
    console.log('useEffect的第二个参数为一个空数组,初始化调用一次之后不再执行,相当于类组件的 componentDidMount 函数。');
  }, []);

  useEffect(() => {
    console.log('当useEffect没有第二个参数时,组件的初始化和更新都会执行。');
  });

  useEffect(() => {
    console.log('组件的初始化执行一次,useEffect返回一个函数,这个函数会在组件卸载时执行。');
    return () => {
      console.log('组件已卸载');
    };
  }, []);

  useEffect(() => {
    console.log('当useEffect的第二个参数传数组传一个依赖项,当依赖项的值发生变化,会触发useEffect执行。 => ', count);
  }, [count]);

  return (
    <div className="p-[20px]">
      <div className="pb-[20px]">登录页面{count}</div>
      <Space>
        <Button color='primary' fill='solid' block onClick={() => setCount(count + 1)}>点击</Button>
        <Button color='primary' fill='solid' block onClick={() => navigate('/home')}>跳转首页</Button>
      </Space>
    </div>
  );
};

export default Login;

5. “状态管理钩子” useState()

用法说明: const [state, setState] = useState(initialState)

主要作用就是在函数式组件中管理组件的数据状态。通过使用 useState,可以在函数式组件中创建、读取和更新状态。

示例: 声明一个状态 name, 初始值是 “小明”,并声明一个对应的状态更新函数 setName,当按钮点击的时候把 name 的值修改为 “大明”。

import { Button } from 'antd-mobile';
import { useState } from 'react';  

function MyComponent() {  
    const [name, setName] = useState('小明');
    
    return (
      <>
        <div>{name}</div>
        <Button color='primary' fill='solid' block onClick={() => setName('大明')}>
        修改名称
        </Button>
      </>
    )
}

export default MyComponent;

(四)安装 Tailwind CSS

1. Tailwind CSS是什么?

Tailwind CSS 的工作原理是扫描所有 HTML 文件、JavaScript 组件以及任何 模板中的 CSS 类(class)名,然后生成相应的样式代码并写入 到一个静态 CSS 文件中。

意思就是说,我们只需要给 DOM 写上类名就可以了,Tailwind CSS 会生成对应的样式给我们。

很久之前就听说 Tailwind CSS 的大名,但是却从来没有用过,因为我第一印象是觉得在 html 中写一堆 class 类名称会显得代码很乱,后期的人不好维护。

但我发现 React 的项目中的样式部分大多数情况都是通过引入外部的 css 文件的,不像 Vue 的模版那么便捷,我经常使用 VsCode 的快捷键 Ctrl + D 来快速找到对应的样式,分开两个文件我就没办法这样做了。

我不喜欢从引入外部的 css 文件,所以尝试使用 Tailwind CSS 来解决所有的样式问题。

2. 安装配置

(1) 使用 PostCSS 的方式安装

npm install -D tailwindcss postcss autoprefixer

npx tailwindcss init

(2) 目录中会自动生成 tailwind.config.js 文件, 修改 content 字段,如下所示

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

(3) 创建 postcss.config.js

export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

(4) 将 tailwind@tailwind 指令添加到主文件中

新建文件 /src/assets/css/tailwind.css

@tailwind base;
@tailwind components;
@tailwind utilities;

main.tsx 中导入

import './assets/css/tailwind.css'

// ...

(5) 使用方法

使用 Tailwind CSS 特别简单,只需要按照命名规则给需要添加样式的DOM元素写上 class 名称就可以。这里需要注意的是,在 ReactJSX 语法中,‌为了保持与 JavaScript 的语法一致,‌并且避免与 JavaScript 的关键字冲突,‌React 选择使用 className 来替代 class

示例:

const Demo = () => {
  return (
    <div className='p-[20px] bg-gray-100'>
      <div className="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4">
        <div className="shrink-0">
          <img className="h-12 w-12" src="/src/assets/imgs/logo.png" alt="Logo" />
        </div>
        <div>
          <div className="text-xl font-medium text-black">Tailwind CSS</div>
          <p className="text-slate-500">按照规则设置class就可以!</p>
        </div>
      </div>
    </div>
  );
};

export default Demo;

运行效果图:

Tailwind CSS 使用

我自己的想法是,浏览一遍官方文档或者找几篇不错的文章看一遍,然后尝试去写页面,很快就能上手。

无他,唯手熟尔!

(五)安装 postcss-px-to-viewport-8-plugin,实现移动端适配

关于移动端适配,我后来觉得固定字体大小也挺好的,就不做字体大小适配了

postcss-px-to-viewport 可以将代码中 px 单位转为 remvw 等视口单位,开发的时候按照设计稿的像素写就可以了。

我项目安装的 postcss 版本是 ^8.4.40, 控制台会报错提示:postcss-px-to-viewport 不适配最新版本的 postcss8:

postcss-px-to-viewport: postcss.plugin was deprecated.
Migration guide:https://evilmartians.com/chronicles/postcss-8-plugin-migration

所以我这里选择安装 postcss-px-to-viewport-8-plugin

npm install postcss-px-to-viewport-8-plugin --save-dev

postcss.config.js 中添加配置:

export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
    'postcss-px-to-viewport-8-plugin': {
      unitToConvert: 'px', // 要转化的单位
      viewportWidth: 750, // UI设计稿的宽度
      unitPrecision: 6, // 转换后的精度,即小数点位数
      propList: ['*'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
      viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw
      fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw
      selectorBlackList: [], // 指定不转换为视窗单位的类名,
      minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
      mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
      replace: true, // 是否转换后直接更换属性值
      exclude: [/\/node_modules/], // 设置忽略文件,用正则做目录名匹配
      // include: /\/src\/mobile\//, // 设置匹配的文件, 只有匹配到的文件才会被转换
      landscape: false, // 是否处理横屏情况
      // landscapeUnit: 'vw', // 横屏时使用的单位
      // landscapeWidth: 568, // 横屏时使用的视口宽度
    },
  },
};

未安装前效果:

px

安装 postcss-px-to-viewport-8-plugin

vw

完美,接下来可以搞页面了。

--学习太累了,我还是想出去玩--

5f8ebb7cc53c1ff5cf23bf84adf17cf.jpg

参考