react-router简易实现

react-router

利用context hashchange 实现router

大致思路如下

  • 利用context保证唯一路由信息,以及传递路由信息
  • 监听hashchange 修改当前路由信息

ps 以下代码都是基于hooks,而且只实现了HashRouter,如果要实现BrowserRouter就要监听popstate事件

//HashRouter.js
import React, { useState, useEffect } from 'react';
import Context from "./context";
const HashRouter = (props) => {
const [location,setLocation]=useState({
    pathname: window.location.hash.slice(1) || "/", // 获取浏览器地址栏中的hash值,如果不存在则默认为"/"
    query: undefined
})
useEffect(()=>{
    window.addEventListener("hashchange", () => { // 监听浏览器地址栏hash值变化
        setLocation(
            {
                ...location,
                pathname: window.location.hash.slice(1) // 更新pathname
            }
        )
    });
},[])
const currentRoute = {
    location: location, 
    history: { // 新增一个history对象用于实现当前路由的切换
        push: (to) => {
            if (typeof to === "object") { // 如果to是一个对象
                let { pathname, query } = to; // 取出pathname和query
                window.location.hash = pathname; // 更新浏览器hash值,触发浏览器hashchange事件
                location.query = query; // 更新query
            } else { // 修改浏览器地址栏的hash值
                window.location.hash = to; // 更新浏览器hash值
            }
        }
    }
}
return (
    <Context.Provider value={currentRoute}>
        {props.children}
    </Context.Provider>
);
};
export default HashRouter;

//Route.js
import React, {useContext} from "react";
import Context from "./context";
const Route = (props) => {
const context=useContext(Context)
const currentRoutePath = context.location.pathname; // 从上下文中获取到当前路由的路径
const {path, component:Component} = props; // 获取给Route组件传递的props属性
const props2 = {
    ...context
}
if (currentRoutePath === path) {
    return (
        <Component {...props2}></Component>
    );
}
return null
};
export default Route;

完整代码地址:https://github.com/dz333333/toy–router
参考:https://segmentfault.com/a/1190000022250917

从输入URL到页面展示 导航流程

导航流程

  • 用户输入url并回车
  • 浏览器进程检查url,组装协议,构成完整的url
  • 浏览器进程通过进程间通信(IPC)把url请求发给网络进程
  • 网络进程接收到url请求后检查本地缓存是否缓存了该请求资源,如果有则将该资源返回给浏览器进程
  • 如果没有,网络进程向web服务器发起http请求(网络请求)请求流程如下:
    • 进行DNS解析,获取服务器ip地址,端口
    • 利用ip地址和服务器建立tcp连接
    • 构建请求头信息
    • 发送请求头信息
    • 服务器响应后,网络进程接收响应头和响应信息,并解析响应内容
  • 网络进程解析响应流程
    • 检查状态码,如果是301/302,则需要重定向,从location字段中读取地址,重新进行第4步,如果是200,则继续处理请求
    • 200响应处理:检查响应类型Content-Type,如果是字节流类型,则将该请求提交给下载管理器,该导航流程结束,不再进行后续的渲染,如果是html则通知浏览器进程准备渲染进程准备进行渲染
  • 准备渲染进程
    • 浏览器进程检查当前url是否和之前打开的渲染进程根域名是否相同,如果相同,则复用之前的进程,如果不同,则开启新的渲染进程
  • 传输数据、更新状态
    • 渲染进程准备好后,浏览器向渲染进程发起 "提交文档"的消息,渲染进程接收到消息后和网络进程建立数据传输的"管道"
    • 渲染进程接受完数据后,向浏览器发送"确认提交"
    • 浏览器进程接收到确认消息后更新浏览器界面状态:包括了安全状态、地址栏的URL、前进后退的历史状态,并更新Web页面。

参考:李兵老师的浏览器工作原理与实践

js 实现 promise

//三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

function MyPromise(f) {
    /*
   有state,有处理结果result,then的执行栈
   */
    this.state = PENDING;
    this.result = null;
    //存放onFulfilled,onRejected回调函数
    this.callbacks = []


    let onFulfilled = value => transition(this, FULFILLED, value)
    let onRejected = reason => transition(this, REJECTED, reason)

    let ignore = false;
    let resolve = value => {
        if (ignore) return;
        ignore = true;
        resolvePromise(this, value, onFulfilled, onRejected)
    }
    let reject = reason => {
        if (ignore) return;
        ignore = true;
        onRejected(reason)
    }

    try {
        f(resolve, reject)
    } catch (error) {
        reject(error)
    }
}

/*
Peomise.then(()=>{'onfulfilled'},()=>{'onRejected'})
then必须返回一个Promise对象
*/
MyPromise.prototype.then = function (onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
        let callback = { onFulfilled, onRejected, resolve, reject }
        if (this.state === PENDING) {
            console.log('push');
            this.callbacks.push(callback)
        } else {
            //在执行上下文栈只包含平台代码之前,不能调用onfulfillment或onRejected。
            setTimeout(() => { handleCallback(callback, this.state, this.result) }, 0)
        }
    })
}
//在当前 promise 和下一个 promise 之间进行状态传递。
const handleCallback = (callback, state, result) => {
    let { onFulfilled, onRejected, resolve, reject } = callback;
    try {
        if (state === FULFILLED) {
            isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result)
        } else if (state === REJECTED) {
            isFunction(onRejected) ? resolve(onRejected(result)) : resolve(result)
        }
    } catch (error) {
        reject(error)
    }
}

//The Promise Resolution Procedure
/*
1.如果result是当前promise本身,就抛出TypeError
2.如果result是另外一个promise,就沿用他的state和result状态
3.如果result是一个thenable对象。是就先取出 then,再用 new Promise 去进入 The Promise Resolution Procedure 过程。
4.如果以上都不是,这个result成为当前promise的result
*/
const resolvePromise = (promise, result, onFulfilled, onRejected) => {
    if (result === promise) {
        let reason = new TypeError('Can not fulfill promise with itself')
        return onRejected(reason)
    }
    if (isPromise(result)) {
        return result.then(onFulfilled, onRejected)
    }
    if (isThenable(result)) {
        try {
            let then = result.then;
            if (isFunction(then)) {
                return new MyPromise(then.bind(result)).then(onFulfilled, onRejected)
            }
        } catch (error) {
            return onRejected(error)
        }
    }
    onFulfilled(result)
}

const transition = (promise, state, result) => {
    //state一旦更改为不是PENDING的状态后不可更改
    if (promise.state !== PENDING) {
        return;
    }
    promise.state = state;
    promise.result = result;
    //状态转换完成后,清空执行栈
    setTimeout(() => handleCallbacks(promise.callbacks, state, result), 0)
}

const handleCallbacks = (callbacks, state, result) => {
    while (callbacks.length) handleCallback(callbacks.shift(), state, result)
}

const isFunction = obj => typeof obj === 'function'
const isObject = obj => !!(obj && typeof obj === 'object')
const isThenable = obj => (isFunction(obj) || isObject(obj)) && 'then' in obj
const isPromise = promise => promise instanceof MyPromise



const promise1 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        console.log('promise');
        resolve()
    }, 1000);
})
promise1.then(() => {
    console.log('aaa');
    return new MyPromise(()=>{setTimeout(() => {
        console.log(3); 
    }, 1000)})
})