从零开始的Web端React音乐播放器(二)

67 阅读4分钟

前言

上一节我们通过vite创建了项目工程,这一节我们将一些常用的插件进行安装并进行简单的配置

项目地址

前端地址:react-music 后端地址:NeteaseCloudMusicApi

react-router@6

安装依赖

pnpm install react-router-dom

在 src 目录下新建 pages 和 router 文件夹 在 pages 下新增页面级组件 Home/index.tsx, About/index.tsx

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

const Home = () => {
  return (
    <div>
      这里是Home
      <Link to="/about">去about</Link>
    </div>
  )
}

export default Home
import { Link } from 'react-router-dom'

const About = () => {
  return (
    <div>
      这里是About
      <Link to="/home">去Home</Link>
    </div>
  )
}

export default About

在 router 目录下创建 index.tsx

import { createBrowserRouter, Navigate } from 'react-router-dom'
import type { RouteObject } from 'react-router-dom'
import Home from '@/pages/Home'
import About from '@/pages/About'

const routes: RouteObject[] = [
  {
    path: '/',
    children: [
      {
        index: true,
        element: <Navigate to="/home" replace />,
      },
      {
        path: 'home',
        element: <Home />,
      },
      {
        path: 'about',
        element: <About />,
      },
    ],
  },
]

export default createBrowserRouter(routes, {
  basename: '/',
})

修改 src/App.tsx

import { RouterProvider } from 'react-router-dom'
import router from './router'

const App = () => {
  return <RouterProvider router={router} />
}

export default App

router 目录下新建 lazyLoad.tsx 实现组件懒加载

import { Suspense } from 'react'

const lazyLoad = (Component: React.LazyExoticComponent<() => JSX.Element>) => {
  return (
    <Suspense>
      <Component />
    </Suspense>
  )
}

export default lazyLoad

修改 router/index.tsx

import { lazy } from 'react'
import { createBrowserRouter, Navigate } from 'react-router-dom'
import type { RouteObject } from 'react-router-dom'
import lazyLoad from './lazyLoad'

const Home = lazy(() => import('@/pages/Home'))
const About = lazy(() => import('@/pages/About'))

const routes: RouteObject[] = [
  {
    path: '/',
    children: [
      {
        index: true,
        element: <Navigate to="/home" replace />
      },
      {
        path: 'home',
        element: lazyLoad(Home)
      },
      {
        path: 'about',
        element: lazyLoad(About)
      }
    ]
  }
]

export default createBrowserRouter(routes, {
  basename: '/'
})

zustand

安装依赖

pnpm install zustand

在 src 目录下新建 stores 文件夹并添加 counter.ts

import { create } from 'zustand'

interface CounterState {
  counter: number
  increase: (cnt: number) => void
}

const useCounterStore = create<CounterState>()((set) => ({
  counter: 0,
  increase: (cnt) => set((state) => ({ counter: state.counter + cnt }))
}))

export default useCounterStore

在Home组件中进行使用

import { Link } from 'react-router-dom'
import useCounterStore from '@/stores/counter'

const Home = () => {
  const counter = useCounterStore((state) => state.counter)
  const increase = useCounterStore((state) => state.increase)

  return (
    <div>
      这里是Home
      <Link to="/about">去about</Link>
      <button onClick={() => increase(1)}> counter: {counter} </button>
    </div>
  )
}

export default Home

axios

安装依赖

pnpm install axios

在 src 目录下新建 utils 文件夹并添加 request.ts 并对 axios 进行简单封装(后续根据需求按需更改)

import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios'

// 创建 axios 实例
const service = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  maxBodyLength: 5 * 1024 * 1024,
  withCredentials: true,
  timeout: 50000
})

// 请求拦截器
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    config.params = {
      ...config.params,
      t: Date.now()
    }
    return config
  },
  (error: unknown) => {
    return Promise.reject(error)
  }
)

// 响应拦截器
service.interceptors.response.use(
  (response: AxiosResponse) => {
    return response
  },
  (error: unknown) => {
    return Promise.reject(error)
  }
)

interface Http {
  get<T>(url: string, params?: unknown): Promise<T>

  post<T>(url: string, params?: unknown): Promise<T>
}

const http: Http = {
  get(url, params) {
    return new Promise((resolve, reject) => {
      service
        .get(url, { params })
        .then((res) => {
          resolve(res.data)
        })
        .catch((err) => {
          reject(err.data)
        })
    })
  },

  post(url, params) {
    return new Promise((resolve, reject) => {
      service
        .post(url, JSON.stringify(params))
        .then((res) => {
          resolve(res.data)
        })
        .catch((err) => {
          reject(err.data)
        })
    })
  }
}

// 导出 axios 实例
export default http

.env

在 src 目录下新建 .env.development 和 .env.production

// .env.development
VITE_APP_ENV = 'development'
VITE_BASE_URL = '/'
VITE_API_URL = '/'
// .env.production
VITE_APP_ENV = 'production'
VITE_BASE_URL = '/'
VITE_API_URL = '/'

修改 router/index.tsx 以及 utils/request.ts

export default createBrowserRouter(routes, {
  basename: import.meta.env.VITE_BASE_URL
})
const service = axios.create({
  baseURL: import.meta.env.VITE_APP_BASE_API,
  timeout: 50000
})

react-query

安装依赖

pnpm install react-query

这里简单举个例子,具体使用后面用到在讲解

 const [list, setList] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    fetchList().then(res => {
      setLoading(false);
      setList(res?.data)
    }).catch(err => setLoading(false))
  }, []);
 const { isLoading, error, data } = useQuery('getListData', () => fetchList().then(res => res.data));

这两部分的代码是等效的,我们不难看出 react-query 的好处

tailwindcss + shadcn/ui

tailwincss 是一个实用且高度可定制的 CSS 框架。它让开发者通过简单地添加类名来轻松创建任何样式,无需编写自定义 CSS。与其他 CSS 框架相比,Tailwindcss 更加注重可定制性,因此可以更好地满足特定项目的需要。 shadcn/ui是一个使用 Radix UI 和 Tailwind CSS 构建的可重用组件库。具有优秀的设计和良好的用户体验。它提供了许多实用的组件,如日期选择器、分页控件等,而且易于使用并且高度可定制。 简单来说,我们可以更加轻松且自由的来开发我们的页面,并且不再过于的关注 ui 组件库,因为我们本身还是在书写 css

tailwincss

安装依赖

pnpm install tailwindcss postcss autoprefixer -D 
npx tailwindcss init -p

修改 tailwind.config.js

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

修改 src/index.css 并在 src/main.ts 引入

@tailwind base;
@tailwind components;
@tailwind utilities;
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'

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

shadcn/ui

这个ui库非常特殊,他的本质就是代码片段,和我们平常使用的方法大有不同,按照我下面的操作进行即可 利用 shadcn-uiinit 命令来设置项目

npx shadcn-ui@latest init

配置components.json

Would you like to use TypeScript (recommended)? no / yes
Which style would you like to use? › Default
Which color would you like to use as base color? › Slate
Where is your global CSS file? › › src/index.css
Do you want to use CSS variables for colors? › no / yes
Where is your tailwind.config.js located? › tailwind.config.js
Configure the import alias for components: › @/components
Configure the import alias for utils: › @/lib/utils
Are you using React Server Components? › no / yes (no)

添加组件使用,这里我们用 switch 组件实例 并且尝试添加一些 tailwindcss 的样式

npx shadcn-ui@latest add switch

这时候他会自动的去生成我们需要的组件到 src/components/ui 里面,我们引入使用即可 修改我们的 home 组件

import { Link } from 'react-router-dom'
import useCounterStore from '@/stores/counter'
import { Switch } from '@/components/ui/switch'

const Home = () => {
  const counter = useCounterStore((state) => state.counter)
  const increase = useCounterStore((state) => state.increase)

  return (
    <div className="bg-pink-900 h-96">
      这里是Home
      <span className="text-lg">123</span>
      <Link to="/about">去about</Link>
      <Switch />
      <button onClick={() => increase(1)}> counter: {counter} </button>
    </div>
  )
}

export default Home

如果出现 'className' is missing in props validation 这个报错 只需要在 .eslintrc.cjs 添加 rules 相关修改即可

  rules: {
    'react/prop-types': 'off'
  },