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'));

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

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

查看所有标签

猜你喜欢:

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

Hit Refresh

Hit Refresh

Satya Nadella、Greg Shaw / HarperBusiness / 2017-9-26 / USD 20.37

Hit Refresh is about individual change, about the transformation happening inside of Microsoft and the technology that will soon impact all of our lives—the arrival of the most exciting and disruptive......一起来看看 《Hit Refresh》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具