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);
const [count,setCount] = useState(0);
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);
const inputRef = useRef(null);
const getFocus = ()=>{
inputRef.current.myFocus();
}
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 = []
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)
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
const renderVdom = type(props)
if(!renderVdom) return null
vdom.oldRenderVdom = renderVdom
return createDOM(renderVdom)
}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0
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
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
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)
let oldTrueDOM = oldTrueDOMs[mountIndex]
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}`
let eventTypeCapture = `on${type}capture`
let syntheticEvent= createSyntheticEvent(event)
updateQueue.isBatchingUpdate=true
//为了跟源码一样,我们需要自己手工再次模拟捕获和冒泡的全过程
//我们需要先记录一栈结构
let targetStack = []
let currentTarget=target
while(currentTarget){
targetStack.push(currentTarget)
currentTarget=currentTarget.parentNode
}
//处理捕获阶段
if(isCapture){
for(let i=targetStack.length-1
const currentTarget = targetStack[i]
let {store} = currentTarget
let handler = store&&store[eventTypeCapture]
handler&&handler(syntheticEvent)
}
}else{
//处理冒泡阶段
for(let i=0
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';
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;
delete config.key;
}
let props = {...config};
if(arguments.length>3){
props.children = Array.prototype.slice.call(arguments,2).map(wrapToVdom)
}else if(arguments.length===3){
props.children = wrapToVdom(children);
}
return {
$$typeof:REACT_ELEMENT,
type,
props,
ref,
key
}
}
function createRef(){
return {current:null};
}
function forwardRef(render){
return {
$$typeof:REACT_FORWARD_REF_TYPE,
render
}
}
function createContext(){
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)
}else if(arguments.length===3){
props.children = wrapToVdom(children);
}
return {
...element,
props
}
}
class PureComponent extends Component{
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){
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;
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;
if(classInstance.shouldComponentUpdate && (!classInstance.shouldComponentUpdate(
nextProps,
nextState
))){
willUpdate=false
}
if(willUpdate && classInstance.UNSAFE_componentWillUpdate){
classInstance.UNSAFE_componentWillUpdate();
}
classInstance.state = nextState;
if(nextProps){
classInstance.props = nextProps;
}
if(willUpdate)
classInstance.forceUpdate();
}
export class Component{
static isReactComponent = true
constructor(props){
this.props = props;
this.state = {};
this.updater = new Updater(this);
}
setState(partialState,callback){
this.updater.addState(partialState,callback);
}
forceUpdate(){
let oldRenderVdom = this.oldRenderVdom;
const oldDOM = findDOM(oldRenderVdom);
if(this.constructor.contextType){
this.context = this.constructor.contextType._currentValue;
}
const {getDerivedStateFromProps} = this.constructor;
if(getDerivedStateFromProps){
let newState = getDerivedStateFromProps(this.props,this.state);
if(newState){
this.state = {...this.state,...newState};
}
}
let newRenderVdom = this.render();
let snapshot = this.getSnapshotBeforeUpdate&&this.getSnapshotBeforeUpdate();
compareTwoVdom(oldDOM.parentNode,oldRenderVdom,newRenderVdom);
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';
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 './';
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
render() {
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) {
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;
}
export function useRoutes(routes){
const location = useLocation();
const {pathname} = location;
const matches = matchRoutes(routes,{pathname});
if(matches){
return renderMatches(matches);
}
}
function renderMatches(renderMatches){
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>
);
},null);
return result;
}
export function useOutlet(){
const value = React.useContext(RouteContext);
return value.outlet;
}
export function useNavigate(){
const {navigator} = React.useContext(NavigatorContext);
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:{};
}
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+'
}
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()
//index = nextIndex
if(listener){
listener({location:history.location})
}
}
function push(to,state){
action = Action.Push
//index = getIndex()+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>
)
}