构建项目 使用Vite构建 pnpm create vite@latest my-app --template react-ts
这个命令会构建一个项目名称为my-app的项目结构
构建完项目结构后执行如下的命令会安装项目所需的依赖:
执行该命令启动项目
添加@别名 修改ts配置
{ "compilerOptions" : { ... "baseUrl" : "." , "paths" : { "@/*" : [ "./src/*" ] } } , ... }
修改vite配置
安装依赖包
... import * as path from "path" ;import { fileURLToPath } from "url" ;const __dirname = path.dirname (fileURLToPath (import .meta .url )); export default defineConfig ({ ... resolve : { alias : { "@" : path.resolve (__dirname, "./src" ), }, }, });
安装插件 Taiwind CSS 使用vite安装
安装Taiwind CSS依赖
pnpm add tailwindcss @tailwindcss/vite
在vite.config.tsvite配置文件中添加taiwind的插件
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [ react(), tailwindcss()] , } )
在全局css文件中引入Taiwind css
@import "tailwindcss" ;...
在使用vite构建的React项目结构中,main.tsx导入了index.css,所以可以在index.css文件中导入
在App.tsx中测试Taiwin css
<h1 className='bg-red-500' >hello taiwind css</h1>
可以看到红色背景的hello taiwind css
解决默认样式覆盖问题 可以发现,<h1>标签的样式没有了,这是因为Taiwind CSS会把默认的样式覆盖。
因此可以在全局css设置中,设置h1标签的样式
// index.css @import "tailwindcss" ;... @layer base { ... h1 { @apply text-3 xl font-bold text-foreground tracking-tight; font-family : system-ui, -apple-system, sans-serif; line-height : 1.2 ; margin-bottom : 1rem ; } } ...
shadcn/ui 安装,在tsconfig.json文件添加编译选项
{ "files" : [ ] , "references" : [ { "path" : "./tsconfig.app.json" } , { "path" : "./tsconfig.node.json" } ] , "compilerOptions" : { "baseUrl" : "." , "paths" : { "@/*" : [ "./src/*" ] } } }
编辑tsconfig.app.json文件,添加shadcn/ui组件的路径解析
{ ... "compilerOptions" : { ... "baseUrl" : "." , "paths" : { "@/*" : [ "./src/*" ] } } , ... }
添加依赖@types/node
编辑vite.config.ts,添加@别名设置、导入Taiwind CSS
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import path from "path" import tailwindcss from "@tailwindcss/vite" export default defineConfig({ plugins: [ react(), tailwindcss()] , resolve: { alias: { "@" : path.resolve(__dirname, "./src" ), } , } , } )
执行命令初始化shadcn/ui组件
pnpm dlx shadcn@latest init
测试添加按钮
pnpm dlx shadcn@latest add button
执行完命令后会在src文件夹下新增了一个文件夹components/ui,并且里面还多了一个button.tsx,因此我们可以直接导入这个组件
import "./App.css" import { Button } from "@/components/ui/button" ;function App ( ) { return ( <> <h1 className ='bg-red-500' > hello taiwind css</h1 > <Button > Click me</Button > </> ) } export default App
可以看到页面出现了一个黑色按钮
Material UI 安装依赖、安装字体
// 安装依赖 pnpm add @mui/material @emotion/react @emotion/styled // 安装字体 pnpm add @fontsource/roboto
在入口ts文件中导入字体
import '@fontsource/roboto/300.css' ;import '@fontsource/roboto/400.css' ;import '@fontsource/roboto/500.css' ;import '@fontsource/roboto/700.css' ;
安装图标
pnpm add @mui/icons-material
使用
import "./App.css" import { Button } from "@/components/ui/button" ;import MButton from "@mui/material/Button" ;import AccessAlarmIcon from '@mui/icons-material/AccessAlarm' ;function App ( ) { return ( <> <h1 className ='bg-red-500 text-border' > hello taiwind css</h1 > <Button > Click me</Button > <MButton variant ="contained" className ="text-border" > <AccessAlarmIcon /> </MButton > </> ) } export default App
Ant Design 安装
使用
import React from 'react' ;import { Button } from 'antd' ;const App = ( ) => ( <div className ="App" > <Button type ="primary" > Button</Button > </div > ); export default App ;
react-router 安装
在根节点中添加路由浏览组件BrowserRouter
import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import { BrowserRouter , Routes , Route } from "react-router" ;import App from './App.tsx' createRoot (document .getElementById ('root' )!).render ( <StrictMode > <BrowserRouter > <Routes > <Route path ="/" element ={ <App /> } /> </Routes > </BrowserRouter > </StrictMode > ,)
可以看到/路由会展示App.tsx的内容
基本使用 添加路由 下面以添加关于页为例
新建about.tsx
function About ( ) { return ( <div className ="w-full h-full" > <div className ="flex flex-col" > <h1 className ="text-blue-300" > About</h1 > <p > This is the about page</p > </div > </div > ) } export default About ;
在App.tsx中添加路由
import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import { BrowserRouter , Routes , Route } from "react-router" ;import App from './App.tsx' import About from "./pages/about/About.tsx" ;createRoot (document .getElementById ('root' )!).render ( <StrictMode > <BrowserRouter > <Routes > <Route path ="/" element ={ <App /> } /> <Route path ="/about" element ={ <About /> } /> </Routes > </BrowserRouter > </StrictMode > ,)
修改路由为/about的时候可以看到about页面
嵌套路由 下面以添加两个子路由为例
新建about2.tsx和about3.tsx
function About2 ( ) { return ( <div className ="w-full h-full" > <h1 className ="bg-red-500" > About2</h1 > <p > This is the second about page</p > </div > ) } export default About2 ;function About3 ( ) { return ( <div className ="w-full h-full" > <h1 className ="bg-yellow-500" > About3</h1 > <p > This is the third about page</p > </div > ) } export default About3 ;
在main.tsx中添加路由嵌套
import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import { BrowserRouter , Routes , Route } from "react-router" ;import App from './App.tsx' import About from "./pages/about/About.tsx" ;import About2 from "./pages/about/about2/About2.tsx" ;import About3 from "./pages/about/about3/About3.tsx" ;createRoot (document .getElementById ('root' )!).render ( <StrictMode > <BrowserRouter > <Routes > <Route path ="/" element ={ <App /> } /> <Route path ="about" element ={ <About /> } > <Route index element ={ <About2 /> } /> <Route path ='about3' element ={ <About3 /> } /> </Route > </Routes > </BrowserRouter > </StrictMode > ,)
其中,index表示,在访问/about路由的时候会首先在嵌套的位置展示<About2 />的内容,切换到/about/about3才会在嵌套的位置展示<About3 />的内容
路由跳转 使用useNavigate钩子函数
import "./App.css" import { Button } from "@/components/ui/button" ;import { useNavigate } from "react-router" ;function App ( ) { let navigate = useNavigate (); const navigateToAbout = ( ) => { navigate ("/about" ) } return ( <> <Button onClick ={navigateToAbout} > Turn to About</Button > </> ) } export default App
高阶使用 路由守卫 实现类似vue的路由表
新建路由结构,Route.tsx
import { RouteObject } from "react-router" ;export type RouteInterface = RouteObject & { name?: string ; auth?: boolean ; children?: RouteInterface []; absPath?: string ; };
在src中新建一个文件夹route,并且新建index.tsx,用于存储路由表
import { RouteInterface } from "@/models/Route.tsx" ;import { lazy } from "react" ;const Index = lazy (() => import ("../pages/Index.tsx" ));const About = lazy (() => import ("../pages/about/About.tsx" ));const About2 = lazy (() => import ("../pages/about/about2/About2.tsx" ));const About3 = lazy (() => import ("../pages/about/about3/About3.tsx" ));const Layout = lazy (() => import ("../pages/layout/Layout.tsx" ));const Header = lazy (() => import ("../pages/layout/header/Header.tsx" ));const Header2 = lazy (() => import ("../pages/layout/header/Header2.tsx" ));const User = lazy (() => import ("../pages/user/User.tsx" ));const NotFound = lazy (() => import ("../pages/notFound/NotFound.tsx" ));export const routes : RouteInterface [] = [ { path : "/" , name : "Index" , element : <Index /> , }, { path : "about" , name : "About" , element : <About /> , children : [ { index : true , name : "About2" , element : <About2 /> , }, { path : "about3" , name : "About3" , element : <About3 /> , }, ], }, { path : "layout" , name : "Layout" , element : <Layout /> , children : [ { index : true , name : "Header" , element : <Header /> , }, { path : "header2" , name : "Header2" , element : <Header2 /> , }, ], }, { auth : true , path : "user/:userId/:userName" , name : "User" , element : <User /> , }, { path : "/notFound" , name : "NotFound" , element : <NotFound /> , }, ];
lazy是React提供的懒加载
在App.tsx中,添加路由守卫组件
import { useLocation, useNavigate, useRoutes, matchRoutes, } from "react-router-dom" ; import "./App.css" ;import { routes } from "./route/index" ;import { useEffect } from "react" ;function RouterBeforeEach ({ children }: any ) { const location = useLocation (); const navigator = useNavigate (); useEffect (() => { const match = matchRoutes (routes, location.pathname ); console .log (match); if (match) { if (match[0 ].route .auth ) { console .log ("需要鉴权" ); navigator ("/" ); } } else { console .log ("未找到路由" ); navigator ("/notFound" ); } }, [location.pathname ]); return children; } function App ( ) { const element = useRoutes (routes); return ( <div > <div > <RouterBeforeEach > {element}</RouterBeforeEach > </div > </div > ); } export default App ;
useEffect有点类似vue的watch,当location.pathname发生变化的时候,才会执行里面的内容
matchRoutes是react-route提供的钩子方法,用于根据路径查找路由表中的路由,也可以识别动态路由
React知识点 组件传参 父组件向子组件传参(父 -> 子) 父子组件传参,子组件可以使用props接收父组件传入的参数
父组件内容
import Son from "./son" ;function Father ( ) { return ( <> <div > <h1 > I'm Father</h1 > <Son parentName ="John" /> </div > </> ) } export default Father ;
子组件内容
function Son (props: { parentName: string , } ) { const { parentName } = props; return ( <> <div > <h1 > I'm Son</h1 > <p > My father is {parentName}</p > </div > </> ) } export default Son ;
子组件向父组件传参(子 -> 父) 子组件向父组件传递数据的时候可以使用回调函数
父组件内容
import Son from "./son" ;import { useState } from "react" ;function Father ( ) { const [sonMsg, setSonMsg] = useState ("" ) const handleSonMsg = (data: string ) => { setSonMsg (data) } return ( <> <div > <h1 > I'm Father</h1 > <p > 儿子说的话:{sonMsg}</p > <Son parentName ="John" onSendMsg ={handleSonMsg} /> </div > </> ) } export default Father ;
子组件内容
import { Button } from "@mui/material" ;function Son (props: { parentName: string , onSendMsg?: (data: string ) => void } ) { const { parentName, onSendMsg } = props; const speakMsgToFather = ( ) => { const msg = "Hello Father!" if (onSendMsg) { onSendMsg (msg); } } return ( <> <div className ="border-2 p-2 mt-2 mb-2" > <h1 > I'm Son</h1 > <p > My father is {parentName}</p > <Button onClick ={speakMsgToFather} variant ="contained" > 给爸爸说句话</Button > </div > </> ) } export default Son ;
兄弟传参 兄弟之间传参可以使用useContext钩子函数进行订阅上下文
父组件内容
import Son from "./son" ;import Daughter from "./Daugther" ;import { useState, createContext } from "react" ;export const SharedContext = createContext<{ data : string ; setData : (value: string ) => void ; }>({ data : '' , setData : () => { } }) function Father ( ) { const [sharedState, setSharedState] = useState ("---我是父亲,这是共享数据---" ) const [sonMsg, setSonMsg] = useState ("" ) const handleSonMsg = (data: string ) => { setSonMsg (data) } return ( <> <SharedContext.Provider value ={{ data: sharedState , setData: setSharedState }}> <div > <h1 > I'm Father</h1 > <p > 儿子说的话:{sonMsg}</p > <p > 共享数据内容:{sharedState}</p > <Son parentName ="John" onSendMsg ={handleSonMsg} /> <Daughter > </Daughter > </div > </SharedContext.Provider > </> ) } export default Father ;
子组件内容
import { useContext } from "react" ;import { Button } from "@mui/material" ;import { SharedContext } from "./father" ;function Son (props: { parentName: string , onSendMsg?: (data: string ) => void } ) { const { data, setData } = useContext (SharedContext ); const { parentName, onSendMsg } = props; const speakMsgToFather = ( ) => { const msg = "Hello Father! This is my code:" + Math .random ().toString (36 ).substring (2 , 10 ); if (onSendMsg) { onSendMsg (msg); } } const handleChangeShareState = ( ) => { const shareData = "我是子组件,正在更新共享数据" setData (shareData); } return ( <> <div className ="border-2 p-2 mt-2 mb-2" > <h1 > I'm Son</h1 > <p > My father is {parentName}</p > <p > 共享数据内容:{data}</p > <div className ="flex flex-row" > <Button onClick ={speakMsgToFather} variant ="contained" > 给爸爸说句话</Button > <Button onClick ={handleChangeShareState} variant ="outlined" > 切换共享数据</Button > </div > </div > </> ) } export default Son ;
女儿组件内容
import { useContext } from "react" ;import { SharedContext } from "./father" ;import { Button } from "@mui/material" ;function Daughter ( ) { const { data, setData } = useContext (SharedContext ); const handleChange = ( ) => { setData ("---我是女儿,我改变了共享数据---" ) } return ( <> <div className ="border-2 p-2 mt-2 mb-2" > <h1 > I'm Daughter</h1 > <p > 共享数据内容:{data}</p > <Button variant ="text" size ="large" onClick ={handleChange} > 改变共享数据</Button > </div > </> ) } export default Daughter ;
React效率问题 路由跳转
第三方组件:react-router、Material UI
问题描述 使用mui写导航页面的时候,使用mui的Link组件作为导航项,并且采用嵌套路由的方式
点击导航的时候,会发现加载速度慢,并且整个页面都在加载
解决方法 使用react-router的RouterLink替换mui的Link
原因分析 做导航界面的时候不能直接使用mui的Link组件,这个组件的本质是<a></a>标签,切换路由的时候会加载整个页面
填写大表单
第三方组件:React-Hook-Form、Material UI
问题描述 在开发大表单页面的时候,使用mui的TextField组件作为输入框的时候,修改数据的时候(onChange)响应很慢,体验感极差。
于是考虑是否可以在输入完数据之后再进行事件响应(onBlur),结果失去焦点的事件可以执行,但是无法修改数据。
通过查看官方文档发现,TextField必须要监听onChange事件才可以修改数据。
在大表单的情况下,不可能每个字段都加上onChange,每个字段都设置State
解决方法 使用React-Hook-Form,可以有效解决大表单填写的问题