开始学习React-Router v4 —— Histories

301 阅读2分钟

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

history 是 React-Router 极少的依赖模块之一,它主要作用是在不同的环境下管理会话。经常会使用到的 history 有:

  • browser history:DOM 的具体应用,支持 HTML5 的 history API。

  • hash history:DOM 的具体应用,用以兼容老版本的浏览器。

  • memory history:在无 DOM 或测试环境下通过内存方式处理来实施,适合 React-Native。 和history 对象相关的主要属性和方法有:

  • length:history 栈中的状态路径个数。

  • action:当前的操作,字符串形式(PUSH, REPLACE, POP)

  • location:当前的状态路径。

    • pathname: URL 路径字段
    • search:URL 查询字段
    • hash:URL 的 hash 字段
    • state:路径的状态,只在 browser 和 memory history 中有效。
    • push(path, [state]):将状态路径压入 history 栈。
    • replace(path, [state]):替换当前的状态路径。
    • go(n):向前移动当前指针 n 次。
    • goBack():go(-1)
    • goForward():go(1)
    • block(prompt):暂时阻止导航(“您确定离开”)。

history 是可变的,因此状态路径的访问方式推荐使用 的属性(props)里间接获取,而不是直接使用 history.location。这可以保证在 React 生存周期里能够获得正确的比较。

index.js

import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import { HashRouter as Router, Route, Switch} from './react-router-dom'
import 'bootstrap';
import App from './components/App'
import User from './components/User';
import Login from './components/Login';
import Protected from './components/Protected'

let Home = (props,context)=>{
  console.log(props,context)
  return <div>首页</div>
}
//let User = ()=><div>用户管理</div>
let Profile = ()=><div>个人设置</div>

ReactDOM.render(
    <App>
        <Route path="/home" component={Home} />
        <Route path="/user" component={User} />
        <Route path="/login" component={Login} />
        <Protected path="/profile" component={Profile} />
    </App>
  ,document.querySelector('#root')
)


/**
 *
 {
  history:{
    push()
  },
  location:{pathname:'/home'},
  match{
    params:{},
    path:'/home',
    url:'/home'
  }
}
url /user/datail/1
path /user/datail/:id
params= {}
 */

HashRouter.js

import React,{Component} from 'react';
import PropTypes from 'prop-types';

export default class HashRouter extends Component{
  static childContextTypes = {
    location:PropTypes.object,
    history:PropTypes.object
  }
  constructor(props){
    super(props)
    this.state = { location: { pathname: window.location.hash.slice(1) || '/' }, state: {}  };
  }
  getChildContext() {
    let that = this
    return {
      location: this.state.location,
      history: {
        push(path) {
          if (typeof path == 'object') {
            let { pathname, state } = path;
            that.setState({
              location: { ...that.state.location, state }
            }, () => {
              console.log('this.state.location.state', that.state.location.state);
              window.location.hash = pathname;
            });
          } else {
            window.location.hash = path;
          }
        }
      }
    }
  }
  componentDidMount(){
    window.location.hash = window.location.hash || '/'
    let render = ()=>{
      this.setState({ location: { ...this.state.location, pathname: window.location.hash.slice(1) || '/' } });
    }
    window.addEventListener('hashchange',render)
  }
  render(){
    return this.props.children
  }
}

Route.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import pathToRegexp from 'path-to-regexp';
export default class Route extends Component {
    constructor(props) {
        super(props);
        let { path } = props;// /user/detail/:id
        this.keys = [];
        this.regexp = pathToRegexp(path, this.keys, { end: false });
        this.keys = this.keys.map(key => key.name);
    }
    static contextTypes = {
        location: PropTypes.object,
        history: PropTypes.object
    }
    render() {
        let { path, component: Component, render, children } = this.props;
        let { location } = this.context;
        let result = location.pathname.match(this.regexp);
        let props = {
            location,
            history: this.context.history
        }
        if (result) {
            let [url, ...values] = result;
            props.match = {
                url,
                path,
                params: this.keys.reduce((memo, key, idx) => {
                    memo[key] = values[idx];
                    return memo;
                }, {})
            }
            if (Component) {
                return <Component {...props} />
            } else if (render) {
                return render(props);
            } else if (children) {
                return children(props);
            } else {
                return null;
            }
        } else {
            if (children) {
                return children(props);
            } else {
                return null;
            }
        }
    }
}

history Hash 的关键是context