react-router理解

react-router理解

1. Provider和Consumer

Provider和Comsumer是React提供的两个原生组件,Provider的value属性传递数据,Provider包裹内的所有Consumer都可以直接获取到Provider的数据

获取方法

let { Provider, Consumer } = React.createContext();

使用方法

<Provider value={{ name: "aeipyuan" }}>
    <div>
        <div>
            <Consumer>{state => {
                console.log(state)
                return <div>{state.name}</div>
            }}</Consumer>
        </div>
    </div>
</Provider>

2. HashRouter 和 Route

根据hash改变组件的步骤

  • HashRouter绑定hashchange事件,每次发生都会触发setState,并记录新的hash
  • 数据改变触发render函数,render一个Provider,并且将新数据(pathname)放到Provider的value属性上
  • Router作为Router的子组件,也会重新执行render,此时通过Consumer接受Provider提供的数据,并且比对Route自身传递的path属性和state里面的pathname,如果符合条件则返回Route属性传入的Component进行渲染,否则返回null不渲染
/* index.jsx */
<Router>
    <Route path="/home" component={Home}></Route>
    <Route path="/profile" component={Profile}></Route>
    <Route path="/user" component={User}></Route>
</Router>
/* HashRouter.js */
export default class HashRouter extends React.Component {
    constructor() {
        super();
        this.state = {
            location: {/* slice去除# */
                pathname: window.location.hash.slice(1) || '/'
            }
        }
    }
    /* 挂载组件完成时绑定hashchange事件 */
    componentDidMount() {
        window.location.hash = window.location.hash || '/'
        window.addEventListener('hashchange', () => {
            this.setState({
                location: {
                    ...this.state.location,
                    pathname: window.location.hash.slice(1) || '/'
                }
            })
        })
    }
    render() {
        /* 要传给Route使用的值 */
        let value = {
            location: this.state.location
        }
        return (<Provider value={{ ...value }}>
            {this.props.children}
        </Provider>)
    }
}
/* Route.js */
export default class Route extends React.Component {
    render() {
        return (<Consumer>
            {state => {
                /* 浏览器路径 */
                let pathname = state.location.pathname;/* /home */
                /* 获取Route的路径和组件,以及是否精确匹配 */
                //{path: "/home", component: ƒ, exac:true}
                let { path, component: Component, exac = false } = this.props;
                /* 比对路径 end为true时为严格模式时*/
                let reg = pathToRegexp(path, [], { end: exac });
                /* 通过属性把Provider的数据继续传给子组件 */
                return reg.test(pathname) ? <Component {...state}/> : null;
            }}
        </Consumer>)
    }
}

3. Link

/* index.jsx */
<div>
    <Link to="/home"> 主页 </Link>
    <Link to="/profile"> 个人中心 </Link>
    <Link to="/user"> 用户 </Link>
</div>

实现步骤

  • 扩充HashRender通过Provider传递的方法,增加push方法强制改变hash
/* 要传给Consumer使用的数据 */
let value = {
    location: this.state.location,
    history: {
        push(to) {
            window.location.hash = to;
        }
    }
}
  • Link组件插入一个Consumer,返回值是一个a标签,点击触发HashRender提供的push方法
return (<Consumer>
    {state => {
        /* 调用state的push函数,强制跳转 */
        return <a onClick={() => {
            state.history.push(this.props.to)
        }}>{this.props.children}</a>
    }}
</Consumer>)

4. Switch

<Switch>
    <Route path="/home" component={Home}></Route>
    <Route path="/home" component={Home}></Route>
    <Route path="/profile" component={Profile}></Route>
    <Route path="/user" component={User}></Route>
</Switch>

Switch实现每次只匹配一个组件的效果

  • 利用Consumer获取浏览器hash值
  • 遍历孩子,遇到符合条件的直接停止遍历,不处理后面的子元素
return (<Consumer>
    {state => {
        let { pathname } = state.location;
        /* 遍历子元素,找出符合条件的进行渲染然后返回 */
        for (let child of this.props.children) {
            /* 比对 */
            let path = child.props.path || '';
            let exac = child.props.exac || false;
            let reg = pathToRegexp(path, [], { end: exac });
            if (reg.test(pathname)) return child;
        }
        return null;
    }}
</Consumer>)

5. Redirect

<Switch>
    <Route path="/home" component={Home}></Route>
    <Route path="/profile" component={Profile}></Route>
    <Route path="/user" component={User}></Route>
    <Redirect to="/err"></Redirect>
</Switch>

当所有页面都不匹配时强制跳转

return (<Consumer>
    {state => {
        // 直接强行改变hash
        state.history.push(this.props.to);
        return null;
    }}
</Consumer>)

6. 根据参数匹配页面

match的作用是使组件可以通过类似/home/:id的方式传递数据,渲染出id相关数据

/* user.jsx */
return (<div>
    <div>
        <Link to="/user/add"> 用户添加 </Link>
        <Link to="/user/list"> 用户列表 </Link>
        <hr />
    </div>
    <div>
        <Route path="/user/add" component={userAdd}></Route>
        <Route path="/user/list" component={userList}></Route>
        <Route path="/user/detail/:id/:name" component={userDetail}></Route>
    </div>
</div>)
/* userList.jsx */
return (<div>
        <Link to="/user/detail/1/Amy">  Amy  </Link>
        <Link to="/user/detail/2/Mike">  Mike  </Link>
        <Link to="/user/detail/3/Nancy">  Nancy  </Link>
</div>)
/* userDetail.js */
return (
    <div>
        Detail
        <div>id: {this.props.match.params.id}</div>
        <div>name:{this.props.match.params.name}</div>
    </div>
)

实现步骤:

  • 利用pathToRegexp函数解析对比可以得到key和values的映射关系
let { pathToRegexp } = require('path-to-regexp')
let keys = [];
let reg = pathToRegexp('/user/detail/:id/:name', keys, { end: false })
console.log(keys.map(v => v.name));//[ 'id', 'name' ]
/* 测试 */
let pathname = '/user/detail/111/bbb';
let [url, ...values] = path.match(reg);
console.log(values);//['111','bbb'];对应于['id','name']
  • 依据pathToRegexp规则修改Route类的render函数,将映射应用到params上,传递给子元素
/* Route.js */
render() {
    return (<Consumer>
        {state => {
            /* 浏览器路径 */
            let { pathname } = state.location;/* /home */
            /* 获取Route的路径和组件,以及是否精确匹配 */
            //{path: "/home", component: ƒ, exac:true}
            let { path, component: Component, exac = false } = this.props;
            /* 比对路径 end为true时为严格模式时*/
            let keys = [];
            let reg = pathToRegexp(path, keys, { end: exac });
            keys = keys.map(v => v.name);
            let [url, ...values] = pathname.match(reg) || [];
            // console.log(keys)//['id','name']
            // console.log(values);//['1','Amy']
            /* 设置传给子节点的数据 */
            let props = {
                ...state,
                match: {
                    params: keys.reduce((data, item, idx) => {
                        data[item] = values[idx];
                        return data;
                    }, {})//结果 {id:'1',name:'Amy'}
                }
            }
            return reg.test(pathname) ? <Component {...props} /> : null;
        }}
    </Consumer>)
}
过程描述
  1. 假设to="/user/detail/1/Amy",点击link标签,调用state传入的state.history.push(to)方法,改变window.location.hash
  2. 触发HashRouter的hashchange事件,改变HashRouter的state重新render包含的内容
  3. App组件的Route匹配到/user渲染User组件
  4. User组件的Route匹配到/user/detail/:id/:name,将{id:'1',name:'Amy'}放到props.history.match,再将整个props解构传给userDetail

7. 总结

  • HashRouter监听hash的改变并提供操作hash的方法,每次改变都会触发包裹的内部元素进行render,并使用Provider传递数据
  • Route、Link、Switch、Redirect都是使用Consumer接受HashRouter关于hash的数据和方法,利用这些数据与属性传入数据进行比对,从而确定是否渲染组件或者子组件
原文地址:https://www.cnblogs.com/aeipyuan/p/12990174.html