最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • React-Router源码小问答

    正文概述 掘金(云之彼端有你)   2021-04-29   555

    React-router是我们开发过程中,经常使用的库。在不接触源码的基础上,我们可能会对默认写法,props接收到的值产生疑惑。这片文章就是为你解惑的。

    问:HashRouterBrowserRouter异同?

    答:相同点是底层都依赖于react-router中的Router组件,都是通过history库创建history对象作为参数传给Router组件。

    import { createHashHistory as createHistory } from "history";
    import { Router } from "react-router";
    
    class HashRouter extends React.Component {
      history = createHistory(this.props);
    
      render() {
        return <Router history={this.history} children={this.props.children} />;
      }
    }
    
    import { Router } from "react-router";
    import { createBrowserHistory as createHistory } from "history";
    
    class BrowserRouter extends React.Component {
      history = createHistory(this.props);
    
      render() {
        return <Router history={this.history} children={this.props.children} />;
      }
    }
    

    差别如上可见,调用了history库中不同的方法创建的history对象,而且不仅仅如此,暴露出去的props也不太一样:

    HashRouter.propTypes = {
      basename: PropTypes.string,
      children: PropTypes.node,
      getUserConfirmation: PropTypes.func,
      hashType: PropTypes.oneOf(["hashbang", "noslash", "slash"])
    };
    
    BrowserRouter.propTypes = {
      basename: PropTypes.string,
      children: PropTypes.node,
      forceRefresh: PropTypes.bool,
      getUserConfirmation: PropTypes.func,
      keyLength: PropTypes.number
    };
    

    问:Route组件的props为什么能拿到history,location和match对象?

    答:Router使用了context,创建了Provider,将locationhistorymatchstaticContext作为内容传给Consumer。而Route作为contextConsumer,就可以接受到参数了。

    // Router.js
    import React from "react";
    import HistoryContext from './HistoryContext'
    import RouterContext from './RouterContext'
    
    class Router extends React.Component {
      constructor(props) {
        super(props);
    
        this.state = {
          location: props.history.location
        };
        
        // 监听路由变化。处于mount过程中,有<Redirect>组件导致路由发生变化,
        // 为了防止location丢失,以及页面抖动(setState)
        // 这时需要把变化的location放到内存中,等页面mount后,再setState
        this._isMounted = false;
        this._pendingLocation = null;
    
        if (!props.staticContext) {
          this.unlisten = props.history.listen(location => {
            // 如果页面mount成功,直接setState;
            // 如果页面还在mounting,则等待页面mount后再setState
            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();
          this._isMounted = false;
          this._pendingLocation = null;
        }
      }
    
      render() {
        return (
          <RouterContext.Provider
            value={{
              history: this.props.history,
              location: this.state.location,
              match: Router.computeRootMatch(this.state.location.pathname),
              staticContext: this.props.staticContext
            }}
          >
            <HistoryContext.Provider
              children={this.props.children || null}
              value={this.props.history}
            />
          </RouterContext.Provider>
        );
      }
    }
    
    // Route.js
    import React from "react";
    
    class Route extends React.Component {
      render() {
        return (
          <RouterContext.Consumer>
            {context => {
              
              const location = this.props.location || context.location;
    
              // <Switch>组件会传入computedMatch
              // 其他方式是不会传入computedMatch字段的
              const match = this.props.computedMatch
                ? this.props.computedMatch
                : (
                  // matchPath:如果是完全匹配的路由,返回{path, url, isExact, params}
                  // 如果不匹配,返回null。这行是控制路由组件展示的关键
                  this.props.path ? matchPath(location.pathname, this.props) : context.match
                );
    
              const props = { ...context, location, match };
    
              let { children, component, render } = this.props;
    
              if (Array.isArray(children) && React.Children.count(children) === 0) {
                children = null;
              }
              
              return (
                <RouterContext.Provider value={props}>
                  {
                    props.match ? (
                      children ? (
                        typeof children === "function" ? children(props) : children
                      ) : (
                        component ? React.createElement(component, props) : (render ? render(props) : null)
                      )
                    ) : (
                      typeof children === "function" ? children(props) : null
                    )
                  }
                </RouterContext.Provider>
              );
            }}
          </RouterContext.Consumer>
        );
      }
    }
    
    // createNamedContext.js
    const createNamedContext = name => {
      const context = React.createContext();
      context.displayName = name;
    
      return context;
    }
    
    // HistoryContext.js
    const HistoryContext = createNamedContext("Router-History")
    export default HistoryContext
    
    // RouterContext.js
    const RouterContext = createNamedContext("Router")
    export default RouterContext
    

    因此,如下问题的发生,就不难解释了。未匹配到/c路由,但是执行了/c路由下的方法。

    React-Router源码小问答 可通过Switch包裹Route组件避免此种情况的发生。

    问:Switch组件是如何保证渲染的是第一个路由组件

    答:通过遍历子组件,取出第一个满足pathname的路由组件,clone后展示出来。

    import React from "react";
    
    class Switch extends React.Component {
      render() {
        return (
          <RouterContext.Consumer>
            {context => {
              const location = this.props.location || context.location;
    
              let element, match;
    
              React.Children.forEach(this.props.children, child => {
                if (match == null && React.isValidElement(child)) {
                  element = child;
    
                  const path = child.props.path || child.props.from;
                  match = path ? matchPath(location.pathname, { ...child.props, path }) : context.match;
                }
              });
    
              return match
                ? React.cloneElement(element, { location, computedMatch: match })
                : null;
            }}
          </RouterContext.Consumer>
        );
      }
    }
    

    问:高级组件withRouter如何将location等传入普通组件内

    答:withRouter内部订阅了context的consumer,将context的包含的值作为props传入原组件中。

    import React from "react";
    import hoistStatics from "hoist-non-react-statics";
    
    function withRouter(Component) {
      const C = props => {
        const { wrappedComponentRef, ...remainingProps } = props;
    
        return (
          <RouterContext.Consumer>
            {context => {
              return (
                <Component
                  {...remainingProps}
                  {...context}
                  ref={wrappedComponentRef}
                />
              );
            }}
          </RouterContext.Consumer>
        );
      };
    
      C.WrappedComponent = Component;
    
      // 拷贝Component上非React的静态方法,防止静态方法丢失
      return hoistStatics(C, Component);
    }
    

    问:router自带的hook有哪些,原理是啥?

    答:Hook底层均是对useContext钩子的封装,通过useContext钩子获取context的值。Hook有如下4种:useHistoryuseLocationuseParamsuseRouteMatch

    import { useContext } from 'react'
    import RouterContext from "./RouterContext"
    import HistoryContext from "./HistoryContext"
    
    export function useHistory() {
      return useContext(HistoryContext)
    }
    
    export function useLocation() {
      return useContext(RouterContext).location
    }
    
    export function useParams() {
      const match = useContext(RouterContext).match
      return match ? match.params : {}
    }
    
    export function useRouteMatch(path) {
      const location = useLocation();
      const match = useContext(RouterContext).match
      return path ? matchPath(location.pathname, path) : match
    }
    

    相关链接:

    hoist-non-react-statics:zhuanlan.zhihu.com/p/36178509
    React.Children: segmentfault.com/a/119000001…


    起源地下载网 » React-Router源码小问答

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元