react源码必知必会

66 阅读18分钟

react基础

import React, { useState,useEffect,useLayoutEffect,useRef,useImperativeHandle} from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
  document.getElementById('root')
);
function Child(props,forwardRef){
  const inputRef = useRef(null);//3
  const [count,setCount] = useState(0);//4
  useImperativeHandle(forwardRef,()=>({
    myFocus(){
      inputRef.current.focus();
    }
  }));
  return (
    <div>
      <input 
      type="text" 
      ref={inputRef} />
    <p>Child:{count}</p>
    <button onClick={()=>{
      debugger
      setCount(count+1)
    }}>+</button>
    </div>
  )
}
const ForwardChild = React.forwardRef(Child);
function Parent(){
  const [number,setNumber] = useState(0);//0=>1
  const inputRef = useRef(null);//1=>2
  const getFocus = ()=>{
    inputRef.current.myFocus();
   // inputRef.current.remove();
  }
  return (
    <div>
      <ForwardChild ref={inputRef}/>
      <button onClick={getFocus}>获得焦点</button>
      <p>{number}</p>
      <button onClick={()=>{
        setNumber(number+1)
      }}>+</button>
    </div>
  )

}
DOMRoot.render(<Parent />);

index.js

import { REACT_FORWARD_REF_TYPE, REACT_TEXT,REACT_PROVIDER,REACT_CONTEXT,REACT_MEMO } from "../constant";
import { addEvent } from '../event';
let hookStates = [];//[0,{myFocus},{current:input},0] 4个元素
let hookIndex = 0;
let scheduleUpdate;

function mount(vdom, container) {
    //传进去虚拟DOM,返回真实DOM
    const newDOM = createDOM(vdom);
    if(newDOM){
        container.appendChild(newDOM);
        if (newDOM.componentDidMount) {
            newDOM.componentDidMount()
        }
    }
}
export function useImperativeHandle(ref,handler){
    ref.current = handler()
}
export function useRef(initialState){
    hookStates[hookIndex]=hookStates[hookIndex]||{current:initialState};
    return hookStates[hookIndex++];
}
export function useLayoutEffect(callback,deps){
    const currentIndex = hookIndex;
    if(hookStates[currentIndex]){
     let [destroy,lastDeps] = hookStates[hookIndex];
     let same = deps && deps.every((item,index)=>item === lastDeps[index]);
     if(same){
         hookIndex++;
     }else{
         destroy?.();
         queueMicrotask(()=>{//queue宏任务 setTimeout模拟
             //执行callback,保存返回的destroy销毁函数
             hookStates[currentIndex]=[callback(),deps];
         });
         hookIndex++;
     }
    }else{
        queueMicrotask(()=>{
         //执行callback,保存返回的destroy销毁函数
         hookStates[currentIndex]=[callback(),deps];
     });
     hookIndex++;
    }
 }
export function useEffect(callback,deps){
   const currentIndex = hookIndex;
   if(hookStates[currentIndex]){
    let [destroy,lastDeps] = hookStates[hookIndex];
    let same = deps && deps.every((item,index)=>item === lastDeps[index]);
    if(same){
        hookIndex++;
    }else{
        destroy?.();
        setTimeout(()=>{
            //执行callback,保存返回的destroy销毁函数
            hookStates[currentIndex]=[callback(),deps];
        });
        hookIndex++;
    }
   }else{
    setTimeout(()=>{
        //执行callback,保存返回的destroy销毁函数
        hookStates[currentIndex]=[callback(),deps];
    });
    hookIndex++;
   }
}
export function useContext(context){
    return context._currentValue;
}
export function useReducer(reducer,initialState){
    const oldState = hookStates[hookIndex]=hookStates[hookIndex]||initialState;
    const currentIndex = hookIndex;
    function dispatch(action){
        let newState = reducer?reducer(oldState,action):typeof action === 'function'?action(oldState):action;
        hookStates[currentIndex]=newState;
        scheduleUpdate();
    }
    return [hookStates[hookIndex++],dispatch];
}
export function useState(initialState){
    return useReducer(null,initialState);
   /*  const oldState = hookStates[hookIndex]=hookStates[hookIndex]||initialState;
    const currentIndex = hookIndex;
    function setState(action){
        let newState = typeof action === 'function'?action(oldState):action;
        hookStates[currentIndex]=newState;
        scheduleUpdate();
    }
    return [hookStates[hookIndex++],setState]; */
}
export function useMemo(factory,deps){
    //第一次挂载的时候,肯定值是空的
    if(hookStates[hookIndex]){
        let [lastMemo,lastDeps] = hookStates[hookIndex];
        let same = deps.every((item,index)=>item === lastDeps[index]);
        if(same){//新的依赖数组和老的依赖数组完全 相等
            hookIndex++;
            return lastMemo;
        }else{
            const newMemo = factory();
            hookStates[hookIndex++]=[newMemo,deps];
            return newMemo;
        }
    }else{
        const newMemo = factory();
        hookStates[hookIndex++]=[newMemo,deps];
        return newMemo;
    }
}
export function useCallback(callback,deps){
    //第一次挂载的时候,肯定值是空的
    if(hookStates[hookIndex]){
        let [lastCallback,lastDeps] = hookStates[hookIndex];
        let same = deps.every((item,index)=>item === lastDeps[index]);
        if(same){//新的依赖数组和老的依赖数组完全 相等
            hookIndex++;
            return lastCallback;
        }else{
            hookStates[hookIndex++]=[callback,deps];
            return callback;
        }
    }else{
        hookStates[hookIndex++]=[callback,deps];
        return callback;
    }
}

//把虚拟DOM变成真实的DOM
function createDOM(vdom) {
    const { type, props, ref } = vdom;
    let dom;
    if (type && type.$$typeof === REACT_MEMO) {
        return mountMemoComponent(vdom);
    }else if (type && type.$$typeof === REACT_CONTEXT) {
        return mountConsumerComponent(vdom);
    }else if (type && type.$$typeof === REACT_PROVIDER) {
        return mountProviderComponent(vdom);
    }
    else if (type && type.$$typeof === REACT_FORWARD_REF_TYPE) {
        return mountForwardComponent(vdom);
    } else if (type === REACT_TEXT) {
        dom = document.createTextNode(props);
    } else if (typeof type == 'function') {
        if (type.isReactComponent) {
            return mountClassComponent(vdom);
        } else {
            return mountFunctionComponent(vdom);
        }
    } else {
        dom = document.createElement(type);//原生组件
    }
    //判断属性的类型,因为对于元素的话,props是对象,对于文本节点而言,它的props就是文本本身
    if (typeof props === 'object') {
        updateProps(dom, {}, props);
        if (props.children) {
            //如果是独生子的话,把独生子的虚拟DOM转换真实DOM插入到DOM节点上
            if (typeof props.children === 'object' && props.children.type) {
                props.children.mountIndex = 0;
                mount(props.children, dom);
            } else if (Array.isArray(props.children)) {
                reconcileChildren(props.children, dom);
            }
        }
    }
    //在根据虚拟DOM创建真实DOM成功后,就可以建立关系
    vdom.dom = dom;
    //如果此虚拟DOM上有ref属性,则把ref.current的值赋成真实DOM
    if (ref) ref.current = dom;
    return dom;
}
function mountMemoComponent(vdom){
    const { type:{type:functionComponent}, props } = vdom;
    const renderVdom = functionComponent(props);
    if(!renderVdom) return null;
    vdom.oldRenderVdom = renderVdom;
    return createDOM(renderVdom);
}
function mountConsumerComponent(vdom){
    const { type, props } = vdom;
    const context = type._context;
    const renderVdom = props.children(context._currentValue);
    if(!renderVdom) return null;
    vdom.oldRenderVdom = renderVdom;
    return createDOM(renderVdom)
}
function mountProviderComponent(vdom){
    const { type, props } = vdom;
    const context = type._context;
    context._currentValue = props.value;
    let renderVdom = props.children;
    if(!renderVdom) return null;
    vdom.oldRenderVdom = renderVdom;
    return createDOM(renderVdom)
}
function mountForwardComponent(vdom) {
    const { type, props, ref } = vdom;
    //type.render=就是TextInput
    const renderVdom = type.render(props, ref);
    if(!renderVdom) return null;
    vdom.oldRenderVdom = renderVdom;
    return createDOM(renderVdom);
}
function mountClassComponent(vdom) {
    const { type: ClassComponent, props, ref } = vdom;
    var defaultProps = ClassComponent.defaultProps;
    var resolvedProps = { ...defaultProps, ...props }
    const classInstance = new ClassComponent(resolvedProps);//class ClassComponent
    if(ClassComponent.contextType){
        classInstance.context = ClassComponent.contextType._currentValue;
    }
    //让虚拟DOM的classInstance属性指向此类的实例
    vdom.classInstance = classInstance;
    if (ref) ref.current = classInstance;
    if (classInstance.UNSAFE_componentWillMount) {
        classInstance.UNSAFE_componentWillMount();
    }
    const renderVdom = classInstance.render();
    if(!renderVdom) return null;
    //在获取render的渲染结果后把此结果放到classInstance.oldRenderVdom进行暂存
    classInstance.oldRenderVdom = renderVdom;
    const dom = createDOM(renderVdom);
    if (classInstance.componentDidMount) {
        //把componentDidMount方法暂存到dom对象上
        dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
    }
    return dom;
}
function mountFunctionComponent(vdom) {
    const { type, props } = vdom;//FunctionComponent  {title:'world'}
    const renderVdom = type(props);
    if(!renderVdom) return null;
    vdom.oldRenderVdom = renderVdom;
    return createDOM(renderVdom);
}
function reconcileChildren(childrenVdom, parentDOM) {
    for (let i = 0; i < childrenVdom.length; i++) {
        childrenVdom[i].mountIndex=i;
        mount(childrenVdom[i], parentDOM);
    }
}
/**
 * 更新DOM元素的属性
 * 1.把新的属性全部赋上去
 * 2.把老的属性在新的属性对象没有删除掉
 */
function updateProps(dom, oldProps = {}, newProps = {}) {
    for (let key in newProps) {
        //children属性会在后面单独处理
        if (key === 'children') {
            continue;
        } else if (key === 'style') {
            //把样式对象上的所有属性都赋给真实DOM
            let styleObject = newProps[key];
            for (const attr in styleObject) {
                dom.style[attr] = styleObject[attr];
            }
        } else if (/^on[A-Z].*/.test(key)) {
            //dom[key.toLowerCase()]=newProps[key];
            addEvent(dom, key, newProps[key]);
        } else {
            //如果是其它属性,则直接赋值
            dom[key] = newProps[key];
        }
    }
    for (let key in oldProps) {
        if (!newProps.hasOwnProperty(key)) {
            dom[key] = null;
        }
    }
}
//class>function>class>function
export function findDOM(vdom) {//Class Counter虚拟DOM {type:Counter,classInstance:CounterInstance}
    if (!vdom) return null;
    //如果vdom对应原生组件的的话肯定有dom属性指向真实DOM
    if(vdom.dom){
        return vdom.dom;
    }else{
        //否则 可能是类组件或者说函数组件 oldRenderVdom {type:div}
        const renderVdom = vdom.classInstance?vdom.classInstance.oldRenderVdom:vdom.oldRenderVdom;
        return findDOM(renderVdom);
    }
}
/**
 * 比较新的和老的虚拟DOM ,实现DOM-DIFF
 * @param {*} parentDOM 老的父真实DOM
 * @param {*} oldVdom   老的虚拟DOm
 * @param {*} newVdom   新的虚拟DOM
 * @param {*} nextDOM   newVdom的离它最近的下一个真实DOM元素
 */
export function compareTwoVdom(parentDOM, oldVdom, newVdom, nextDOM) {
    //如果老的虚拟DOM和新的虚拟DOM都是null或undefined
    if (!oldVdom && !newVdom) {
        return;
        //如果老的虚拟DOM有值,并且新的虚拟DOM为null
    } else if (oldVdom && !newVdom) {
        unMountVdom(oldVdom);
    } else if (!oldVdom && newVdom) {
        //创建新的虚拟DOM对应的真实DOM
        let newDOM = createDOM(newVdom);
        if (nextDOM) {
            parentDOM.insertBefore(newDOM, nextDOM);
        } else {
            parentDOM.appendChild(newDOM);
        }
        if (newDOM.componentDidMount) {
            newDOM.componentDidMount();
        }
    } else if (oldVdom && newVdom && (oldVdom.type !== newVdom.type)) {
        //如果虽然说老的有,新的也有,但是类型不同,则也不能复用老的真实DOM节点
        unMountVdom(oldVdom)
        let newDOM = createDOM(newVdom)
        if (nextDOM) {
            parentDOM.insertBefore(newDOM, nextDOM);
        } else {
            parentDOM.appendChild(newDOM);
        }
        if (newDOM.componentDidMount) {
            newDOM.componentDidMount();
        }
    }else{//如果有老的虚拟DOM,也有新的虚拟DOM,并且类型是一样的,就可以复用老的真实DOM
        updateElement(oldVdom,newVdom);
    }
}
/**
 * 更新元素
 * @param {*} oldVdom 老的虚拟DOM
 * @param {*} newVdom 新的虚拟DOM
 */
function updateElement(oldVdom,newVdom){
    //如果新老的虚拟DOM都是文本节点的话
    if(oldVdom.type.$$typeof === REACT_FORWARD_REF_TYPE){
        updateForwardComponent(oldVdom,newVdom);
    }else if(oldVdom.type.$$typeof === REACT_MEMO){
    updateMemoComponent(oldVdom,newVdom);
   }else if(oldVdom.type.$$typeof === REACT_PROVIDER){
     updateProviderComponent(oldVdom,newVdom);
   }else if(oldVdom.type.$$typeof === REACT_CONTEXT){
    updateContextComponent(oldVdom,newVdom);
   }
   else if(oldVdom.type === REACT_TEXT){
    //复用老DOM节点
    let currentDOM = newVdom.dom = findDOM(oldVdom);
    if(oldVdom.props !== newVdom.props){
        currentDOM.textContent = newVdom.props;
    }
    return;
    //如果是原生组件的话,就是指span div p
   }else if(typeof oldVdom.type === 'string'){
    let currentDOM = newVdom.dom = findDOM(oldVdom);
    //用新的虚拟DOM属性更新老的真实DOM
    updateProps(currentDOM,oldVdom.props,newVdom.props);
    updateChildren(currentDOM,oldVdom.props.children,newVdom.props.children);
   }else if(typeof oldVdom.type === 'function'){//如果类型是一个函数的话,说明肯定是一个组件
    if(oldVdom.type.isReactComponent){//如果是类组件的话
        updateClassComponent(oldVdom,newVdom);
    }else{
        updateFunctionComponent(oldVdom,newVdom);//如果是函数组件的话
    }
   }
}
function updateForwardComponent(oldVdom,newVdom){
    let currentDOM = findDOM(oldVdom);
    if(!currentDOM)return;
    let parentDOM = currentDOM.parentNode;
    const { type, props,ref } = newVdom;
    const newRenderVdom = type.render(props,ref);
    //比较新旧虚拟DOM
    compareTwoVdom(parentDOM,oldVdom.oldRenderVdom,newRenderVdom);
    //还要把newRenderVdom保存起来
    newVdom.oldRenderVdom=newRenderVdom;
}
function updateMemoComponent(oldVdom,newVdom){
  let {type:{compare,type:functionComponent}} = oldVdom;
  //比较新的和老的属性对象,如果是一样的,就不render
  if(compare(oldVdom.props,newVdom.props)){
    //则不重新渲染,直接复用老的渲染的虚拟DOM
    newVdom.oldRenderVdom=oldVdom.oldRenderVdom;
  }else{
    const oldDOM = findDOM(oldVdom);
    const parentDOM = oldDOM.parentNode;
    const renderVdom = functionComponent(newVdom.props)
    compareTwoVdom(parentDOM,oldVdom.oldRenderVdom,renderVdom);
    newVdom.oldRenderVdom=renderVdom;
  }
}
function updateProviderComponent(oldVdom,newVdom){
    //先获取父DOM节点
    let parentDOM = findDOM(oldVdom).parentNode;
    let {type,props} = newVdom;
    let context = type._context;
    context._currentValue=props.value;
    let renderVdom  = props.children;
    compareTwoVdom(parentDOM,oldVdom.oldRenderVdom,renderVdom);
    newVdom.oldRenderVdom = renderVdom;
}
function updateContextComponent(oldVdom,newVdom){
    //先获取父DOM节点
    let parentDOM = findDOM(oldVdom).parentNode;
    let {type,props} = newVdom;
    let context = type._context;
    let renderVdom  = props.children(context._currentValue);
    compareTwoVdom(parentDOM,oldVdom.oldRenderVdom,renderVdom);
    newVdom.oldRenderVdom = renderVdom;
}
function updateFunctionComponent(oldVdom,newVdom){
    let currentDOM = findDOM(oldVdom);
    if(!currentDOM)return;
    //获取当前的真实DOM的父节点
    let parentDOM = currentDOM.parentNode;
    //重新执行函数获取新的虚拟DOM
    const { type, props } = newVdom;//FunctionComponent  {title:'world'}
    const newRenderVdom = type(props);
    //比较新旧虚拟DOM
    compareTwoVdom(parentDOM,oldVdom.oldRenderVdom,newRenderVdom);
    //还要把newRenderVdom保存起来
    newVdom.oldRenderVdom=newRenderVdom;
}
function updateClassComponent(oldVdom,newVdom){
    //复用老的类组件的实例
    let classInstance = newVdom.classInstance = oldVdom.classInstance;
    if(classInstance.UNSAFE_componentWillReceiveProps){
        classInstance.UNSAFE_componentWillReceiveProps(newVdom.props);
    }
    classInstance.updater.emitUpdate(newVdom.props);
}
/**
 * 更新它的子节点
 * @param {*} parentDOM 父真实DOM
 * @param {*} oldVChildren 老的子虚拟DOM
 * @param {*} newVChildren 新的子虚拟DOM
 */
function updateChildren(parentDOM,oldVChildren,newVChildren){
    oldVChildren = (Array.isArray(oldVChildren)?oldVChildren:oldVChildren?[oldVChildren]:[]);
    newVChildren = (Array.isArray(newVChildren)?newVChildren:newVChildren?[newVChildren]:[]);
    //存放老节点的map
    const keyedOldMap = {}
    //上一个放置好的,不需要移动元素的索引
    let lastPlacedIndex = -1;    
    oldVChildren.forEach((oldVChild,index)=>{
        //如果用户提供了key就用用户提供的key,否则就使用index索引
        let oldKey = (oldVChild.key)?oldVChild.key:index;
        keyedOldMap[oldKey]=oldVChild;
    })
    //创建一个补丁包,存放将要进行的操作
    let patch = [];
    //遍历新的虚拟DOM数组
    newVChildren.forEach((newVChild,index)=>{
        newVChild.mountIndex = index;
        let newKey = newVChild.key?newVChild.key:index;
        //用新的key去老的map中找找有没有可复用的虚拟DOM
        let oldVChild = keyedOldMap[newKey];
        //如果找到了就可以进行复用了
        if(oldVChild){
            //如果找到了就直接进行更新
            updateElement(oldVChild,newVChild);
            //再判断此节点是否需要移动
            //如果此可复用的老节点的挂载索引比上一个不需要移动的节点的索引要小的话,那就需要移动 
            if(oldVChild.mountIndex<lastPlacedIndex){// 1 < 4
                patch.push({
                    type:'MOVE',
                    oldVChild,//移动老B 1
                    newVChild,
                    mountIndex:index //3
                });
            }
            //把可以复用的老的虚拟DOM节点从map中删除
            delete keyedOldMap[newKey]; 
            //更新lastPlacedIndex为老的lastPlacedIndex和oldVChild.mountIndex中的较大值
            lastPlacedIndex=Math.max(lastPlacedIndex,oldVChild.mountIndex);
        }else{
            patch.push({
                type:'PLACEMENT',
                newVChild,
                mountIndex:index
            });
        }
    });
    //执行patch中的操作
    //获取所有需要移动的元素 ['B']
    const moveVChildren = patch.filter(action=>action.type === 'MOVE').map(action=>action.oldVChild);
    //获取所有留在map中的老的虚拟DOM加上移动的老的虚拟DOM
    //直接从老的真实DOM中删除 D F B
    Object.values(keyedOldMap).concat(moveVChildren).forEach(oldVChild=>{
        let currentDOM = findDOM(oldVChild);
        parentDOM.removeChild(currentDOM);
    });
    //patch =[{type:'MOVE',B},{type:'PLACEMENT',G}]
    patch.forEach(action=>{
        const {type,oldVChild,newVChild,mountIndex} = action;
        let oldTrueDOMs = parentDOM.childNodes;//获取老的真实DOM的集合[A,C,E]
        if(type === 'PLACEMENT'){
            //先根据新的虚拟DOM创建新的真实DOM
            let newDOM = createDOM(newVChild);
            const oldTrueDOM = oldTrueDOMs[mountIndex];
            if(oldTrueDOM){
                //如果要挂载的索引处有真实DOM,就是插到它的前面
                parentDOM.insertBefore(newDOM,oldTrueDOM);
            }else{
                parentDOM.appendChild(newDOM);
            }
        }else if(type === 'MOVE'){
            let oldDOM = findDOM(oldVChild);//B真实DOM
            let oldTrueDOM = oldTrueDOMs[mountIndex];//获取挂载索引处现在的真实DOM
            if(oldTrueDOM){
                //如果要挂载的索引处有真实DOM,就是插到它的前面
                parentDOM.insertBefore(oldDOM,oldTrueDOM);
            }else{
                parentDOM.appendChild(oldDOM);
            }
        }
    });

}
function unMountVdom(vdom) {
    const { props, ref } = vdom;
    //获取此虚拟DOM对应的真实DOM
    let currentDOM = findDOM(vdom);
    //如果类的实例上componentWillUnmount方法则执行它
    if (vdom.classInstance && vdom.classInstance.componentWillUnmount) {
        vdom.classInstance.componentWillUnmount();
    }
    if (ref) {
        ref.current = null;
    }
    if (props.children) {
        const children = Array.isArray(props.children) ? props.children : [props.children];
        children.forEach(unMountVdom);
    }
    //如果此虚拟DOM对应了真实DOM,则把此真实DOM进行删除
    if (currentDOM) currentDOM.remove();
}

class DOMRoot {
    constructor(container) {
        this.container = container;
    }
    render(vdom) {
        mount(vdom, this.container);
        scheduleUpdate = ()=>{
            hookIndex=0;
            compareTwoVdom(this.container,vdom,vdom);
        }
    }
}

function createRoot(container) {
    return new DOMRoot(container);
}
const ReactDOM = {
    createRoot,
    createPortal:function(vdom,container){
        mount(vdom,container);
    }
}
export default ReactDOM;

addevent

import {updateQueue} from './Component';
/**
 * 给DOM元素添加事件处理函数
 * @param {*} dom 要添加事件的DOM元素
 * @param {*} eventType 事件的类型 onClick onClickCapture
 * @param {*} handler 事件处理函数
 */
export function addEvent(dom,eventType,handler){
    //判断dom元素上有没有store属性,如果有直接返回,如果没能则赋值为空对象然后返回
    let store = dom.store ||(dom.store = {});
    //向store中存放属性和值,属性是事件类型onclick 值是一个事件函数函数
    //onClick  onClickCapture
    store[eventType.toLowerCase()]= handler;
    const eventName = eventType.toLowerCase();
  /*   if(!document[eventName]){
        document[eventName] =dispatchEvent;
    } */
    const name = eventName.replace(/Capture$/,'').slice(2);
    if(!document[name]){
        //其实在React17 前后此逻辑是有改变的
        //在React17以前是不合理的,此方法只在冒泡阶段执行,并且直接模拟捕获和冒泡二个流程
        //此event是浏览器传进来的
        document.addEventListener(eventName.slice(2).toLowerCase(),(event)=>{
            dispatchEvent(event,true);
        },true);
        document.addEventListener(eventName.slice(2).toLowerCase(),(event)=>{
            dispatchEvent(event,false);
        },false);
        document[name]=true;
    } 
}
function dispatchEvent(event,isCapture){
    //为什么要做事件委托,为什么要把子DOM的事件全部委托给父类
    //1.为了减少绑定,提高性能 2.统一进行事件实现合成事件
    //target事件源 button type是件名称 click
    const {target,type} = event;
    let eventType = `on${type}`;//onclick
    let eventTypeCapture = `on${type}capture`;//onclick
    let syntheticEvent= createSyntheticEvent(event);
    updateQueue.isBatchingUpdate=true;
    //为了跟源码一样,我们需要自己手工再次模拟捕获和冒泡的全过程
    //我们需要先记录一栈结构
    let targetStack = [];
    let currentTarget=target;//button
    while(currentTarget){
        targetStack.push(currentTarget);//button div#counter div#root document
        currentTarget=currentTarget.parentNode;
    }
    //处理捕获阶段
    if(isCapture){
        for(let i=targetStack.length-1;i>=0;i--){
            const currentTarget = targetStack[i];
            let {store} = currentTarget;
            let handler = store&&store[eventTypeCapture];
            handler&&handler(syntheticEvent);
        }
    }else{
        //处理冒泡阶段
        for(let i=0;i<targetStack.length;i++){
            const currentTarget = targetStack[i];
            let {store} = currentTarget;
            let handler = store&&store[eventType];
            handler&&handler(syntheticEvent);
            if(syntheticEvent.isPropagationStopped){
                break;
            }
        }
    }
    updateQueue.batchUpdate();
}
/**
 * 根据原生事件对象创建合成事件
 * 1.为了实现兼容性处理
 * @param {*} event 
 */
function createSyntheticEvent(nativeEvent){//click button
  let syntheticEvent={};
  for(let key in nativeEvent){
    let value = nativeEvent[key];
    if(typeof value ==='function')value = value.bind(nativeEvent);
    syntheticEvent[key]=value;
  }
  syntheticEvent.nativeEvent=nativeEvent;
  //是否已经阻止了默认事件
  syntheticEvent.isDefaultPrevented = false;
  syntheticEvent.preventDefault = preventDefault;
  syntheticEvent.isPropagationStopped = false;
  syntheticEvent.stopPropagation = stopPropagation;
  return syntheticEvent;
}
function preventDefault(){
   this.isDefaultPrevented = true;
   const nativeEvent = this.nativeEvent;
   if(nativeEvent.preventDefault){
    nativeEvent.preventDefault();
   }else{//IE
    nativeEvent.returnValue = false;
   }
}
function stopPropagation(){
    this.isPropagationStopped = true;
    const nativeEvent = this.nativeEvent;
    if(nativeEvent.stopPropagation){
     nativeEvent.stopPropagation();
    }else{//IE
     nativeEvent.cancelBubble = true;
    }
 }

react


import { REACT_ELEMENT ,REACT_FORWARD_REF_TYPE,REACT_PROVIDER,REACT_CONTEXT,REACT_MEMO} from './constant';
import {wrapToVdom,shallowEqual} from './utils';
import {Component} from './Component';
import * as hooks from './react-dom/client';
/**
 * 根据参数,返回一个React元素
 * @param {*} type 元素的类型 div span
 * @param {*} config 配置对象 className style
 * @param {*} children 后面所有参数都是children,children可能有,也可能没有,可能有一个,也可能有多个
 */
function createElement(type,config,children){
    let ref;
    let key;
    if(config){
        delete config.__source;
        delete config.__self;
        ref = config.ref;//是用来引用此元素的
        delete config.ref;
        key = config.key;//key是用来标记一个父亲的唯一儿子的
        delete config.key;
    }
    let props = {...config};
    //如果参数数量大于3说明有儿子,并且儿子数量大于一个
    if(arguments.length>3){
        props.children = Array.prototype.slice.call(arguments,2).map(wrapToVdom)
    //如果说等于3,那就是只有一个儿子
    }else if(arguments.length===3){
        props.children = wrapToVdom(children);
    }
    return {
        $$typeof:REACT_ELEMENT,//默认是元素类型
        type,//span div
        props,//属性
        ref,
        key
    }
}
function createRef(){
    return {current:null};
}
//其实函数组件本质上就是render方法,就是接收属性,返回react元素
function forwardRef(render){
   return {
    $$typeof:REACT_FORWARD_REF_TYPE,
    render// 其实就是原来的函数组件那个函数
   }
}
function createContext(){
    //_currentValue代表当前的值
    let context = {_currentValue:undefined};
    context.Provider = {
        $$typeof:REACT_PROVIDER,//供应商
        _context:context
    };
    context.Consumer={
        $$typeof:REACT_CONTEXT,//上下文
        _context:context
    }
    return context;
}
function cloneElement(element,newProps,children){
    let props = {...element.props,...newProps};
    if(arguments.length>3){
        props.children = Array.prototype.slice.call(arguments,2).map(wrapToVdom)
    //如果说等于3,那就是只有一个儿子
    }else if(arguments.length===3){
        props.children = wrapToVdom(children);
    }
    return {
        ...element,
        props
    }
}
class PureComponent extends Component{
    //重写scu方法,如果属性变了或者状态变以就会返回true,如果都没变才会返回false
    shouldComponentUpdate(newProps,nextState){
        return !shallowEqual(this.props,newProps)||!shallowEqual(this.state,nextState)
    }

}
function memo(type,compare=shallowEqual){
    return {
        $$typeof:REACT_MEMO,
        type,//函数组件
        compare//比较方法
    }
}
const React = {
    createElement,
    Component,
    createRef,
    forwardRef,
    createContext,
    cloneElement,
    PureComponent,
    memo,
    ...hooks
}
export * from './react-dom/client';
export default React;

component

import {findDOM,compareTwoVdom} from './react-dom/client';
//这是一个更新队列
export let updateQueue = {
  isBatchingUpdate:false, //这是一个是否是批量更新的标识,默认是非批量的,是同步的
  updaters :new Set(),//更新的集合
  batchUpdate(){
    updateQueue.isBatchingUpdate=false;
    for(const updater of updateQueue.updaters){
      updater.updateComponent();
    }
    updateQueue.updaters.clear();
  }
}
class Updater{
  //每个更新器会保存一个组件类的实例
  constructor(classInstance){
    this.classInstance = classInstance;
    //用来存放更新状态
    this.pendingStates = [];
    this.callbacks = [];
  }
  flushCallbacks(){
    if(this.callbacks.length>0){
      //如果没有使用箭头函数,那这里的
      this.callbacks.forEach((callback)=>callback());
      this.callbacks.length=0;
    }
  }
  addState(partialState,callback){
    this.pendingStates.push(partialState);
    if(typeof callback  === 'function'){
      this.callbacks.push(callback);
    }
    //发射更新有两种,一种是更新属性,一种是更新状态
    this.emitUpdate();
  }
  emitUpdate(nextProps){
    //保存传过来的新属性
    this.nextProps = nextProps;
    //如果需要批量更新
    if(updateQueue.isBatchingUpdate){
      //则不要直接更新组件,而是先把更新器添加到updaters里去进行暂存
      updateQueue.updaters.add(this);
    }else{
      this.updateComponent();
    }
    
  }
  updateComponent(){
    //获取等生效的状态数组和类的实例
    const {pendingStates,nextProps,classInstance}= this;
    //如果有正在等待生效的状态
    if(nextProps||pendingStates.length>0){
        shouldUpdate(classInstance,nextProps,this.getState());
    }
  }//根据等待生效的状态数组组计算新的状态
  getState(){
    const {pendingStates,classInstance}= this;
    //先获取类的实例上的老udy
    let {state}= classInstance;
    pendingStates.forEach((partialState)=>{
      if(typeof partialState === 'function'){
        partialState=partialState(state);
      }
      state={...state,...partialState}
    });
    pendingStates.length=0;
    return state;
  }
}
function shouldUpdate(classInstance,nextProps,nextState){
  //是否要更新
    let willUpdate = true;
    //如果有shouldComponentUpdate方法,并且返回值为false的话
    if(classInstance.shouldComponentUpdate && (!classInstance.shouldComponentUpdate(
      nextProps,
      nextState
    ))){
      willUpdate=false
    }
    //如果要更新,并且存在组件将要更新的方法
    if(willUpdate && classInstance.UNSAFE_componentWillUpdate){
      classInstance.UNSAFE_componentWillUpdate();
    }
    //不管最终要不要更新页面上的组件,都会把新的状态传送给classInstance.state
    classInstance.state = nextState;
    if(nextProps){
      classInstance.props = nextProps;
    }
    //让组件强制更新
    if(willUpdate)
      classInstance.forceUpdate();
}
export class Component{
    //给类Component添加了一个静态属性 isReactComponent=true
    static isReactComponent = true
    constructor(props){
        this.props = props;
        this.state = {};
        //每个类会有一个更新器的实例
        this.updater = new Updater(this);
    }
    setState(partialState,callback){
        this.updater.addState(partialState,callback);
    }
    forceUpdate(){
       //先获取老的虚拟DOM,再计算新的虚拟DOM,找到新老虚拟DOM的差异,把这些差异更新到真实DOM上
      //获取老的虚拟DOM div#counter
       let oldRenderVdom = this.oldRenderVdom;
       //获取到此组件对应的老的真实DOM,才的DIV
       const oldDOM = findDOM(oldRenderVdom);
       if(this.constructor.contextType){
        this.context = this.constructor.contextType._currentValue;
       }
       const {getDerivedStateFromProps} = this.constructor;
       if(getDerivedStateFromProps){ //可以替代掉以前componentWillReceiveProps
         let newState = getDerivedStateFromProps(this.props,this.state);
         if(newState){
          this.state = {...this.state,...newState};
         }
       }
       //根据新的状态计算新的虚拟DOM
       let newRenderVdom = this.render();
       let snapshot = this.getSnapshotBeforeUpdate&&this.getSnapshotBeforeUpdate();
       //比较新旧虚拟DOM的差异,把更新后的结果放在真实DOM上
       compareTwoVdom(oldDOM.parentNode,oldRenderVdom,newRenderVdom);
       //在更新后需要把oldRenderVdom更新为新的newRenderVdom
       // 第一次挂载 老的div#counter
       // 第一次更新的时候 新的div#counter
       // replaceChild  div#root>新的div#counter
       //它永远指向当前父DOM节点当前的子DOM节点
       this.oldRenderVdom=newRenderVdom;
       this.updater.flushCallbacks();
       if(this.componentDidUpdate){
        this.componentDidUpdate(this.props,this.state,snapshot);
       }
    }
}

react的router 2020版

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Link, Switch, Redirect, NavLink } from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
import Login from './components/Login';
import Protected from './components/Protected';
import NavHeader from './components/NavHeader';
/**
 * Router是路由容器,放在最外层
 * Route代表路由配置 当前地址栏的路径是path的话,就渲染component
 * 匹配的path是前缀  url是以path开头 /user exact不再匹配前缀了,而是精确匹配,看是否完全相等
 * router配置有三种 1 手工写代码 2 约定式 umi 3 配置式 routes.js antdesignpro
 */
ReactDOM.render(
  <Router>
    <>
      <NavHeader />
      <ul>
        <li><NavLink exact to="/">Home</NavLink></li>
        <li><NavLink to={{ pathname: "/user" }}>User</NavLink></li>
        <li><NavLink to="/profile">Profile</NavLink></li>
      </ul>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/user" component={User} />
        <Route path="/login" component={Login} />
        <Protected path="/profile" component={Profile} />
        <Redirect from="/home" to="/" />
      </Switch>
    </>
  </Router>,
  document.getElementById('root')
);

routerlink

import React from 'react';
import { Link, Route } from './';
/**
 * component 就是要渲染的组件 匹配才渲染
 * render 要渲染的函数 匹配才渲染
 * children 是一个函数,不管匹配还是不匹配都会渲染
 * 当我们访问的路径是 /user/list的情况下
 * NavLink /user
 */
export default props => {
    let { to, exact = false, children } = props;
    return (
        <Route path={typeof to == 'object' ? to.pathname : to} exact={exact} children={
            routeProps => <Link className={routeProps.match ? 'active' : ''} to={to} exact={exact}>{children}</Link>
        } />
    )
}


import React from 'react';
import RouteContext from './context';
export default class extends React.Component {
    static contextType = RouteContext;
    render() {
        return (
            <a className={this.props.className} onClick={
                () => this.context.history.push(this.props.to)
            }>{this.props.children}</a>
        )
    }
}

router

import React from 'react';
import RouteContext from './context';
export default class extends React.Component {
    state = {
        location: { pathname: '/', state: null }
    }
    componentDidMount() {
        let pushState = window.history.pushState;
        window.history.pushState = function (state, title, pathname) {
            if (typeof window.onpushstate == 'function') {
                window.onpushstate(state, pathname);
            }
            return pushState.apply(window.history, arguments);
        }
        window.onpopstate = (event) => {
            this.setState({
                location: {
                    ...this.state.location,
                    pathname: document.location.pathname,
                    state: event.state
                }
            });
        }
        window.onpushstate = (state, pathname) => {
            this.setState({
                location: {
                    ...this.state.location,
                    pathname,
                    state
                }
            });
        }
    }
    render() {
        let that = this;
        let value = {
            location: this.state.location,
            history: {
                location: this.state.location,
                push(to) {
                    if (that.message) {
                        let confirm = window.confirm(that.message(typeof to === 'object' ? to : { pathname: to }));
                        if (!confirm) return;
                    }
                    if (typeof to === 'object') {
                        let { pathname, state } = to;
                        window.history.pushState(state, '', pathname);
                    } else if (typeof to === 'string') {
                        window.history.pushState(null, '', to);
                    }
                },
                block(message) {
                    that.message = message;
                }
            }
        }
        return (
            <RouteContext.Provider value={value}>
                {this.props.children}
            </RouteContext.Provider>
        )
    }
}

switch

import React from 'react';
import RouteContext from './context';
import pathToRegexp from 'path-to-regexp';
export default class extends React.Component {
    static contextType = RouteContext;
    render() {
        let pathname = this.context.location.pathname;
        if (this.props.children) {
            let children = Array.isArray(this.props.children) ? this.props.children : [this.props.children];
            for (let i = 0; i < children.length; i++) {
                let child = children[i];
                let { path = "/", exact = false } = child.props;
                let regexp = pathToRegexp(path, [], { end: exact });
                let result = pathname.match(regexp);
                if (result) return child;
                //如果有一个儿子匹配上了,就不会再匹配后面的路径了
            }
        }
        return null;
    }
}

route

import React from 'react';
import RouteContext from './context';
import pathToRegexp from 'path-to-regexp';
export default class extends React.Component {
    static contextType = RouteContext //组件实例会多一个 this.context =value
    render() {
        //exact是否精确匹配 path 
        //此Route路径 
        //component 如果这个Route路径跟地址栏中的路径匹配的话要渲染哪个组件
        let { exact = false, path = "/", component: RouteComponent, render, children } = this.props;
        let pathname = this.context.location.pathname;
        let paramNames = [];
        let regexp = pathToRegexp(path, paramNames, { end: exact });
        let result = pathname.match(regexp);
        let props = {
            location: this.context.location,
            history: this.context.history
        }
        if (result) {
            //url=/user/detail/1590906236475 values=["1590906236475"]
            let [url, ...values] = result;
            paramNames = paramNames.map(item => item.name);
            let params = paramNames.reduce((memo, name, index) => (memo[name] = values[index], memo), {});
            let match = {
                url,
                path,
                params,
                isExact: url === pathname//看看匹配的路径和完整路由是否完全相等
            }
            props.match = match;
            if (RouteComponent) {
                return <RouteComponent {...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;
            }
        }
    }
}

react的router 2021、2022版

routes

import Home from './components/Home';
import User from './components/User';
import UserList from './components/UserList';
import UserAdd from './components/UserAdd';
import UserDetail from './components/UserDetail';
import Profile from './components/Profile';
import Login from './components/Login';
import {Navigate} from './react-router-dom'
const routes = [
    {
      path: "/",
      element:<Home/>
    },
    {
      path: "/user",
      element: <User/>,
      children: [
        {
          path: "list",
          element:<UserList/>
        },
        {
          path: "add",
          element: <UserAdd/>
        },
        {
          path: "detail/:id",
          element: <UserDetail/>
        }
      ]
    },
    {
      path: "/profile",
      element: <Profile/>
    },
    {
      path: "/login",
      element: <Login/>
    },
    {
      path: "*",
      element: <Navigate to="/"/>
    }
  ]

  export default routes;

index

import React from 'react';
import ReactDOM from 'react-dom/client';
import {BrowserRouter,HashRouter,Routes,Route,NavLink,Navigate,useRoutes} from './react-router-dom';
import routes from './routes';
import Post from './components/Post'
const root = ReactDOM.createRoot(document.getElementById('root'));
const activeStyle = {backgroundColor:'green'};
const activeClassName = 'active';
const activeNavProps = {
	style:({isActive})=>isActive?activeStyle:{},
	className:({isActive})=>isActive?activeClassName:''
}
const LazyPost = React.lazy(()=>import('./components/Post'));
function App(){
	const [routesConfig,setRoutes] = React.useState(routes);
	const addRoute = ()=>{
		setRoutes([
			...routesConfig,
			{
				path:'/post',
				element:(
					<React.Suspense fallback={<div>loading</div>}>
						<LazyPost/>
					</React.Suspense>
				)
			},
		]);
	}
	return (
		<div>
			{useRoutes(routesConfig)}
			<button onClick={addRoute}>动态添加路由规则</button>
		</div>
	)
}

root.render(
	<BrowserRouter>
		<ul>
			<li><NavLink end={true} to="/" {...activeNavProps}>首页</NavLink></li>
			<li><NavLink to="/user" {...activeNavProps}>用户管理</NavLink></li>
			<li><NavLink to="/profile" {...activeNavProps}>个人中心</NavLink></li>
			<li><NavLink to="/post" {...activeNavProps}>贴子管理</NavLink></li>
		</ul>
		<App/>
	</BrowserRouter>
);

router

import React from 'react';
import { createBrowserHistory, createHashHistory } from '../router';
import { Router,useLocation,useNavigate } from '../react-router';
export * from '../react-router';
export function BrowserRouter({ children }) {
    //在整个项目中只有一份history实例
    const historyRef = React.useRef(null);
    if (historyRef.current === null) {
        historyRef.current = createBrowserHistory();
    }
    const history = historyRef.current;
    //调用工厂方法,创建浏览器历史对象
    //定义一个状态,获取当前历史对象中的路径
    let [state, setState] = React.useState({
        action: history.action,//执行哪个动作到达此路径的 pushState=>PUSH popState=>POP
        location: history.location//当前路径
    });
    //给history添加监听函数,当浏览器的路径发生变化的时候,会执行setState,并传递最新的路径
    //并重新渲染路由容器组件
    React.useLayoutEffect(() => history.listen(setState), [history]);
    return (
        <Router
            children={children}
            location={state.location}
            navigationType={state.action}
            navigator={history}
        />
    )
}

export function HashRouter({ children }) {
    //在整个项目中只有一份history实例
    const historyRef = React.useRef(null);
    if (historyRef.current === null) {
        historyRef.current = createHashHistory();
    }
    const history = historyRef.current;
    //调用工厂方法,创建浏览器历史对象
    //定义一个状态,获取当前历史对象中的路径
    let [state, setState] = React.useState({
        action: history.action,//执行哪个动作到达此路径的 pushState=>PUSH popState=>POP
        location: history.location//当前路径
    });
    //给history添加监听函数,当浏览器的路径发生变化的时候,会执行setState,并传递最新的路径
    //并重新渲染路由容器组件
    React.useLayoutEffect(() => history.listen(setState), [history]);
    return (
        <Router
            children={children}
            location={state.location}
            navigationType={state.action}
            navigator={history}
        />
    )
}

export const Link = function(props){
   const {to,state,...rest} = props;
   const navigate = useNavigate();
   const handleClick = (event)=>{
    event.preventDefault();
    navigate(to,state);
   }
   return (
    <a
       {...rest}
       onClick={handleClick}
    />
   )
}

export function NavLink({
    className:classNameProp,
    style:styleProp,
    to,
    children,
    end=false,
    ...rest
}){
  const {pathname} = useLocation();
  //计算当前NavLink中的to路径和地址栏中的路径是否匹配
  //如果完整匹配是可以的
  //或者不需要结束,也不是不区要严格匹配的话,只要pathname是以to开头的就可以,并且to后面跟的是路径分隔符
  // pathname = '/user/list'  to="/user" TODO
  const isActive = (pathname===to)||(
    !end && pathname.startsWith(to) && pathname.chatAt(to.length)==='/'
  );  
  let className =classNameProp({isActive});
  let style = styleProp({isActive});
  return (
    <Link 
    className={className}
    style={style}
    to={to}
    {...rest}>{children}</Link>
  )  
}

useroutes

import React from 'react';
import {LocationContext,NavigatorContext,RouteContext} from './context';
import {matchRoutes} from '../../router';
export function useLocation(){
    const {location} = React.useContext(LocationContext);
    return location;
}
/**
 * 这是一个自定义hooks
 * 用当前的路径和routes里面的path进行匹配,如果匹配上就渲染对应的element
 */
export function useRoutes(routes){
    const location = useLocation();
    const {pathname} = location;// / /user /profile
    const matches = matchRoutes(routes,{pathname});
    if(matches){
        return renderMatches(matches);
    }
}
function renderMatches(renderMatches){
    //matches = [{route:{element:User}},{route:{element:UserList}}]
   let result =  renderMatches.reduceRight((outlet,match,index)=>{
       const matches = renderMatches.slice(0,index+1);
       return (
        <RouteContext.Provider value={{outlet,matches}}>
            {match.route.element}
        </RouteContext.Provider>
       );//UserList
   },null);
   return result;
}
export function useOutlet(){
    const value = React.useContext(RouteContext);
    return value.outlet;
}

export function useNavigate(){
    const {navigator} = React.useContext(NavigatorContext);//history
    let navigate = React.useCallback((to,state)=>{
        navigator.push(to,state)
    },[navigator]);
    return navigate;
}
export function useParams(){
    const {matches} = React.useContext(RouteContext);
    const routeMatch = matches[matches.length-1];
    return routeMatch?routeMatch.params:{};
}
/**
url = /1/zhufeng/16
<Route path="/:id" element={User}> match params {id:1}
    <Route path="/:name" element={UserName}>params {id:1,name:'zhufeng'}
        <Route path="/:age" element={UserNameAge}>params= {id:1,name:'zhufeng',age:16}

        </Route>
    </Route>
</Route>
 */

history


const Action = {
    Pop: 'POP',
    Push: 'PUSH'
}
const PopStateEventType = 'popstate';
let action = Action.Pop;
export function createHashHistory() {
    if(!window.location.hash){
        window.location.hash='/';
    }
    function getHashLocation(window, globalHistory) {
        const pathname = window.location.hash.substr(1)
        const state = globalHistory.state || {};
        return { pathname, state:state };
    }
    function createHashHref(to){
        let url = window.location.href;
        let hashIndex = url.indexOf('#');
        let href  = hashIndex==-1?url:url.slice(0,hashIndex);
        return href+'#'+to;// /user#/profile
    }
    return getUrlBasedHistory(getHashLocation,createHashHref);
}
export function createBrowserHistory() {
    function getBrowserLocation(window, globalHistory) {
        const { pathname } = window.location;
        const state = globalHistory.state || {};
        return { pathname, state:state };
    }
    function createBrowserHref(to){
        return to;
    }
    return getUrlBasedHistory(getBrowserLocation,createBrowserHref);
}

function getUrlBasedHistory(getLocation,createHref) {
    const globalHistory = window.history;
    let listener = null;
   /*  let index = getIndex();
    if(index === null){
        index=0;
        globalHistory.replaceState({
            usr:globalHistory.state,//usr  userState
            idx:index//在原来的状态基础上添加了一个索引0
        },'');
    } */
    /* function getIndex(){
        let state = globalHistory.state ||{idx:null}
        return state.idx;
    } */
    function handlePop(){
        action = Action.Pop;
        //const nextIndex = getIndex();//1
        //index = nextIndex;//把1赋给index
        if(listener){
            listener({location:history.location});
        }
    }
    function push(to,state){
      action = Action.Push;
      //index = getIndex()+1;//1
      //创建一个新的url地址
      const url = createHref(to);
      //在放入新的路径状态的时候,对状态做一个封装或者说加强
      //index指的是当前的索引
      globalHistory.pushState(state,'',url);
      if(listener){
        listener({location:history.location});
      }
    }
    let history = {
        get index() {
			//return index;
		},
        get action() {
            return action;
        },
        get location() {
            return getLocation(window, globalHistory);
        },
        //history.push 会跳转路径
        push,
        listen(fn){
            window.addEventListener(PopStateEventType,handlePop);
            listener=fn;
            return ()=>{
                window.removeEventListener(PopStateEventType,handlePop);
                listener = null;
            }
        },
        go(n){
            return globalHistory.go(n);
        }
    }
    window.his = history;
    return history;
}

link与NavLink

import React from 'react';
import { createBrowserHistory, createHashHistory } from '../router';
import { Router,useLocation,useNavigate } from '../react-router';
export * from '../react-router';
export function BrowserRouter({ children }) {
    //在整个项目中只有一份history实例
    const historyRef = React.useRef(null);
    if (historyRef.current === null) {
        historyRef.current = createBrowserHistory();
    }
    const history = historyRef.current;
    //调用工厂方法,创建浏览器历史对象
    //定义一个状态,获取当前历史对象中的路径
    let [state, setState] = React.useState({
        action: history.action,//执行哪个动作到达此路径的 pushState=>PUSH popState=>POP
        location: history.location//当前路径
    });
    //给history添加监听函数,当浏览器的路径发生变化的时候,会执行setState,并传递最新的路径
    //并重新渲染路由容器组件
    React.useLayoutEffect(() => history.listen(setState), [history]);
    return (
        <Router
            children={children}
            location={state.location}
            navigationType={state.action}
            navigator={history}
        />
    )
}
export function HashRouter({ children }) {
    //在整个项目中只有一份history实例
    const historyRef = React.useRef(null);
    if (historyRef.current === null) {
        historyRef.current = createHashHistory();
    }
    const history = historyRef.current;
    //调用工厂方法,创建浏览器历史对象
    //定义一个状态,获取当前历史对象中的路径
    let [state, setState] = React.useState({
        action: history.action,//执行哪个动作到达此路径的 pushState=>PUSH popState=>POP
        location: history.location//当前路径
    });
    //给history添加监听函数,当浏览器的路径发生变化的时候,会执行setState,并传递最新的路径
    //并重新渲染路由容器组件
    React.useLayoutEffect(() => history.listen(setState), [history]);
    return (
        <Router
            children={children}
            location={state.location}
            navigationType={state.action}
            navigator={history}
        />
    )
}

export const Link = function(props){
   const {to,state,...rest} = props;
   const navigate = useNavigate();
   const handleClick = (event)=>{
    event.preventDefault();
    navigate(to,state);
   }
   return (
    <a
       {...rest}
       onClick={handleClick}
    />
   )
}

export function NavLink({
    className:classNameProp,
    style:styleProp,
    to,
    children,
    end=false,
    ...rest
}){
  const {pathname} = useLocation();
  //计算当前NavLink中的to路径和地址栏中的路径是否匹配
  //如果完整匹配是可以的
  //或者不需要结束,也不是不区要严格匹配的话,只要pathname是以to开头的就可以,并且to后面跟的是路径分隔符
  // pathname = '/user/list'  to="/user" TODO
  const isActive = (pathname===to)||(
    !end && pathname.startsWith(to) && pathname.chatAt(to.length)==='/'
  );  
  let className =classNameProp({isActive});
  let style = styleProp({isActive});
  return (
    <Link 
    className={className}
    style={style}
    to={to}
    {...rest}>{children}</Link>
  )  
}