react-Router 及源码分析

栏目: 服务器 · 发布时间: 6年前

内容简介:安装目的当你点击不同路径,就渲染不同组件实现方式

安装 http-serverreact-router-dompath-to-regexp 将路径转换为正则

目的当你点击不同路径,就渲染不同组件

实现方式

  1. hashrouter
  2. BrowserRouter

hashrouter

最早客户端实现路由靠锚点,实现切换页面不刷新,

应用方法,对 hashchange 进行监听,这个是浏览器自带的哦~!

//浏览器自带
    window.addEventListener('hashchange',(event)=>{
        console.log(event);
    })
复制代码

BrowserRouter

利用h5 APi 实现 history对象,它提供啦操作浏览器绘画历史的接口,追踪一组location,并且保存索引周,指向当前作用的location

跑通路由

react-router-dom 引入Route(路由规则)和Router(容器)

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {HashRouter as Router,Route,Link,Switch,Redirect,Forward,Back} from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Protected from './components/Protected';
import Profile from './components/Profile';
import Login from './components/Login';
import MenuLink from './components/MenuLink';
import NavHeader from './components/NavHeader';
import 'bootstrap/dist/css/bootstrap.css';
import './components/MenuLink.css';
//一个路径对应一个组件
ReactDOM.render(
    <Router>
        <div>
            <nav>
                <div className="navbar navbar-inverse">
                    <div className="container-fluid">
                        <NavHeader />
                        <div>
                            <ul className="nav navbar-nav">
                                <MenuLink to='/' exact={true} lable="首页" />
                                <MenuLink to='/user' lable="用户管理" />
                                <MenuLink to='/profile' lable="个人设置" />
                            </ul>
                        </div>
                    </div>
                </div>
            </nav>
            <Switch>
                <Route exact path="/" component={Home}/>
                <Route path="/user" component={User}/>
                <Route path="/login" component={Login}/>
                <Protected path="/profile" component={Profile}/>
                <Redirect to="/"/>
            </Switch>  
        </div>
    </Router>

, document.getElementById('root'));

复制代码

Route两个属性,path对应路径, component对应组件 exact 是否绝对匹配, Router只能拥有一个子组件

component对应组件的props属性有

  • history
  • location {hash,pathname,search,state}
  • match {path,url,isExact,params }

这些属性是Router传过来的,靠contxt中的(provider提供者,Consumer消费者)传递(react.6.3)他们是由 React.creatContext() 创建

我们要实现 Home User Protected Profile Login MenuLink NavHeader 和 react-router-dom里的 HashRouter, Route,Link,Switch,Redirect,Forward,Back的功能

Components组件

components/Home主页

import  React , {Component} from 'react';
export default class Home extends Component{
    render(){
        return (
            <div>Home</div>
        )
    }
}
复制代码

components/Profile受权限页面

import React,{Component} from 'react';
export default class Profile extends Component{
	render() {
		return (
			<div>Profile</div>
		)
	}
}
复制代码

Protected受保护路由

import React,{Component} from 'react'
import {Route,Redirect} from '../react-router-dom';
// rest = {path,exact,,xxxxxx}
//返回路由,执行render,返回组件,或者重定向  to里面放对象或者路径
export default function ({component: Component,...rest}) {
	return (
	
		<Route {...rest} render={props => (
			localStorage.getItem('logined')? <Component {...props} />:<Redirect to={{pathname: '/login',state: {from:props.location.pathname}}}/>
		)}/>
	)
}
复制代码

components/User用户界面

import React,{Component} from 'react';
import {Link,Route,Back,Forward} from '../react-router-dom';
import UserAdd from './UserAdd';
import UserList from './UserList';
import UserDetail from './UserDetail';


export default class User extends Component{
    componentWillMount() {
		console.log('User componentWillMount');
	}
	componentDidMount() {
		console.log('User componentDidMount');
    }
    render() {
        return (
            <div className="row">
                <div className="col-md-2">
                    <ul className="nav nav-stacked">
                        <li><Link to="/user/add">新增用户</Link></li>
                        <li><Link to="/user/list">用户列表</Link></li>
                        <li><Back /></li>
                        <li><Forward /></li>
                    </ul>
                </div>
                <div className="col-md-10">
                    <Route path="/user/add" component={UserAdd} />
                    <Route path="/user/list" component={UserList} />
                    <Route path="/user/detail/:id" component={UserDetail}/>
                </div>
            </div>
        )
    }
}
复制代码

components/UserAdd添加用户界面

有跳转功能

import  React , {Component} from 'react';
import api from './api';
import {Prompt} from '../react-router-dom'
export default class UserAdd extends Component{
    state={
        isBlocking: false
    }

    componentWillUpdate(nextProps, nextState) {
        console.log('Component WILL UPDATE!');
        console.log(nextProps, nextState);
    }

    componentDidUpdate(prevProps, prevState) {
        console.log('Component DID UPDATE!');
        console.log(prevProps, prevState);
    }


    handleSubmit=(event) => { 
        this.setState({isBlocking: false},()=>{
            event.preventDefault();
            let username=this.username.value;
            let email=this.email.value;
            let user={username,email};
            api.createUser(user);
            this.props.history.push('/user/list');                  
        })
    }
    render(){
        // 提交表单,提交到跳页面
        return (
            <form onSubmit={this.handleSubmit}>
                <Prompt
                    when = {this.state.isBlocking}
                    message={
                        loc =>`请问你是否要切换到${loc.pathname}`
                    }
                />
                <div className="form-group">
                    <label htmlFor="username" className="control-label">用户名</label>
                    <input 
                        onChange={()=> this.setState({isBlocking:true})}
                        ref={input=>this.username = input} type="text" className="form-control"/>
                </div>
                <div className="form-group">
                    <label htmlFor="email" className="control-label">邮箱</label>
                    <input 
                        onChange={()=> this.setState({isBlocking:true})}
                        ref={input=>this.email = input} type="text" className="form-control"/>
                </div>
                <div className="form-group">
                    <input type="submit" className="btn btn-primary"/>
                </div>
            </form>
        )
    }
}
复制代码

components/UserList用户列表展示页

import  React , {Component} from 'react';
import  api  from './api';
import  {Link}  from '../react-router-dom';
import 'bootstrap/dist/css/bootstrap.css';
export default class UserList extends Component{
    state={
        users:[]
    }
    componentWillMount(){
        //读取用户
        let users = api.getUsers();
        this.setState({users});
    }
    handleDelete =(id)=>{
        let users = api.delUser(id);
        this.setState({users});
    }
    render(){
        return (
            <table className="table table-bordered">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>用户名</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    {
                        this.state.users.map(user => (                     
                            <tr key={user.id}>
                                    {/* <td><Link to={`/user/detail/${user.id}`}>{user.id}</Link></td> */}
                                    <td>
                                        <Link to={{pathname:`/user/detail/${user.id}`,state:user}}>
                                            {user.id}
                                        </Link>
                                    </td>                                    
                                    <td>{user.username}</td>
                                    <td>
                                        <button 
                                            onClick={()=>this.handleDelete(user.id)}
                                            className="btn btn-danger">删除</button>
                                    </td>
                            </tr>
                        ))
                    }
                </tbody>
            </table>
        )
    }
}
复制代码

components/menuLink菜单

import React,{Component} from 'react'
import {Route,Link} from '../react-router-dom';
import './MenuLink.css'
//渲染Route有三种方式 component render children
//什么时候用函数组件,如果不需要state尽量用函数组件,简洁,可控
export default  ({to,exact=false,label}) => (
	<Route path={to} exact={exact} children={
		({match}) => <li className={match? 'active':''}><Link to={to}>{label}</Link></li>
	}/>
)
复制代码

components/navHeader导航

import React,{Component} from 'react'
import {withRouter} from '../react-router-dom';
class NavHeader extends Component{
	render() {
		return (
			<div className="navbar-header">
				<a onClick={()=>this.props.history.push('/')} className="navbar-brand">管理系统</a>
			</div>
		)
	}
}
//NavHeader本来是一个普通的组件,跟Route没有关系
export default withRouter(NavHeader);
复制代码

components/UserDetail用户详细信息页

import  React , {Component} from 'react';
import  api  from './api';
export default class UserDetail extends Component{
    state={
        user:{}
    }
    componentDidMount(){
        let user = this.props.location.state.user;  
        console.log(user);
        if(!user){
            let id = this.props.match.params.id;
            user = api.getUser(id);
        }      
        this.setState({user});
    }
    render(){
        let user = this.state.user;
        return (
            <div>
                <p>ID:{user.id}</p>
                <p>用户名:{user.username}</p>
                <p>邮箱:{user.email}</p>
            </div>
        )
    }
}
复制代码

components/api.js存储添加用户使用的方法

let userApi = {
    //获取所有用户
    getUsers(){
        let usersStr = localStorage.getItem('users');
        return usersStr? JSON.parse(usersStr) :[];
    },

    //创建用户
    createUser(user){
        let users = userApi.getUsers();
        user.id = users.length>0? users[users.length-1].id + 1:1;
        users.push(user);
        localStorage.setItem('users',JSON.stringify(users));
    },
    getUser(id){
        return userApi.getUsers().find(user => user.id == id)
    },
    delUser(id){
        let users = userApi.getUsers().filter(user => user.id != id)
        localStorage.setItem('users',JSON.stringify(users));
        return users;        
    }
    
}
export default userApi;
复制代码

react-router-dom 源码

react-router-dom/index.js

import HashRouter from './HashRouter';
import Route from './Route';
import Link from './Link';
import Switch from './Switch';
import Redirect from './Redirect';
import Forward from './Forward';
import Back from './Back';
import withRoter from './withRoter';
import Prompt from './Prompt';
export {HashRouter,Route,Link,Switch,Redirect,Back,Forward,withRoter,Prompt}

复制代码

hashrouter.jshashrouter是个返回容器的组件

import  React , {Component} from 'react';
import { Provider } from './context';
//每当地址栏的锚点发生变化的时候,都需要重新配置
export default class HashRouter extends Component{
    state={
        location:{
            pathname:window.location.hash?window.location.hash.slice(1):'/'
        }
    }
    componentDidMount(){      
        window.addEventListener('hashchange',()=>{
            this.setState({
                location:{
                    ...this.state.location,
                    pathname:window.location.hash?window.location.hash.slice(1):'/'
                }
            })
        })
    }
    render(){
		let that=this;
        let value = {
            location:that.state.location,
            history:{
                //value会用Provider传递给Consumer,从而使用用这里定义方法
                push(to){
                    if(that.block){
                    //保证to是对象
                        let ok = window.confirm(that.block(typeof to === 'object' ? to:{pathname:to}));
                        if(!ok){
                            return;
                        }
                    }
                    // if(that.unblock){
                    //     window.confirm(that.block)
                    // }
                    if(typeof to === 'object'){
                        let {pathname,state} = to;
                        that.setState({
                            ...that.state,
                            location:{
                                ...that.state.location,
                                pathname,
                                state
                            }
                        },()=>{
                            window.location.hash = pathname;
                        })
                    }else{
                        window.location.hash = to;
                    }
                },
                goback(){
                    window.history.go(-1);
                },
                forward(){
                    window.history.go(1);
                },
                //弹窗方法
                block(message){
                    that.block = message;
                },
                unblock(message){
                    that.block = null;
                }
            }
        }
        return(
            // 想传递数据用Provider,接收数据用Consumer
            <Provider value={value}>
                {/* children就是Route */}
                {this.props.children}
            </Provider>
        )
    }

}
复制代码

react-router-dom/Route.js

import React,{Component} from 'react';
import {Consumer} from './context';
import pathToRegexp from 'path-to-regexp';
//每当地址栏的锚点发生变化的时候,都需要重新配置
//pathToRegexp('要转换的路径',参数,是否结束)
export default class Route extends Component{
    render() {
        return (
            <Consumer>
                {
                    // 接收属性
                    value => {
                        let {location: {pathname}}=value;// /user
                        let {path="/",component: Component,exact=false,render,children}=this.props;
                        let keys=[];//[id]
                        
                       //匹配路径
                       let regexp=pathToRegexp(path,keys,{end: exact});
                        let result=pathname.match(regexp);
                        let props={
							location: value.location,
							history:value.history
						}
                        if(result){
							let [,...values]=result;
                            keys=keys.map(key => key.name);
							let params = keys.reduce((memo,name,index) => {
								memo[name]=values[index];
								return memo;
							},{});
							//matchv {path,url,isExact,params }
                            let match={
								url:pathname,
								path,
								params
                            }
                            props.match=match;
                            //prototype组件,如果render存在,则执行
                            //渲染Route有三种方式 component render children
                            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;
                            }
                        }
                    }
                }
            </Consumer>
        )
    }
}

复制代码

/Link.js

import  React , {Component} from 'react';
import  {Consumer} from './context';
//每当地址栏的锚点发生变化的时候,都需要重新配置
export default class Link extends Component{
    render(){
        return(
            <Consumer>
                {
                    value => {
                        let {history: {push}} = value;
                        return <a onClick={()=>push(this.props.to)}>{this.props.children}</a>
                    }
                }
            </Consumer>
        )
    }

}
复制代码

switch.js匹配优化

import React,{Component} from 'react';
import {Consumer} from './context';
import pathToRegexp from 'path-to-regexp';
export default class Switch extends Component{
    render(){
        return (
            <Consumer>
                {
                    value =>{
                        let {location:{pathname}} = value;
                        let children = this.props.children;
                        for(let i=0;i<children.length;i++){
                            let child=children[i];
							//path的默认值为/ exact默认值为false,非精确匹配 /user/1
							let {path="/",exact=false}=child.props;//:id
                            let reg=pathToRegexp(path,[],{end: exact});
                            if(reg.test(pathname)){
                                return child;
                            }
                        }
                        return null;
                    }
                }
            </Consumer>
        )
    }
}
复制代码

Redirect.js重定向

import  React , {Component} from 'react';
import  {Consumer} from './context';
export default class Redirect extends Component{
    render(){
        return(
            <Consumer>
                {
                    value=>{
                        value.history.push(this.props.to);
                        return null;
                    }
                }
            </Consumer>
        )
    }
}
复制代码

Back.js

import  React , {Component} from 'react';
import {Consumer} from './context';

export default class Back extends Component{
    render(){
        return (
            <Consumer>
                {
                    value => {
                        return <a onClick={() => value.history.goback()}>返回</a>;
                    }
                }
            </Consumer>
        )
    }
}
复制代码

Forward.js

import  React , {Component} from 'react';
import {Consumer} from './context';

export default class Back extends Component{
    render(){
        return (
            <Consumer>
                {
                    value => {
                        return <a onClick={()=>value.history.forward()}>前进</a>;
                    }
                }
            </Consumer>
        )
    }
}
复制代码

withRoter将Component 和router建立链接

import React from 'react';
import { Route } from '../react-router-dom';
//<Route component = {Component} />是实例,不能被直接返回
export default (Component) => () => <Route component = {Component} />
复制代码

Prompt.js防止条状弹出框

import React,{Component} from 'react'
import {Consumer} from './context';
export default class Prompt extends Component{
	componentWillUnmount() {
		this.history.unblock();
	}
	render() {
		return (
			<Consumer>
				{
					value => {
						this.history=value.history;
						let {when,message}=this.props;
						if (when) {
							value.history.block(message);
						} else {
							value.history.unblock();
						}
					}
				}
			</Consumer>
		)
	}
}
复制代码

Consumer,provider原理

组件复用两大策略

  1. 高阶组件
  2. 函数组委子组件
import React,{Component} from 'react'
import ReactDOM from 'react-dom';
class Panel extends Component{
	render() {
		return (
			<div className="panel panel-default">
			<div className="panel-heading">头部</div>
			<div className="panel-body">
				{
					this.props.children('面板')
				}
			</div>
		</div>
		)
  }
}
ReactDOM.render(<div>
	<Panel>
	{(text) => <div style={{color:'red'}}>{`我是${text}`}</div>}
	</Panel>
	<Panel>
	{(text) => <div style={{color:'green'}}>{`我是${text}`}</div>}
</Panel>
</div>,document.querySelector('#root'));

复制代码

同理

import React,{Component} from 'react'
import ReactDOM from 'react-dom';
//let {Provider,Consumer}=React.createContext();
class Provider extends Component{
	render() {
		let value=this.props.value;
		//拿到consumer
		let children=this.props.children;
		children = children.map(child => {
		    //判断consumer类型
			if (child.type.toString().includes('Consumer'))
			//cloneElement可以返回就属性的props,也可以继承新的props
				return React.cloneElement(child,{value});
			else
				return child;
		});
		return <div>{children}</div>;
   }
}
class Consumer extends Component{
	render() {
		return this.props.children(this.props.value);
	}
}
ReactDOM.render((
<Provider value={1}>
		<Consumer>
			{
				value => <div>{value}</div>
			}
		</Consumer>
		<div>2</div>
</Provider>

),document.querySelector('#root'));

大功告成长城,不懂得留言哦~复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

一网打尽

一网打尽

[美]布拉德·斯通 / 李晶、李静 / 中信出版社 / 2014-1-15 / 49.00元

亚马逊最早起步于通过邮购来经营图书业务。但贝佐斯却不满足于仅做一名书商,他希望缔造亚马逊万货商店的神话——能提供海量的货源,并以超低的价格提供最具吸引力的便捷服务。为了实现这一诺言,他发展了一种企业文化,这种文化蕴含着执着的雄心与难以破解 的秘诀。亚马逊的这 一文化现在依旧在发扬光大。 布拉德·斯通非常幸运地得到采访亚马逊的前任和现任高管、员工以及贝佐斯本人、家人的机会,使我们第一次有机会深......一起来看看 《一网打尽》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具