React三原理和路由

news2025/4/24 12:32:49

代码下载

React 组件通讯原理

setState() 说明

setState() 是异步更新数据的,使用该语法时,后面的 setState() 不要依赖于前面的 setState(),可以多次调用 setState() ,只会触发一次重新渲染:

    this.setState({ count: this.state.count + 1 })
    console.log('count: ', this.state.count);
    this.setState({ count: this.state.count + 1 })
    console.log('count: ', this.state.count);

推荐使用 setState((state, props) => {}) 语法,参数state表示最新的state,参数props表示最新的props:

    this.setState((state, props) => {
      console.log('第一次 count: ', state.count);
      return { count: state.count + 1 }
    })
    this.setState((state, props) => {
      console.log('第二次 count: ', state.count);
      return { count: state.count + 1 }
    })
    console.log('count: ', this.state.count);

第二个参数在状态更新(页面完成重新渲染)后立即执行某个操作,语法 setState(updater[, callback])

    this.setState((state, props) => {
      return { count: state.count + 1 }
    }, () => {
      console.log('count: ', this.state.count);
    })

JSX 语法的转化过程

JSX 仅仅是 createElement() 方法的语法糖(简化语法),JSX 语法被 @babel/preset-react 插件编译为 createElement() 方法。React 元素是一个对象,用来描述你希望在屏幕上看到的内容:

组件更新机制

setState() 的两个作用: 1. 修改 state 2. 更新组件(UI)

过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)

// 组件更新机制
class UpdateCom extends React.Component {
  state = { color: 'skyblue'}
  render() {
    console.log('根组件 render');
    return (
      <div className='root' style={{ backgroundColor: this.state.color}}>
        <p>根组件</p>
        <button onClick={this.changeColor}>变色</button>
        <div className='rootContainer'>
          <LeftCom></LeftCom>
          <RightCom></RightCom>
        </div>
      </div>
    )
  }
  getColor = () => Math.floor(Math.random() * 256)
  changeColor = () => {
    this.setState({ color: `rgb(${this.getColor()}, ${this.getColor()}, ${this.getColor()})` })
  }
}
class LeftCom extends React.Component {
  state = { count: 0 }
  render() {
    console.log('左父组件 render');
    return (
      <div className='leftParent'>
        <p>左父组件 count: {this.state.count}</p>
        <button onClick={() => this.setState({count: this.state.count + 1})}>+1</button>
        <div className='leftParentContainer'>
          <LeftChildOne></LeftChildOne>
          <LeftChildTwo></LeftChildTwo>
        </div>
      </div>
    )
  }
}
class RightCom extends React.Component {
  state = { count: 0 }
  render() {
    console.log('右父组件 render');
    return (
      <div className='rightParent'>
        <p>右父组件 count: {this.state.count}</p>
        <button onClick={() => { this.setState({count: this.state.count + 1}) }}>+1</button>
        <div className='rightParentContainer'>
          <RightChildOne></RightChildOne>
          <RightChildTwo></RightChildTwo>
        </div>
      </div>
    )
  }
}
const LeftChildOne = () => {
  console.log('左子组件1 render');
   return (<div className='leftChildOne'>左子组件1</div>)
  }
const LeftChildTwo = () => { 
  console.log('左子组件2 render');
  return (<div className='leftChildTwo'>左子组件2</div>)
}
const RightChildOne = () => { 
  console.log('右子组件1 render');
  return (<div className='rightChildOne'>右子组件1</div>)
}
const RightChildTwo = () => {
  console.log('右子组件2 render');
  return (<div className='rightChildTwo'>右子组件2</div>)
}
ReactDOM.createRoot(document.getElementById('updateCom')).render(<UpdateCom></UpdateCom>)

css:

  width: 800px;
}
.rootContainer, .leftParentContainer, .rightParentContainer {
  display: flex;
}
.leftParent {
  flex: 1;
  background-color: green;
}
.leftParentContainer > div {
  flex: 1;
  background-color: blue;
  margin: 10px;
}
.rightParent {
  flex: 1;
  background-color: cyan;
}
.rightParentContainer > div {
  flex: 1;
  background-color: pink;
  margin: 10px;
}

组件性能优化

1、 减轻 state,state 只存储跟组件渲染相关的数据(比如:count / 列表数据 / loading 等)

注意:不用做渲染的数据不要放在 state 中,比如定时器 id等,对于这种需要在多个方法中用到的数据,应该放在 this 中。

2、避免不必要的重新渲染,父组件更新会引起子组件也被更新,这种思路很清晰

问题:子组件没有任何变化时也会重新渲染?如何避免不必要的重新渲染呢?

解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState),通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染。更新阶段的钩子函数,组件重新渲染前执行 (shouldComponentUpdate -> render)。

class NumCom extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    console.log('props: ', this.props, 'nextProps: ', nextProps);
    // 根据 props 数据是否变化,决定渲染
    return this.props.num !== nextProps.num
  }
  render() {
    console.log('NumCom render');
    return (<><p>随机数:{this.props.num}</p></>)
  }
}
class RefreshCom extends React.Component {
  state = { num: 1 }
  shouldComponentUpdate(nextProps, nextState) {
    console.log('state: ', this.state, 'nextState: ', nextState);
    // 根据 state 数据是否变化,决定渲染
    return this.state.num !== nextState.num
  }
  render() {
    console.log('RefreshCom render');
    return (
      <>
        <h4>避免不必要的重新渲染</h4>
        <NumCom num={this.state.num}></NumCom>
        <button onClick={() => this.setState({ num: Math.ceil(Math.random()*3)})}>生成随机数</button>
      </>
    )
  }
}

3、纯组件,PureComponent 与 React.Component 功能相似,PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较

原理:纯组件内部通过分别 对比 前后两次 props 和 state 的值,来决定是否重新渲染组件。纯组件内部的对比是 shallow compare(浅层对比),对于值类型来说比较两个值是否相同(直接赋值即可,没有坑);对于引用类型来说只比较对象的引用(地址)是否相同。注意 state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据!

class PureNum extends React.PureComponent {
  render() {
    console.log('PureNum render');
    return (<><p>随机数:{this.props.num}</p></>)
  }
}
class PureObjNum extends React.PureComponent {
  render() {
    console.log('PureObjNum render');
    return (<><p>随机数:{this.props.numObj.num}</p></>)
  }
}
class PureCom extends React.PureComponent {
  state = {
    num: 1,
    numObj: { num: 1 }
  }
  render() {
    console.log('PureCom render');
    return (
      <>
        <h4>纯组件</h4>
        <PureNum num={this.state.num}></PureNum>
        <button onClick={this.handle}>生成随机数</button>
        <PureObjNum numObj={this.state.numObj}></PureObjNum>
        <button onClick={this.handleOne}>生成随机数对象(修改原对象)</button> <br></br>
        <button onClick={this.handleTwo}>生成随机数对象(创建新对象)</button>
      </>
    )
  }
  handle = () => {
    const number = Math.ceil(Math.random()*3)
    console.log('num: ', this.state.num, ', new num: ', number);
    this.setState({ num: number })
  }
  handleOne = () => {
    // 修改原对象,错误,因为不会刷新
    const number = Math.ceil(Math.random()*3)
    console.log('num: ', this.state.numObj.num, ', new num: ', number);
    const obj = this.state.numObj
    obj.num = number
    this.setState({ numObj: obj})
  }
  handleTwo = () => {
    // 创建新对象,正确,但是也得判断,否则每次都会刷新
    const number = Math.ceil(Math.random()*3)
    console.log('num: ', this.state.numObj.num, ', new num: ', number);
    if (number !== this.state.numObj.num) {
      const obj = {...this.state.numObj, num: number}
      this.setState({ numObj: obj})
    }
  }
}

虚拟DOM和Diff算法

React 更新视图的思想是:只要 state 变化就重新渲染视图,特点就是思路非常清晰。

问题:组件中只有一个 DOM 元素需要更新时,也得把整个组件的内容重新渲染到页面中?答案是否定的。理想状态是部分更新,只更新变化的地方。那么 React 是如何做到部分更新的?虚拟 DOM 配合 Diff 算法。

虚拟 DOM 本质上就是一个 JS 对象,用来描述你希望在屏幕上看到的内容(UI):

执行过程

  1. 初次渲染时,React 会根据初始state(Model),创建一个虚拟 DOM 对象(树)。
  2. 根据虚拟 DOM 生成真正的 DOM,渲染到页面中。
  3. 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)。
  4. 与上一次得到的虚拟 DOM 对象,使用 Diff 算法 对比(找不同),得到需要更新的内容。
  5. 最终,React 只将变化的内容更新(patch)到 DOM 中,重新渲染到页面。

虚拟DOM最大的特点是 脱离了浏览器的束缚,也就是意味着只要是能支持js的地方都可以用到react,所以为什么说react是可以进行跨平台的开发。

render 方法调用并不意味着浏览器中的重新渲染,render 方法调用仅仅说明创建了新的虚拟 DOM 对象(树)要进行diff 算法。

React 路由

现代的前端应用大多都是 SPA(单页应用程序),也就是只有一个 HTML 页面的应用程序。因为它的用户体
验更好、对服务器的压力更小,所以更受欢迎。为了有效的使用单个页面来管理原来多页面的功能,前端路由
应运而生。

  • 前端路由的功能是让用户从一个视图(页面)导航到另一个视图(页面)
  • 前端路由是一套映射规则,在React中,是 URL路径 与 组件 的对应关系
  • 使用React路由简单来说,就是配置 路径和组件(配对)

使用步骤

1、安装,在项目根目录执行 npm i react-router-domyarn add react-router-dom

2、导入路由的5个核心组件:Router、Routes、Route、Link 或 NavLink

import { BrowserRouter as Router, Routes, Route, Link, NavLink } from 'react-router-dom'

3、使用 Router 组件包裹整个应用(重要)

4、路由的本质就是映射关系,用 Routes(就是router5里的Switch组件) 组件去盛放这层路由映射关系

5、使用 Link 或 NavLink 组件作为导航菜单(路由入口),这两个组件主要包含两个属性:to属性和replace属性。

  • to属性: 用来设置跳转到哪个路径,相当于是push操作;
  • replace属性:和to类似,也会跳转到目标路径,但其执行的是replace操作,设置了replace属性会把路由栈里当前路由替换掉

6、使用 Route 组件配置路由规则和要展示的组件(路由出口),注意 Route 组件必须包裹在 Routes 组件内

  • path属性:用于设置匹配到的路径
  • element属性:设置匹配到路径后要渲染的组件;(在Router5里是使用component属性)
// 使用步骤
const Home = () => (<p>首页</p>)
const First = () => (<p>页面一</p>)
const Second = () => (<p>页面二</p>)
const NotFound = () => (<p>没有发现该内容</p>)

const RouterStep = () => {
  return (
    <>
      <Router>
        <p>Link</p>
        <Link to='/home'>首页</Link>
        <Link to='/first'>页面一</Link> 
        <Link to='/second' replace={true}>页面二</Link>
        <Link to='/xxx'>NotFound</Link>
        <p>NavLink</p>
        <NavLink to='/first' style={({isActive}) => { return isActive ? { color: 'blue' } : {} }}>页面一</NavLink>
        <NavLink to='/second' className={({isActive}) => { return isActive ? 'selected smoll' : ''}}>页面二</NavLink>
        <Routes>
          <Route path='/home' element={<Home></Home>}></Route>
          <Route path='/first' element={<First></First>}></Route>
          <Route path='/second' element={<Second></Second>}></Route>
          <Route path='*' element={<NotFound></NotFound>}></Route>
        </Routes>
      </Router>
    </>
  )
}

说明:
1、Router 组件包裹整个应用,一个 React 应用只需要使用一次

2、两种常用 Router:HashRouter 和 BrowserRouter

  • HashRouter:使用 URL 的哈希值实现(localhost:3000/#/first)
  • (推荐)BrowserRouter:使用 H5 的 history API 实现(localhost:3000/first)

3、Link 或 NavLink 组件:用于指定导航链接(a标签)。最终Link会编译成a标签,而to属性会被编译成 a标签的href属性。

4、Route 组件指定路由展示组件相关信息,Route组件写在哪,渲染出来的组件就展示在哪

  • path属性:路由规则
  • element属性:展示的 React 元素

5、NavLink就是Link组件的样式增强版,它与Link的用法基本相同,只不过就是多了几个属性进行设置样式,并且当前选中的NavLink组件上会多出一个active类名

  • style 属性:接收一个函数,函数接收一个对象,包含isActive属性,表示当前是否被选中;
  • className 属性:接收一个函数,函数接收一个对象,包含isActive属性,表示当前是否被选中

6、通配符,在router6中,支持如下的几种通配符:

  • /xxx 确定的路径名,如 : /home 表示home页面组件能匹配上路径只能是 /home ;
  • /xxx/:xxx 动态路径名,:xxx会动态变化的 。如:/home/:id 表示home页面能匹配上 /home/11、/home/22、/home/abc、/home/xxx 等路径;
  • /xxx/:xxx/xxx动态路径名后跟确定路径,如: /home/:id/abc 表示home页面能匹配上 /home/11/abc、/home/22/abc 、/home/aa/abc 等路径;
  • /xxx/* 确定路径名,后面可以跟多个子路径,如:/home/* 表示home页面能匹配上 /home、/home/12、/home/ab、/home/cd/123 等路径;
  • /xxx/:xxx/* 动态路径名后不限制子路径,如:/home/:id/* 表示home页面匹配 /home/11/abc/bcd、 /home/22/qwe 等路径;

默认路由

现在的路由都是点击导航菜单后展示的,如何在进入页面的时候就展示呢?

默认路由表示进入页面时就会匹配的路由,默认路由path为 /

{/* 默认路由 */}
<Route path='/' element={<home></home>}></Route>

路由重定向

使用Navigate 组件来实现路由的重定向,只要这个组件出现,就会执行重定向跳转,并跳到其对应的to路径中,导入组件:

import { Navigate } from 'react-router-dom';

使用Navigate也很简单,只要将想要被重定向的Route匹配关系中将element属性里的原组件替换成Navigate组件即可;至于想要被重定向到哪个路径,只需要将这个路径写入到Navigate组件中的to属性即可:

<Route path='/' element={<Navigate to='/home' replace></Navigate>}></Route>

路由的执行过程

  • 点击 Link 或 NavLink 组件(a标签),修改了浏览器地址栏中的 url 。
  • React 路由监听到地址栏 url 的变化。
  • React 路由内部遍历所有 Route 组件,使用路由规则( path )与 pathname 进行匹配。
  • 当路由规则(path)能够匹配地址栏中的 pathname 时,就展示该 Route 组件的内容。

匹配模式

  • 精确匹配:只有当 path 和 pathname 完全匹配时才会展示该路由
  • 模糊匹配:只要 pathname 以 path 开头就会匹配成功

在router6中,不必再特别去设置exact属性去进行精确匹配组件了,因为router6中已经帮内置进去了。

如果想模糊匹配某一部分,在路径后加 /*,增加一个匹配规则,将默认路由设置为模糊匹配:

        <Routes>
          <Route path='/*' element={<Home></Home>}></Route>
        </Routes>

路由懒加载

在路由中通常会定义很多不同的页面。如果不应用懒加载的话,很多页面都会打包到同一个js文件中,文件将会异常的大。造成进入首页时,需要加载的内容过多,时间过长,在浏览器中可能会出现短暂的空白页,从而降低用户体验,而运用路由懒加载是将各个模块分开打包,用户查看的时候再加载对应的模块,减少加载用时。

懒加载就是延迟加载,也即在需要的时候才会进行加载所需组件。

在react中借助React.lazy方法来进行懒加载,用法如下 React.lazy(() => import("懒加载组件的路径"))

1、定义一个单文件组件:

import React from "react";

export default class LazyOne extends React.Component {
    render() {
        return (
            <>
                <p>第一个懒加载组件</p>
            </>
        )
    }
}

2、采用lazy懒加载的形式加载组件

  const LazyOne = React.lazy(() => import('./LazyOne'))

3、增加路由匹配规则

<Route path='/lazyOne' element={<LazyOne></LazyOne>}></Route>

4、导航懒加载组件路由

<Link to='/lazyOne'>懒加载一</Link>

5、此时点击路径跳转,控制台会报错,这是因为当我们是用lazy去懒加载时,应该同时引入Suspense去包裹懒加载的组件。也就是说,lazy处理的懒加载组件一定要处于Suspense组件包裹下。

      <React.Suspense>
        <Router>
          <Link to='/lazyOne'>懒加载一</Link>
          <Routes>
            <Route path='/lazyOne' element={<LazyOne></LazyOne>}></Route>
            <Route path='*' element={<NotFound></NotFound>}></Route>
          </Routes>
        </Router>
      </React.Suspense>

引入Suspense之后,再去点击跳转控制台就不会再报错了。

6、当网络不好的时候进行页面跳转,会出现长时间的白屏现象。可以利用Suspense组件的fallback属性来进行改善用户体感,将fallback的值设置为antd的Spin组件:

      <React.Suspense fallback={<Spin></Spin>}>
        <Router>
          <Link to='/lazyOne'>懒加载一</Link>
          <Routes>
            <Route path='/lazyOne' element={<LazyOne></LazyOne>}></Route>
            <Route path='*' element={<NotFound></NotFound>}></Route>
          </Routes>
        </Router>
      </React.Suspense>

fallback是Suspense组件的属性,其作用是当lazy处理的懒加载组件还没有加载出来时要显示的内容。一般地都会往fallback属性里传入一个loading动画,用来缓解加载白屏的问题。

当组件处于加载状态时,fallback组件是替换了整个 Suspense 组件包裹的内容,此时只显示了Spin组件。但实际上在路由切换的时候是不应该全部被 Suspense 的 fallback 替代的,因为路由切换时有的部分是不变的。造成此部分也被替换的原因是,Suspense 组件包裹的范围太广了。按照正常逻辑只要动态加载的那部分被替换,其余不变的部分就仍然展示。对此需要缩小 Suspense 组件的包裹范围,只让它包裹住懒加载的组件即可。这时候可以使用 高阶组件 或 render-props来配合处理懒加载组件:

// 高阶组件
const withLoading = (WrapedComponent) => {
  class LoadingCom extends React.Component {
    render() {
      return (<React.Suspense fallback={<Spin></Spin>}><WrapedComponent {...this.props}></WrapedComponent></React.Suspense>)
    }
  }
  LoadingCom.displayName = `WithLoading${getDisplayName(WrapedComponent)}`
  return LoadingCom
}

// render-props 
function FuncLoading(props) {
  return (
    <React.Suspense fallback={<Spin></Spin>}>
      {props.children}
    </React.Suspense>
  )
}

const RouteLazy = () => {
  const LazyOne = React.lazy(() => import('./LazyOne'))
  const LazyTwo = React.lazy(() => import('./LazyTwo'))
  const LazyThree = React.lazy(() => import('./LazyThree'))
  const LoadingOne = withLoading(LazyTwo)
  return (
    <>
      <React.Suspense fallback={<Spin></Spin>}>
        <Router>
          <div>
            <Link to='/lazyOne'>懒加载一</Link>
            <Link to='/loadingOne'>懒加载二优化</Link>
            <Link to='/loadingTwo'>懒加载三优化</Link>
          </div>
          <Routes>
            <Route path='/lazyOne' element={<LazyOne></LazyOne>}></Route>
            <Route path='/loadingOne' element={<LoadingOne></LoadingOne>}></Route>
            <Route path='/loadingTwo' element={<FuncLoading><LazyThree></LazyThree></FuncLoading>}></Route>
            <Route path='*' element={<NotFound></NotFound>}></Route>
          </Routes>
        </Router>
      </React.Suspense>
    </>
  )
}
ReactDOM.createRoot(document.getElementById('routeLazy')).render(<RouteLazy></RouteLazy>)

其中 LazyTwo、LazyThree 组件与前面的 LazyOne 类似,略。

路由嵌套

在Router5中是将嵌套的子路由直接写在父路由对应的组件内部的。这样有个弊端就是Route太分散了,各个组件里都有Route。Router6.X对此做了一个调整,现在不用分散嵌套子路由,取而代之的是路由嵌套统一在一个 Routes 组件下维护。

想要子路由生效,必须还得在其所属的父路由里引入一个Outlet组件进行路由占位。想要让子路由在哪里展示,就把Outlet放到其内部即可:

import { BrowserRouter as Router, Routes, Route, Link, NavLink, Outlet } from 'react-router-dom'

const ContentOne = () => { return (<p>内容一</p>) }
const ContentTwo = () => { return (<p>内容二</p>) }
const ContentThree = () => { return (<p>内容三</p>) }
const NoContent = () => { return (<p>没有内容</p>) }
// 主体结构组件
const MainBody = () => {
  return (
    <div className='mainBody'>
      {/* 头部区域 */}
      <div className='top'>
        路由嵌套
        <Consumer>
          {(data) => <button onClick={data.logout}>退出</button>}
        </Consumer>
      </div>

      {/* 左边栏区域 */}
      <div className='leftAside'>
        左边栏
        <ul>
          <li><Link to='/'>内容一</Link></li>
          <li><Link to='/two'>内容二</Link></li>
          <li><Link to='/three'>内容三</Link></li>
        </ul>
      </div>
      
      {/* 内容区域 */}
      <div className='content'>
        内容
        <Outlet></Outlet>
      </div>

      {/* 底部区域 */}
      <div className='bottom'>底部区域</div>
    </div>
  )
}
// 登录组件
const Login = () => {
  return (
    <Consumer>
      {(data) => <button onClick={data.login}>登录</button>}
    </Consumer>
  )
}
class RouteNest extends React.Component {
  state = { isLogin: false }
  render() {
    const main = <Provider value={{ logout: () => this.setState({ isLogin: false }) }}><MainBody></MainBody></Provider>
    const login = <Provider value={{ login: () => this.setState({ isLogin: true }) }}><Login></Login></Provider>
    const first = (<>{this.state.isLogin ? main : login}</>)
    return (
      <Router>
        <Routes>
          {/* 父路由(默认) */}
          <Route path='/' element={first}>
            {/* 子路由 */}
            {/* 默认子路由 */}
            {/* <Route path='/' element={<ContentOne></ContentOne>}></Route> */}
            <Route index element={<ContentOne></ContentOne>}></Route>
            <Route path='/two' element={<ContentTwo></ContentTwo>}></Route>
            <Route path='/three' element={<ContentThree></ContentThree>}></Route>
          </Route>
          <Route path='/login' element={<Login></Login>}></Route>
          <Route path='*' element={<NotFound></NotFound>}></Route>
        </Routes>
      </Router>
    )
  }
}
ReactDOM.createRoot(document.getElementById('routeNest')).render(<RouteNest></RouteNest>)

如何设置默认显示的子路由?只需要在想要被设为默认展示的子路由上写一个 index属性即可(注意,被设置index的子路由不能再设置path属性),那么当访问 ‘/’ 就能做到默认展示子路由了,跳转路径也的相应调整为 <Link to='/'>内容一</Link>。也可以直接将默认子路由设置为 <Route path='/' element={<ContentOne></ContentOne>}></Route>

注意:路由嵌套时,子路由的path路径要以父路由的path名开头,否则会报错。

编程式导航

如果希望通过JS代码跳转,需要通过useNavigate获取到navigate对象对象然后进行后续操作。

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

使用类组件开发则需要封装高阶函数,也即手动封装withRouter:

function withRouter(WrapedComponent) {
  function ComponentWithRouter(props) {
    const navigate = useNavigate()
    return <WrapedComponent {...props} router={navigate}></WrapedComponent>
  }
  ComponentWithRouter.displayName = `WithRouter${getDisplayName(WrapedComponent)}`

  return ComponentWithRouter
}

可以直接调用 useNavigate 函数去返回一个navigate,然后调用navigate并将跳转路径作为参数传进去,例如:navigate(‘url路径’),当的navigate传入数字时(一般是-1),是跳到路由栈当前路由前面几个对应的路由。 navigate里也可以传第二个options参数: {replace,state} 。replace参数代表替换当前路由栈中的路由,state代表路由传参:

function Red() { return (<div style={{ backgroundColor: 'red', width: '100px', height: '100px' }}></div>) }
function Green() { return (<div style={{ backgroundColor: 'green', width: '100px', height: '100px' }}></div>) }
// 类组件
class ClassComponent extends React.Component {
  render() { return (<>
    <p>类组件</p>
    <button onClick={() => this.props.router('/red')}>红</button>
    <button onClick={() => this.props.router('/green', { replace: true, state: { data: '数据' }})}>绿</button>
    <button onClick={() => this.props.router(-1)}>返回</button>
  </>) }
}
// 函数组件
function FuncComponent() {
  const navigate = useNavigate()
  return (<>
    <p>函数组件</p>
    <button onClick={() => navigate('/red')}>红</button>
    <button onClick={() => navigate('/green', { replace: true, state: { data: '数据' }})}>绿</button>
    <button onClick={() => navigate(-1)}>返回</button>
  </>)
}
function FuncRoute(props) {
  const WithRouterCom = withRouter(ClassComponent)
  return (
    <Router>
      <WithRouterCom></WithRouterCom>
      <FuncComponent></FuncComponent>
      <Routes>
        <Route path='/red' element={<Red></Red>}></Route>
        <Route path='/green' element={<Green></Green>}></Route>
        <Route path='*' element={<NotFound></NotFound>}></Route>
      </Routes>
    </Router>
  )
}
ReactDOM.createRoot(document.getElementById('funcRoute')).render(<FuncRoute></FuncRoute>)

路径如果以 / 开头代表 根路由+当前路径拼接成新路由; 不以 / 开头表示在当前路由后再跟上路径拼接成新路由路径。

navigate("/register") // 跳转后路径为 http://localhost:3000/register
navigate("register") // 跳转后路径为 http://localhost:3000/home/register

路由传参

路由传参一般用到三种方式:动态路径传参、search参数传递、state传递参数。

动态路径传参

如果将path在Route匹配时写成 /detail/:id 那么 /detail/abc/detail/123 都可以匹配到该Route对应的组件并进行显示,针对动态路径传参的这种方式,常用 useParams 来进行获取参数。

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

function Detail() { 
  const params = useParams()
  let value = ''
  switch (params.id) {
    case '1':
      value = '一'
      break;
    case '2':
      value = '二'
      break
    default:
      break;
  }
  return (<p>详情{value}</p>) 
}

function ParamsCom() {
  const navigate = useNavigate()
  return (<>
    <p>动态路径传参</p>
    <Link to='/detail/1'>详情一</Link>
    <button onClick={() => navigate('/detail/2')}>去详情二</button>
  </>)
}

function RouteParams() {
  return (<Router>
    <ParamsCom></ParamsCom>
    <Routes>
      <Route path='/detail/:id' element={<Detail></Detail>}></Route>
      <Route path='*' element={<NotFound></NotFound>}></Route>
    </Routes>
  </Router>)
}
ReactDOM.createRoot(document.getElementById('routeParams')).render(<RouteParams></RouteParams>)

说明:动态路径可以配置多个参数,如 <Route path='/detail/:id/:lastId' element={<Detail></Detail>}></Route>

search传参

seatch 传递参数就是把我们要传递的参数拼接到url上进行传递,具体方式是以 ? 开头,键值对的形式传参,每个参数之间用 & 连接,如 http://localhost:3000/list?page=1&size=10

一般使用 useSearchParams 这个函数来获取 search 参数(也可用useLocation,不过得额外再处理下search参数),注意 useSearchParams 返回的是个数组,且数组里是一个 URLSearchParams 当前值和 set 方法。并且取值时常借助 Object.fromEntries 这个方法:

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

function User() {
  const [searchParams] = useSearchParams()
  const params = Object.fromEntries(searchParams)
  return (<p>id: {params.id}, name: {params.name}, age: {params.age}</p>)
}
function ParamsCom() {
  const navigate = useNavigate()
  return (<>
    <p>search传参</p>
    <Link to='/user?id=1&name=zhangsan&age=18'>用户一</Link>
    <button onClick={() => navigate('/user?id=2&name=lisi&age=19')}>去用户二</button>
  </>)
}
function RouteParams() {
  return (<Router>
    <ParamsCom></ParamsCom>
    <Routes>
      <Route path='/user' element={<User></User>}></Route>
      <Route path='*' element={<NotFound></NotFound>}></Route>
    </Routes>
  </Router>)
}
ReactDOM.createRoot(document.getElementById('routeParams')).render(<RouteParams></RouteParams>)

Object.entries() 是将对象转成一个自身可枚举属性的键值对数组,同样也可以把键值对数组转成了对象。Object.fromEntriesObject.entries 反向,Object.fromEntries() 方法把键值对列表转换为一个对象。

state传参

上面两种方式的传参都有一个缺点即传递的参数都在url路径上体现了,并且涉及到复杂类型的参数传递就显得很麻烦了。如果不想参数在url上面,且想传些对象时就可以采取这种state传参的方式。使用useLocation钩子来获取state参数。

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

function Content() {
  const { state } = useLocation()
  console.log('state: ', state);
  return (<p>{state.content}</p>)
}
function ParamsCom() {
  const navigate = useNavigate()
  return (<>
    <p>state传参</p>
    <Link to='/content' state={{content: '这是内容一的内容'}}>内容一</Link>
    <button onClick={() => navigate('/content', {state: { content: '这是内容二的内容' } })}>去内容二</button>
  </>)
}
function RouteParams() {
  return (<Router>
    <ParamsCom></ParamsCom>
    <Routes>
      <Route path='/content' element={<Content></Content>}></Route>
      <Route path='*' element={<NotFound></NotFound>}></Route>
    </Routes>
  </Router>)
}
ReactDOM.createRoot(document.getElementById('routeParams')).render(<RouteParams></RouteParams>)

useRoutes 配置化路由

前面都是用Route组件的方式来实现路由配置的,但现在都提倡配置化,能不能把routes相关的配置都维护到一串数据里,不用以组件-属性这种方式管理呢?

在 router5 中需引入的 react-router-config 这个三方库来帮维护。但在 Router6 官方给提供了 useRoutes 这个函数。凭此可以将 routes 对象以参数形式传入 useRoutes 中,此函数会根据 routes 里的匹配关系对应渲染成相关的路由组件。

routes其本身是一个数组,数组里维护各个匹配关系的对象。匹配关系这个对象里一般都有path、element、children、index属性:

  • path属性就是组件对应的路径;
  • element属性就是要对应的组件;
  • index属性就是默认要展示的页面组件;
  • children属性是路由嵌套时需要用也是一个数组,children数组里的属性和外层一样,子路由的配置就是在children属性里维护的。

useRoutes的使用只要将路由配置对象 routes 传到 useRoutes 函数即可:

// 军事新闻组件
const Junshi = () => { return (<p>军事新闻</p>) }
// 财经新闻组件
const Caijin = () => { return (<p>财经新闻</p>) }
// 体验新闻组件
const Tiyu = () => { return (<p>体育新闻</p>) }
// 布局组件
const Layout = () => {
  return (
    <div className='mainBody'>
      {/* 头部区域 */}
      <div className='top'>useRoutes 配置化路由</div>

      {/* 左边栏区域 */}
      <div className='leftAside'>
        左边栏
        <ul>
          <li><Link to='/'>军事</Link></li>
          <li><Link to='/caijin'>财经</Link></li>
          <li><Link to='/tiyu'>体育</Link></li>
        </ul>
      </div>

      {/* 内容区域 */}
      <div className='content'>
        内容
        <Outlet></Outlet>
      </div>

      {/* 底部区域 */}
      <div className='bottom'>底部区域</div>
    </div>
  )
}

// 路由配置对象
const routes = [
  {
    path: '/',
    element: <Layout></Layout>,
    children: [
      {
        index: true,
        element: <Junshi></Junshi>
      },
      {
        path: '/caijin',
        element: <Caijin></Caijin>
      },
      {
        path: '/tiyu',
        element: <Tiyu></Tiyu>
      }
    ]
  },
  {
    path: '*',
    element: <NotFound></NotFound>
  }
]
function RouteCom() {
  console.log('RouteCom render');
  
  // useRoutes 配置化路由
  return (<>{useRoutes(routes)}</>)
}
function ConfigRoute() {
  return (<Router><RouteCom></RouteCom></Router>)
}
ReactDOM.createRoot(document.getElementById('configRoute')).render(<ConfigRoute></ConfigRoute>)
useRoutes 的 rerender 问题

可以发现 useRoutes 配置化路由只要切换路由 RouteCom 组件就会重新渲染,而换成如下方式则不会重新渲染:

function RouteCom() {
  console.log('RouteCom render');

  return <Routes>
    <Route path='/' element={<Layout></Layout>}>
      <Route index element={<Junshi></Junshi>}></Route>
      <Route path='/caijin' element={<Caijin></Caijin>}></Route>
      <Route path='/tiyu' element={<Tiyu></Tiyu>}></Route>
    </Route>
    <Route path='*' element={<NotFound></NotFound>}></Route>
  </Routes>
}

由于 useRoutes 是通过 context 实现的,切换路由时 context 共享出来的 value 值发生了变化,从而使得使用到这个 context 的组件触发了 rerender,这也就造成了使用 useRoutes 的 RouteCom 组件在切换路由时重新渲染了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1968740.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CPQ报价管理系统 | 成本报价CPQ解决方案

一、成本报价流程现状 1、传统流程 2、业务痛点 ①、数据手工重复输入环节多、易错&#xff0c;为保障准确性需多次复核&#xff0c;影响报价效率 ②、原材波动较大&#xff0c;但是当前询价流程只有一次性&#xff0c;原材成本发生变化&#xff0c;无法及时更新变化提醒报价…

类和对象(作业篇)

简简单单整理一下咱们的小作业&#xff0c;这次的作业比较简单&#xff0c;只有选择题&#xff1a; public class Test{private float f1.0f;int m12;static int n1;public static void main(String args[]){Test tnew Test();} }A&#xff1a;抛开private不说&#xff0c;先看…

解析顺序表【数据结构】

1.线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有线序列。线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表有&#xff1a;顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构&#xff0c;也就是说是连续的一条线…

HTML 字符集详解及示例

文章目录 摘要引言从ASCII到UTF-8的演变ASCII 字符集ANSI字符集ISO-8859-1字符集UTF-8字符集 示例代码运行Demo小结表格总结未来展望参考资料 摘要 本文介绍了HTML中的字符集演变历史&#xff0c;从最初的ASCII到现代的UTF-8&#xff0c;并提供了设置字符集的示例代码。文中涵…

图形编辑器基于Paper.js教程10:导入导出svg,导入导出json数据

深入了解Paper.js&#xff1a;实现SVG和JSON的导入导出功能 Paper.js是一款强大的矢量绘图JavaScript库&#xff0c;非常适合用于复杂的图形处理和交互式网页应用。本文将详细介绍如何在Paper.js项目中实现SVG和JSON格式的导入导出功能&#xff0c;这对于开发动态图形编辑器等…

git reset --soft(回退commit,保留add)

参考博客&#xff1a;git reset --soft命令的使用-CSDN博客感觉博客中举的例子不是很好。读者自行判断。举的例子的场景适合使用revert&#xff0c;撤销就行了。另外建议看下边这篇博客&#xff0c;这篇详细介绍了reset和revert&#xff0c;带图。但是要注意这个reset是hard的&…

mysql 内存一直增长(memory/sql/thd::main_mem_root)

mysql版本 8.0.14 发现过程 查询总内存 SELECT t.EVENT_NAME, t.CURRENT_NUMBER_OF_BYTES_USED FROM performance_schema.memory_summary_global_by_event_name t ORDER BY t.CURRENT_NUMBER_OF_BYTES_USED DESC;前&#xff1a; memory/sql/thd::main_mem_root 1…

第十五天啦 2024.8.1 (Spring框架)

1.从宏观上看spring框架和springboot Spring框架解决了企业级的开发的复杂性&#xff0c;它是一个容器框架&#xff0c;用于装java对象&#xff08;Bean&#xff09;&#xff0c;使程序间的依赖关系交由容器统一管理&#xff0c;松耦合&#xff0c;提高了可测试性和维护效率&a…

网络原理的TCP/IP

TCP/IP协议 1)应用层 应用层和应用程序直接相关,与程序员息息相关的一层协议,应用层协议,里面描述的内容,就是写的程序,通过网络具体按照啥样的方式来进行传输,不同的应用程序,就可以用不同的应用层协议,在实际开发的过程中,需要程序员自制应用层协议 应用层协议本质上就是对…

主题巴巴WordPress主题合辑打包下载+主题巴巴SEO插件

主题巴巴WordPress主题合辑打包下载&#xff0c;包含博客一号、博客二号、博客X、门户一号、门户手机版、图片一号、杂志一号、自媒体一号、自媒体二号和主题巴巴SEO插件。

5行代码快速Git配置ssh

0 流程步骤 检查本地主机是否已经存在ssh key生成ssh key获取ssh key公钥内容&#xff08;id_rsa.pub&#xff09;复制该内容&#xff0c;到Github账号上添加公钥&#xff0c;进入Settings设置验证是否设置成功 1 代码 # 1.检查本地主机是否已经存在ssh key cd ~/.ssh ls # …

Notepad++ 安装 compare 插件

文章目录 文章介绍对比效果安装过程参考链接 文章介绍 compare 插件用于对比文本差异 对比效果 安装过程 搜索compare插件 参考链接 添加链接描述

权威认可!聚铭网络再度入选中国信通院《中国数据库产业图谱(2024年)》

7月16日&#xff0c;由中国信息通信研究院、中国通信标准化协会指导&#xff0c;中国通信标准化协会大数据技术标准推进委员会&#xff08;CCSA TC601&#xff09;、InfoQ联合主办的“2024可信数据库发展大会”在京召开。会上&#xff0c;正式发布《中国数据库产业图谱&#xf…

《电子技术》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答 问&#xff1a;《电子技术》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的第一批认定学术期刊。 问&#xff1a;《电子技术》级别&#xff1f; 答&#xff1a;省级。主管单位&#xff1a;上海科学技术协会 主办单位&#xff1a;上海…

基于N32L406+FlashDB(键值数据库 时序数据库)+mdk5 移植教程

这里首先感谢作者的开源&#xff1a; FlashDB: 一款支持 KV 数据和时序数据的超轻量级数据库 (gitee.com) 1.FlashDB简介 一款超轻量级的嵌入式数据库&#xff0c;专注于提供嵌入式产品的数据存储方案。FlashDB 不仅支持传统的基于文件系统的数据库模式&#xff0c;而且结合了…

【YOLOv8】一文全解+亮点介绍+训练教程+独家魔改优化技巧

前言 Hello&#xff0c;大家好&#xff0c;我是cv君&#xff0c;最近开始在空闲之余&#xff0c;经常更新文章啦&#xff01;除目标检测、分类、分隔、姿态估计等任务外&#xff0c;还会涵盖图像增强领域&#xff0c;如超分辨率、画质增强、降噪、夜视增强、去雾去雨、ISP、海…

o(∩_∩)o设置代理访问博客(五)o(∩_∩)o --使用BeeWare打包应用

背景&#xff1a; 最近了解了一个新的打包框架BeeWare&#xff0c;据说他支持的平台要比nuitka更多。利用之前访问博客的脚本&#xff0c;来尝试一下打包windows应用程序。 BeeWare 从零开始 环境&#xff1a;python -m pip install briefcase 使用pycharm新建了一个名为bee…

【HTML入门】第二十一课 - 【实战】做一个简单的数据表格

这一小节&#xff0c;我们继续练习纯HTML&#xff0c;开发一个简单的数据表格吧。就像这样&#xff1a; 目录 1 设计需求分析 2 用到的标签 3 实战代码 1 设计需求分析 做之前&#xff0c;我们仍然是分析一下这张图&#xff0c;以便更好的更快的开发出来。 分2个大部分第一个…

掌控板(为Python编程学习而生)文章目录+入门教程 简介

前言 文章目录 掌控入门系列教程目录 【Mind】掌控板入门教程01 “秀”出我创意 【Mind】掌控板入门教程02 趣味相框 【Mind】掌控板入门教程03 节日的祝福【Mind】掌控板入门教程04 迷你动画片【Mind】掌控板入门教程05 心情灯【Mind】掌控板入门教程06 多彩呼吸灯【Mind】掌…

word预览方式---iframe,Microsoft Office Online、xDoc、Google Docs

提示&#xff1a;word预览方式—iframe 文章目录 [TOC](文章目录) 前言一、Microsoft Office Online二、xDoc三、Google Docs四、预览组件总结 前言 使用vue/cli脚手架vue create创建 一、Microsoft Office Online https://view.officeapps.live.com/op/view.aspx?src二、xDo…