React教程详解二(脚手架、路由)

一起快乐学习哦~ 

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>&nbsp;&nbsp;
        <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>&nbsp;&nbsp;
        <NavLink replace to="/route2" children={"2路由组件"}></NavLink>&nbsp;&nbsp;
        <h2>下面是路由组件的内容</h2>
        {/* 注册路由 */}
        {Routes} 
      </div>
    )
}

利用<Outlet>指定子路由组件呈现位置;

// pages/Change2.jsx
import { NavLink, Outlet } from 'react-router-dom'

export default function Change2() {
    return (
      <div>
        第二个路由组件的内容&nbsp;
        {/* 路由链接 */}
        <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')); 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷糊的小小淘

整理不易,赏点动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值