如何实现一个ReactRouter-创新互联

如何实现一个ReactRouter?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

创新互联公司服务项目包括云霄网站建设、云霄网站制作、云霄网页制作以及云霄网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,云霄网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到云霄省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!

ReactRouter的实现

ReactRouterReact的核心组件,主要是作为React的路由管理器,保持UIURL同步,其拥有简单的API与强大的功能例如代码缓冲加载、动态路由匹配、以及建立正确的位置过渡处理等。

描述

React Router是建立在history对象之上的,简而言之一个history对象知道如何去监听浏览器地址栏的变化,并解析这个URL转化为location对象,然后router使用它匹配到路由,最后正确地渲染对应的组件,常用的history有三种形式:Browser HistoryHash HistoryMemory History

Browser History

Browser History是使用React Router的应用推荐的history,其使用浏览器中的History对象的pushStatereplaceStateAPI以及popstate事件等来处理URL,其能够创建一个像https://www.example.com/path这样真实的URL,同样在页面跳转时无须重新加载页面,当然也不会对于服务端进行请求,当然对于history模式仍然是需要后端的配置支持,用以支持非首页的请求以及刷新时后端返回的资源,由于应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问URL时就会返回404,所以需要在服务端增加一个覆盖所有情况的候选资源,如果URL匹配不到任何静态资源时,则应该返回同一个index.html应用依赖页面,例如在Nginx下的配置。

location / {
 try_files $uri $uri/ /index.html;
}

Hash History

Hash符号即#原本的目的是用来指示URL中指示网页中的位置,例如/tupian/20230522/index.html History

Memory History不会在地址栏被操作或读取,这就可以解释如何实现服务器渲染的,同时其也非常适合测试和其他的渲染环境例如React Native,和另外两种History的一点不同是我们必须创建它,这种方式便于测试。

const history = createMemoryHistory(location);

实现

我们来实现一个非常简单的Browser History模式与Hash History模式的实现,因为H5pushState方法不能在本地文件协议file://运行,所以运行起来需要搭建一个http://环境,使用webpackNginxApache等都可以,回到Browser History模式路由,能够实现history路由跳转不刷新页面得益与H5提供的pushState()replaceState()等方法以及popstate等事件,这些方法都是也可以改变路由路径,但不作页面跳转,当然如果在后端不配置好的情况下路由改编后刷新页面会提示404,对于Hash History模式,我们的实现思路相似,主要在于没有使用pushStateH5API,以及监听事件不同,通过监听其hashchange事件的变化,然后拿到对应的location.hash更新对应的视图。






 
 Router



 
      
  • home
  •   
  • about
  •   
 





 
 Router



 
      
  • home
  •   
  • about
  •   
 

分析

  • 我们可以看一下ReactRouter的实现,commit ideef79d5TAG4.4.0,在这之前我们需要先了解一下history库,history库,是ReactRouter依赖的一个对window.history加强版的history库,其中主要用到的有match对象表示当前的URLpath的匹配的结果,location对象是history库基于window.location的一个衍生。

  • ReactRouter将路由拆成了几个包:react-router负责通用的路由逻辑,react-router-dom负责浏览器的路由管理,react-router-native负责react-native的路由管理。


  • 我们以BrowserRouter组件为例,BrowserRouterreact-router-dom中,它是一个高阶组件,在内部创建一个全局的history对象,可以监听整个路由的变化,并将history作为props传递给react-routerRouter组件,Router组件再会将这个history的属性作为context传递给子组件。

// packages\react-router-dom\modules\HashRouter.js line 10
class BrowserRouter extends React.Component {
 history = createHistory(this.props);

 render() {
 return ;
 }
}

接下来我们到Router组件,Router组件创建了一个React Context环境,其借助contextRoute传递context,这也解释了为什么Router要在所有Route的外面。在RoutercomponentWillMount中,添加了history.listen,其能够监听路由的变化并执行回调事件,在这里即会触发setState。当setState时即每次路由变化时-> 触发顶层Router的回调事件->Router进行setState-> 向下传递nextContext此时context中含有新的location-> 下面的Route获取新的nextContext判断是否进行渲染。

// line packages\react-router\modules\Router.js line 10
class Router extends React.Component {
 static computeRootMatch(pathname) {
 return { path: "/", url: "/", params: {}, isExact: pathname === "/" };
 }

 constructor(props) {
 super(props);

 this.state = {
  location: props.history.location
 };

 // This is a bit of a hack. We have to start listening for location
 // changes here in the constructor in case there are any s
 // on the initial render. If there are, they will replace/push when
 // they mount and since cDM fires in children before parents, we may
 // get a new location before the  is mounted.
 this._isMounted = false;
 this._pendingLocation = null;

 if (!props.staticContext) {
  this.unlisten = props.history.listen(location => {
  if (this._isMounted) {
   this.setState({ location });
  } else {
   this._pendingLocation = location;
  }
  });
 }
 }

 componentDidMount() {
 this._isMounted = true;

 if (this._pendingLocation) {
  this.setState({ location: this._pendingLocation });
 }
 }

 componentWillUnmount() {
 if (this.unlisten) this.unlisten();
 }

 render() {
 return (
  
 );
 }
}

我们在使用时都是使用Router来嵌套Route,所以此时就到Route组件,Route的作用是匹配路由,并传递给要渲染的组件propsRoute接受上层的Router传入的contextRouter中的history监听着整个页面的路由变化,当页面发生跳转时,history触发监听事件,Router向下传递nextContext,就会更新Routepropscontext来判断当前Routepath是否匹配location,如果匹配则渲染,否则不渲染,是否匹配的依据就是computeMatch这个函数,在下文会有分析,这里只需要知道匹配失败则matchnull,如果匹配成功则将match的结果作为props的一部分,在render中传递给传进来的要渲染的组件。Route接受三种类型的render props,此时要注意的是如果传入的component是一个内联函数,由于每次的props.component都是新创建的,所以Reactdiff的时候会认为进来了一个全新的组件,所以会将旧的组件unmountre-mount。这时候就要使用render,少了一层包裹的component元素,render展开后的元素类型每次都是一样的,就不会发生re-mount了,另外children也不会发生re-mount

// \packages\react-router\modules\Route.js line 17
class Route extends React.Component {
 render() {
 return (
  
  {context => {
   invariant(context, "You should not use  outside a ");

   const location = this.props.location || context.location;
   const match = this.props.computedMatch
   ? this.props.computedMatch //  already computed the match for us
   : this.props.path
    ? matchPath(location.pathname, this.props)
    : context.match;

   const props = { ...context, location, match };

   let { children, component, render } = this.props;

   // Preact uses an empty array as children by
   // default, so use null if that's the case.
   if (Array.isArray(children) && children.length === 0) {
   children = null;
   }

   if (typeof children === "function") {
   children = children(props);
   // ...
   }

   return (
   
    {children && !isEmptyChildren(children)
    ? children
    : props.match
     ? component
     ? React.createElement(component, props)
     : render
      ? render(props)
      : null
     : null}
   
   );
  }}
  
 );
 }
}

我们实际上我们可能写的最多的就是Link这个标签了,所以我们再来看一下组件,我们可以看到Link最终还是创建一个a标签来包裹住要跳转的元素,在这个a标签的handleClick点击事件中会preventDefault禁止默认的跳转,所以实际上这里的href并没有实际的作用,但仍然可以标示出要跳转到的页面的URL并且有更好的html语义。在handleClick中,对没有被preventDefault、鼠标左键点击的、非_blank跳转的、没有按住其他功能键的单击进行preventDefault,然后pushhistory中,这也是前面讲过的路由的变化与 页面的跳转是不互相关联的,ReactRouterLink中通过history库的push调用了HTML5 historypushState,但是这仅仅会让路由变化,其他什么都没有改变。在Router中的listen,它会监听路由的变化,然后通过context更新propsnextContext让下层的Route去重新匹配,完成需要渲染部分的更新。

// packages\react-router-dom\modules\Link.js line 14
class Link extends React.Component {
 handleClick(event, history) {
 if (this.props.onClick) this.props.onClick(event);

 if (
  !event.defaultPrevented && // onClick prevented default
  event.button === 0 && // ignore everything but left clicks
  (!this.props.target || this.props.target === "_self") && // let browser handle "target=_blank" etc.
  !isModifiedEvent(event) // ignore clicks with modifier keys
 ) {
  event.preventDefault();

  const method = this.props.replace ? history.replace : history.push;

  method(this.props.to);
 }
 }

 render() {
 const { innerRef, replace, to, ...rest } = this.props; // eslint-disable-line no-unused-vars

 return (
  
  {context => {
   invariant(context, "You should not use  outside a ");

   const location =
   typeof to === "string"
    ? createLocation(to, null, null, context.location)
    : to;
   const href = location ? context.history.createHref(location) : "";

   return (
    this.handleClick(event, context.history)}
    href={href}
    ref={innerRef}
   />
   );
  }}
  
 );
 }
}

关于如何实现一个ReactRouter问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注创新互联行业资讯频道了解更多相关知识。


本文名称:如何实现一个ReactRouter-创新互联
标题URL:
http://myzitong.com/article/dceepi.html

其他资讯