一起快乐学习哦~
react脚手架
react提供了一个用于创建react项目的脚手架库:create-react-app,该脚手架使用的技术架构为react+webpack+es6+eslint,利用脚手架开发模块化、组件化、工程化项目;
在使用该脚手架之前需安装create-react-app:
npm install create-react-app -g
安装后,利用该库创建项目
create-react-app xxx项目名
创建成功后,进入该文件夹,文件夹中内容如下:
README.md
node_modules/
package.json
public/
index.html
favicon.ico
manifest.json --- 应用加壳的配置文件,可删
robots.txt ---爬虫协议文件,可删
src/
App.css
App.js
App.test.js ---用于给App组件做测试,可删
index.css
index.js
logo.svg
reportWebVitals ---页面性能分析文件(需要web-vitals库的支持),可删
setupTests.js ----组件单元测试文件(需要jest dom库的支持),可删
其中public/index.html是页面文件(SPA),src/index.js是项目的入口文件。
根据pakage.json中的命令执行启动即可,一般是npm start;项目会默认在3000端口启动;
补充:(脚手架配置代理有两种方法)
一: 在package.json中配置
"proxy": "xxxx服务器" // 填入要连接的后端服务器
此种方法配置简单,但是不能配置多个代理;工作原理是在先匹配前端地址找资源,若找不到则根据此处找到代理到后端服务器;
二:创建代理配置文件setupProxy.js
在src文件夹下创建配置文件setupProxy.js,并配置如下代理规则:
// const proxy = require('http-proxy-middleware') // http-proxy-middleware 1.x 版本前配置代理使用
const { createProxyMiddleware } = require('http-proxy-middleware'); // http-proxy-middleware 1.x 版本后配置代理使用
module.exports = function(app) {
app.use(
createProxyMiddleware('/api', {
target: 'http://localhost:5000',
changeOrigin: true,
// changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
// changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
// changeOrigin默认值为false,但我们一般将changeOrigin值设为true
pathRewrite: {'^/api': ''}
})
);
}
此方法可用于配置多个代理;
路由react-router-dom@5
前言
react中配置路由需要使用react-router-dom插件库,上述脚手架中并未自动下载该库,需要在使用前先install一下;
npm install react-router-dom@5 // 此处先对react-router-dom@5进行的讲解
内置组件包括<BrowserRouter><HashRouter><Route><Redirect><Link><NavLink><Switch>
其它包括history对象、match对象、withRouter函数
<Link>以及<NavLink>组件用来指定导航链接,该组件要指定to属性;经编译后变为a标签,to属性编译为a标签的href属性,NavLink与Link的区别是他默认有一个active类(activeClassName={active}),可以在调到某路由链接时高亮;当<NavLink>组件增加了end属性后,表示若子路由匹配成功,则父组件导航不显示高亮效果(默认该属性为false表明当匹配到子路由时父路由也会高亮)~
<Route>组件用于指定根据上述导航链接所匹配的组件,该标签指定path与component属性,其中path属性用于匹配Link或NavLink组件的to属性,component属性用以指定匹配路由组件;
<Link><NavLink><Route>都要被Router组件包裹,可通过两个组件实现<HashRouter>和<BrowserRouter>,其中BrowserRouter主要依靠H5的history对象实现((localhost:3000/xxxx)),而HashRouter则主要依赖于URL的哈希值实现(localhost:3000/#/xxxx);另外HashRouter在刷新后会导致state参数的丢失,而对BrowserRouter没有影响(因为state参数保存在history中);HashRouter可以用于解决一些路径错误相关的问题;由于Router组件在整个react项目中只需要引入一次,因此一般包裹在App组件外侧;
<Switch>组件用于提高路由匹配效率,正常情况下路由得到匹配(模糊匹配)后,依然会继续往下查找,被<Switch>组件覆盖后,可以实现在找到某匹配路由后不再继续向下匹配~若在Router组件中配置exact属性,则为精确匹配~严格模式开启后,可能会匹配不到二级及以下路由,慎重使用;
<Switch>
<Route exact path="/route1" component={Change1}></Route>
<Route exact path="/route2" component={Change2}></Route>
</Switch>
<Redirect>组件用于路由的重定向,一般放置在路由的最后,用以匹配路径找不到的组件~
<Route path="/route1" component={Change1}></Route>
<Route path="/route2" component={Change2}></Route>
<Redirect to="route1"/>
如下代码是路由组件的简单应用(react-router-dom@5);
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import {BrowserRouter} from 'react-router-dom'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
// app.js
import { Component } from 'react';
import Change1 from './pages/Change1'; // 引入路由组件Change1
import Change2 from './pages/Change2'; // 引入路由组件Change2
import { NavLink, Route } from 'react-router-dom';
import './App.css';
export default class App extends Component {
render() {
return (
<div className="App">
<NavLink to="/route1">1路由组件</NavLink>
<NavLink to="/route2" children={"2路由组件"}></NavLink>
<Switch>
<Route exact path="/route1" component={Change1}></Route>
<Route exact path="/route2" component={Change2}></Route>
<Redirect to="route1"/>
</Switch>
</div>
)
}
}
一般组件与路由组件
若一个组件通过切换路由进行展示,则为路由组件,在react中,路由组件与一般组件最大的区别是路由组件中的props对象会包含三个固定的属性:history/location/match
而一般组件中props必须通过父组件进行传递才有值,否则为一个空对象;
使用withRouter函数加工一般组件后,可将一般组件变为路由组件,从而可以使用路由组件的方法;
路由懒加载
和vue中一样,如果想要路由组件在需要展示时才加载,则就要用到路由懒加载;在react中路由懒加载需要引入react中的lazy函数;指定了懒加载之后必须使用Suspense组件的fallback属性指定路由在加载过程中的提示,一般放置一个页面;
import React, { Component, lazy, Suspense} from 'react'
import {NavLink, Route,Routes} from 'react-router-dom'
import Loading from './pages/Loading' // 正常引入路由加载未出来之前的页面
const Change1 = lazy(() => import('./pages/Change1')) // 使用路由懒加载引入路由组件
const Change2 = lazy(() => import('./pages/Change2'))
export default class Parents extends Component {
render() {
return (
<div>
<NavLink to="change1">路由1组件</NavLink>
<NavLink to="change2">路由2组件</NavLink>
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/change1" element={<Change1 />} />
<Route path="/change2" element={<Change2 />} />
</Routes>
</Suspense>
</div>
)
}
}
向路由组件传递参数
同vue一样,也有三种传递参数的方式:
- params参数
传递params参数要在路由链接中通过/连接,同时在注册路由时用/:占位
<Link to="/test/Tom/19">详情</Link> // 路由链接
<Route path="/test/:name/:age" component={Test}> // 注册路由
使用params参数方式传递过的数据,需要通过this.props.match.params取到~
- search参数
传递search参数要在路由链接中通过?键值对&键值对形式,在注册路由时无需声明接收,正常注册即可;
<NavLink to="/route2?name=Tom&age=19" children={"2路由组件"}></NavLink> // 路由链接
<Route exact path="/route2" component={Change2}></Route> // 注册路由
接收search参数时不像接收params参数那样方便,通过this.props.location.search可以取到类似于urlencoded编码形式的参数,需进一步处理;
可以用字符串的截取方法拿到正确的参数数据,一般情况下会利用querystring库配合解析;
npm install querystring // 安装querystring
import qs from 'querystring' // 在组件中引入该库
querystring身上有两个方法:stringify与parse方法,可以将urlencoded编码形式的字符串解析为键值对形式;也可以将键值对形式字符串为urlencoded形式~
const obj = {
name: 'Tom',
age: 19
}
console.log(qs.stringify(obj)); // name=Tom&age=19
const str = 'name=Tom&age=19';
console.log(qs.parse(str)); // {age:"19", name:"Tom"}
- state参数
传递state参数需要在路由链接中利用state: {}形式携带参数(使用to属性的对象形式);在注册路由时无需声明,亦正常注册即可;
<NavLink to={ {pathname: '/route1', state: {name: 'Tom', age: 18}} }>1路由组件</NavLink> // 路由链接采用对象形式传递to属性
<Route exact path="/route1" component={Change1}></Route> // 注册路由
接收state参数时通过this.props.location.state即可;
传递state参数最大的特点是该参数在页面刷新时也不会丢失~
补充(to属性的两种写法):
(1)字符串形式的属性值: to="/xxxRoute"
(2)对象形式的属性值: to = { {pathname: '/xxxRoute'}, state: {} } 一般用来传递state属性;
补充(路由跳转方式):
若未特殊声明,则路由跳转默认采用push方法,若要修改为其它方式,需在路由链接中指定(如下展示调转为replace方式)
<NavLink replace to={ {pathname: '/route1', state: {name: 'Tom', age: 18}} }>1路由组件</NavLink>
<NavLink replace to="/route2?name=Tom&age=19" children={"2路由组件"}></NavLink>
编程式路由与声明式路由
声明式路由导航是指利用<Link>与<NavLink>完成的路由导航;
编程式路由导航是指利用history对象中的push、replace、go等方法实现功能;或者使用withRouter方法将一般组件变为路由组件(返回一个新的路由组件),这样就可以拿到this.props.history对象;
依然是之前的代码,使用编程式路由如下:
import { Component } from 'react';
import Change1 from './components/change1';
import Change2 from './components/change2';
import { Route, withRouter } from 'react-router-dom'
import './App.css';
class App extends Component {
change = (val) => {
console.log(val);
val === 1 ? this.props.history.push('/change1') : this.props.history.push('/change2')
}
render() {
return (
<div className="App">
<Route path={'/change1'} component={Change1}></Route>
<Route path={'/change2'} component={Change2}></Route>
<button onClick={() => this.change(1)}>切换1</button>
<button onClick={() => this.change(2)}>切换2</button>
</div>
)
}
}
export default withRouter(App) // 将App组件利用withRouter方法变为路由组件,从而使用props中的history对象
history.goBack()表示路由后退,history.goForward()表示路由前进,history.go(n)表示前进(n为正数)或后退(n为负数);
编程式导航传递参数形式如下:
this.props.history.push('/route2/Tom/18') // 传递params参数
this.props.history.push('/route2?name=Tom&age=18') // 传递search参数
this.props.history.push('/route2', {name: 'Tom', age: 19}) // 传递state参数
路由注册方法与声明式路由导航方法保持一样;
利用<Route>组件进行路由嵌套时,path属性需要写上其父路由的path,<NavLink>组件的to属性也要加上其父路由的path;
路由react-router-dom@6
npm install react-router-dom // 默认安装最新版,目前最新版为v6
react-router@6提倡使用函数式组件,接下来的篇幅主要利用函数式组件讲述route v6版本中增加或移除的组件或方法:
补充,函数式组件传参props不包含history/match/location对象
- 移除<Switch>组件,新增<Routes>组件
v6版本新增组件<Routes>,取代组件<Switch>,并且v6中必须要用<Routes>包裹<Route>,
<Route>相当于一个if语句,若路径与当前url匹配,则呈现其对应的组件~
该组件身上有个caseSensitive属性,用以指定匹配路径时是否区分大小写(默认是false)
- 移除<Route>组件中component属性,新增element属性
<Route>组件中的component属性用以表明路由链接跳转匹配的路由组件,v6中使用element属性代替,并且取值要写标签形式;
<NavLink replace to={ {pathname: '/route1', state: {name: 'Tom', age: 18}} }>1路由组件</NavLink>
<NavLink replace to="/route2" children={"2路由组件"}></NavLink> // 路由链接
<Routes>
<Route exact path="/route1" element={<Change1/>}></Route>
<Route exact path="/route2" element={<Change2/>}></Route> // 注册路由
</Routes>
<Router> 也可以写成嵌套路由形式:
<NavLink to="/route2/route3">打开3路由组件</NavLink> // 路由链接
<Route path="/route2" element={<Change2/>}>
<Route path='route3' element= {<Change3 />}></Route> // 路径为/route2/route3
</Route> // 注册嵌套路由
- 移除<Redirect>组件,新增<Navigate>组件
<Navigate>组件用于修改路径,切换视图,默认跳转方式是push模式,可通过replace属性改变;
如匹配到路由‘/’后,会切换到/route1路径下,重新渲染页面;
<Route path='/' element={<Navigate to='/route2' replace></Navigate>}></Route>
- 新增路由表配置
利用hooks中的useRoutes()可以配置路由表规则(根据路由表,动态创建routes以及route),同时需要<Outlet>组件进行占位渲染路由组件,<Outlet>组件功能类似于vue中的<router-view>;也可在路由表中配置多级路由;
路由表通常配置在项目中的react/src/routes/index.js文件中,示例如下:
// 该文件用于配置路由表
import { Navigate } from "react-router-dom";
import Change1 from '../pages/Change1';
import Change2 from '../pages/Change2';
import Change3 from "../pages/Change3";
export default [
{
path: '/route1',
element: <Change1 />
},
{
path: '/route2',
element: <Change2 />,
children: [
{
path: 'route3',
element: <Change3 />
}
]
},
{
path: '/',
element: <Navigate to='/route1'/>,
},
]
将配置好的路由表传入useRoutes()hooks中,就可以生成对应的路由规则;
import routes from './routes' // 引入路由表
const Routes = useRoutes(routes) // 得到路由规则
将原本利用<Routes><Route><Route/><Routes>的结构替换为{Routes},即注册好了路由;
import { NavLink, useRoutes } from 'react-router-dom';
import routes from './routes' // 引入路由表
import './App.css';
export default function App() {
const Routes = useRoutes(routes) // 配置路由规则
return (
<div className="App">
{/* 路由链接 */}
<NavLink replace to="/route1">1路由组件</NavLink>
<NavLink replace to="/route2" children={"2路由组件"}></NavLink>
<h2>下面是路由组件的内容</h2>
{/* 注册路由 */}
{Routes}
</div>
)
}
利用<Outlet>指定子路由组件呈现位置;
// pages/Change2.jsx
import { NavLink, Outlet } from 'react-router-dom'
export default function Change2() {
return (
<div>
第二个路由组件的内容
{/* 路由链接 */}
<NavLink to='/route2/route3'>3路由组件</NavLink>
{/* 利用Outlet指定路由组件展示位置 */}
<Outlet />
</div>
)
}
- 路由组件传递参数
由于函数组件中props无history、location、match对象,需要使用到神通广大的hooks;
① 获取params参数,使用useParams()hooks;
路由链接与注册路由方式与@5中保持一致,
// 路由链接
<NavLink replace to="/route1/Tom/19">1路由组件</NavLink>
// 路由表中注册路由
{
path: '/route1/:name/:age',
element: <Change1 />
},
// pages/Count1.jsx
import { useParams } from "react-router-dom"
export default function Change1() {
const {name, age} = useParams()
return (
<div>
第一个路由组件中的内容
姓名: {name}
年龄: {age}
</div>
)
}
② 获取search参数,使用useSeachParams()hooks;
与useParams()不同的是,使用之后并不能直接通过解构赋值拿到参数,useParams()返回一个包含当前serach参数和更新search参数方法的数组;路由链接与注册路由方式与@5中保持一致。
const [search, setSearch] = useSearchParams()
通过search.get(xxx)拿到对应参数值;
<NavLink replace to="/route1?name=Tom&age=19">1路由组件</NavLink> // 路由链接
{
path: '/route1',
element: <Change1 />
}, // 注册路由
// pages/Change1.jsx
import { useSearchParams } from "react-router-dom"
export default function Change1() {
const [search, getSearch] = useSearchParams() // 获取search参数
const name = search.get('name')
const age = search.get('age')
return (
<div>
第一个路由组件中的内容
姓名: {name}
年龄: {age}
</div>
)
}
③ 获取state参数,使用useLocation()hooks
useLocation()返回如下内容,通过.state可以拿到state参数;
<NavLink replace to="/route1" state={{name: 'Tom', age:19}}>1路由组件</NavLink> // 路由链接
{
path: '/route1',
element: <Change1 />
}, // 注册路由
//pages/Change1.jsx
import { useLocation } from "react-router-dom"
export default function Change1() {
const {name, age} = useLocation().state || {}// 获取state参数
return (
<div>
第一个路由组件中的内容
姓名: {name}
年龄: {age}
</div>
)
}
- 利用useNavigate()实现编程式路由导航
由于没有了history对象,因此使用useNavigate()返回一个函数用于实现编程式路由导航~
有两种方式:
①指定具体路径形式
const navigate = useNavigate()
navigate('xxx路径',
{
replace: false, // 跳转方式
state:{} // 是否传递state参数
})
// 或
navigate('xxx路径')
路径中可传递params参数或search参数,方法与上述相同;
②使用数字跳转路由
navigate(n) // n为数字,大于0表前进,n小于0表示后退
- 新增useMatch()hooks
使用useMatch()来返回当前信息,等同于@5中的match属性;
如Change2的路由匹配是path: '/route2/:name/:age',则在Change2路由组件中打印useMatch()可得到如下:
const m = useMatch('/route2/:name/:age')
console.log(m);
也可以拿到params参数,但是不如直接使用useParams香~
- useInRouterContext()
该hooks用于:若某组件在<Router>的上下文中呈现,则useInRouterContext()返回true,反之为false,即某组件是否被BrowserRouter或者HashRouter所包裹,如果被包裹则返回true,反之为false;
如下示例中所有被App组件下的组件都返回true;Demo组件中返回false;
// index.js
root.render(
<div>
<BrowserRouter>
<App />
</BrowserRouter>
<Demo />
</div>
);
- useNavigateType()
用于返回当前的导航类型(POP、PUSH、REPLACE三种),其中pop类型表示当前页面是通过刷新页面得到的;
import { useInRouterContext } from 'react-router-dom'
console.log(useNavigationType(), 'useNavigationType'); // PUSH
- useOutlet()
该hooks用于呈现当前组件中要渲染的嵌套路由,如嵌套路由没有挂载,则结果为null;
import React from 'react'
import { NavLink, Outlet, useOutlet } from 'react-router-dom'
export default function Change1() {
console.log(useOutlet()); // 可以输出<Outlet/>占位容器内所存放的组件对象
return (
<div>
Change1
<NavLink to="/change1/change3">跳转3路由</NavLink>
<Outlet/>
</div>
)
}
- useResolvedPath()
该hook可以解析url中的path、search以及hash参数:
import { useResolvedPath } from 'react-router-dom'
console.log(useResolvedPath('https://reactjs.org/docs/add-react-to-a-website.html?age=19&par=Team#add-react-in-one-minute'));