陌笛-react.js路由

web应⽤的路由

  • 路由的概念
https://news.163.com/world/index.html
https://xxx.163.com/xx/index.php
...

访问过程为

  1. 浏览器发出请求
  2. 服务器监听到80端⼝(或443)有请求过来,并解析url路径
  3. 根据服务器的路由配置,返回相应信息(可以是 html 字串,也可以是 json 数据,图⽚等)
  4. 浏览器根据数据包的Content-Type来决定如何解析数据
    简单来说路由就是⽤来跟后端服务器进⾏交互的⼀种⽅式,通过不同的路径,来请求不同的资源,请求
    不同的⻚⾯是路由的其中⼀种功能。
  • 前端路由的诞⽣
    • ajax的出现
    • 前端框架的演变

前端路由的实现⽅式

后端通过固定⼊⼝返回静态⽂件模板,前端在模板上构建⼤型spa应⽤路由上,通过检测 url 的变化,截获 url 地址,然后解析来匹配路由规则。

  • hash路由的实现(2014年以前)
https://www.xxx.com/home/#my

陌笛-react.js路由_第1张图片

  • history的实现(2014年后)
    14年后,因为HTML5标准发布。多了两个 API,pushStatereplaceState,通过这两个 API可以改变 url 地址且不会发送请求。
    同时还有 onpopstate 事件。通过这些就能⽤另⼀种⽅式来实现前端路由了

陌笛-react.js路由_第2张图片

vue-router

  • 实现原理
    • hash模式(/dev/src/history/hash.js)
    /**
     * 添加 url hash 变化的监听器
     */
    function setupListeners () {
      const router = this.router
      /**
       * 每当 hash 变化时就解析路径
       * 匹配路由
       */
      window.addEventListener('hashchange', () => {
        const current = this.current
        /**
         * transitionTo:
         * 匹配路由
         * 并通过路由配置,把新的⻚⾯ render 到 ui-view 的节点
         */
        this.transitionTo(getHash(), route => {
          replaceHash(route.fullPath)
        })
      })
    }
    
    • history模式(/dev/src/history/html5.js)
    export class HTML5History extends History {
      constructor (router, base) {
        super(router, base)
        /**
         * 原理还是跟 hash 实现⼀样
         * 通过监听 popstate 事件
         * 匹配路由,然后更新⻚⾯ DOM
         */
        window.addEventListener('popstate', e => {
          const current = this.current
    // Avoiding first `popstate` event dispatched in some browsers but
          first
    // history route not updated since async guard at the same time.
          const location = getLocation(this.base)
          if (this.current === START && location === initLocation) {
            return
          }
          this.transitionTo(location, route => {
            if (supportsScroll) {
              handleScroll(router, route, current, true)
            }
          })
        })
      }
      go (n) {
        window.history.go(n)
      }
      push (location, onComplete, onAbort) {
        const { current: fromRoute } = this
        this.transitionTo(location, route => {
    // 使⽤ pushState 更新 url,不会导致浏览器发送请求,从⽽不会刷新⻚⾯
          pushState(cleanPath(this.base + route.fullPath))
          onComplete && onComplete(route)
        }, onAbort)
      }
      replace (location, onComplete, onAbort) {
        const {current: fromRoute} = this
        this.transitionTo(location, route => {
    // replaceState 跟 pushState 的区别在于,不会记录到历史栈
          replaceState(cleanPath(this.base + route.fullPath))
          onComplete && onComplete(route)
        }, onAbort)
      }
    }
    
    • 使⽤⽅式文档

react-router

  • react-router-dom 和 react-router

  • react-router-dom使⽤⽅式⽂档(react-router-dom是react-router的超集,前者比后者多了浏览器环境下的一些功能)

  • react-router实现原理
    陌笛-react.js路由_第3张图片

  • react-router-dom实现原理
    在此实现一下react中最常用的BrowserRouter、Route、Link

    // react-router-dom/index.js
    
    import BrowserRouter from './BrowerRouter';
    import Route from './Route';
    import Link from './Link';
    
    export {
        BrowserRouter,
        Route,
        Link
    }
    

    react-router-dom/context.js,通过context将一些公共状态和方法例如history、location、push传入到对应的组件中

    import React from 'react';
    export default React.createContext();
    

    react-router-dom/BrowserRouter.js

    import React, { Component } from "react";
    import Context from './context';
    
    export default class BrowserRouter extends Component {
        constructor(props) {
            super(props);
            this.state = {
                location: {
                    pathname: window.location.pathname || "/",
                    search: undefined,
                },
                match: {
    
                }
            }
        }
        componentWillMount() {
            window.addEventListener("popstate", () => {
                this.setState({
                    location: {
                        pathname: window.location.pathname
                    }
                })
            })
        }
        render() {
            const currentRoute = {
                location: this.state.location,
                match: this.state.match,
                history: {
                    push: (to) => {
                        // 根据当前to 去匹配不同的路由 实现路由切换
                        if (typeof to === 'object') {
                            let { pathname, query } = to;
                            // 只是改变当前state的数据, 不触发reRender
                            this.setState({
                                location: {
                                    query: to.query,
                                    pathname: to.pathname
                                }
                            });
                            window.history.pushState({}, {}, pathname)
                        } else {
                            // 如果是字符串
                            this.setState({
                                location: {
                                    pathname: to
                                }
                            })
                            window.history.pushState({}, {}, to)
                        }
    
                    }
                }
            }
            return (
                {this.props.children}
            )
        }
    }
    

    react-router-dom/Route.js

    import React, { Component } from "react";
    import { pathToRegexp, match } from "path-to-regexp";
    import context from "./context";
    export default class Route extends Component {
        static contextType = context;
        render() {
            const currenRoutePath = this.context.location.pathname; // 从上下文context中获取到当前路由
            const { path, component: Component, exact = false } = this.props; // 获取Route组件props的路由
            const paramsRegexp = match(path, { end: exact }); // 生成获取params的表达式
            const matchResult = paramsRegexp(currenRoutePath);
            console.log("路由匹配结果", matchResult);
            this.context.match.params = matchResult.params;
            const props = {
                ...this.context
            }
            const pathRegexp = pathToRegexp(path, [], { end: exact }); // 生成路径匹配表达式
            if (pathRegexp.test(currenRoutePath)) {
                return () // 将蛋清概念上下文路由信息当作props传递给组件
            }
            return null;
        }
    }
    

    react-router-dom/Link.js

    import React, { Component } from "react";
    import context from "./context";
    export default class Link extends Component {
        static contextType = context;
        render() {
            const { to } = this.props;
            return ( {
                this.context.history.push(to)
            }}>{this.props.children})
        }
    }
    

你可能感兴趣的:(react.js,前端,javascript)