{"id":19459270,"url":"https://github.com/addonewr/react-like","last_synced_at":"2026-06-10T01:31:04.951Z","repository":{"id":179955740,"uuid":"130701580","full_name":"AddOneWR/react-like","owner":"AddOneWR","description":"🚀 A compact version of React that implements many castration features","archived":false,"fork":false,"pushed_at":"2019-03-31T03:37:52.000Z","size":68,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-25T11:49:08.149Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AddOneWR.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-04-23T13:35:02.000Z","updated_at":"2019-03-31T03:37:54.000Z","dependencies_parsed_at":null,"dependency_job_id":"f365ff89-6d45-469d-9ff0-f7043d3a0a95","html_url":"https://github.com/AddOneWR/react-like","commit_stats":null,"previous_names":["addonewr/react-like"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/AddOneWR/react-like","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AddOneWR%2Freact-like","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AddOneWR%2Freact-like/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AddOneWR%2Freact-like/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AddOneWR%2Freact-like/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AddOneWR","download_url":"https://codeload.github.com/AddOneWR/react-like/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AddOneWR%2Freact-like/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34133404,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-10T17:31:09.656Z","updated_at":"2026-06-10T01:31:04.932Z","avatar_url":"https://github.com/AddOneWR.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## 背景\n\n之前自学了一阵子`React`源码([文章](https://addonedn.github.io/2018/04/18/React%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB-1%E7%99%BD%E8%AF%9D/))，感觉自己对`Component`和`setState`,所以这里决定写一个`React-Like`项目加深一下对`React`的理解\n\n## 开始\n\n\u003e 项目使用了`transform-react-jsx`来进行`JSX`和`JS`的转换\n\n### 组件\n\n既然是写`React`，那就先定义一下`React`基本结构\n\n```Javascript\n// src/react/index.js\nimport Component from './Component.js'\nimport createElement from './CreateReactElement.js'\n\nconst React = {\n    Component,\n    createElement\n}\n\nexport default React;\n```\n\n其中`Component`为基本组件作为父类,`createElement`来创建组件\n\n```Javascript\n// src/react/Component.js\nimport { enqueueSetState } from './StateQueue'\n\nclass Component {\n  constructor(props){\n    this.isComponent = true // 是否为组件\n    this.isReplace = false // 是否是更新的组件\n    this.props = props\n    this.state = {}\n  }\n\n  setState(partialState){\n    enqueueSetState(partialState, this)\n  }\n}\n\nexport default Component;\n```\n\n这里进行了一些基本的初始化, 还定义了`setState`方法，其中调用了`enqueueSetState`(后话)进行组件更新\n\n```Javascript\n// src/react/CreateReactElement.js\n\nfunction createElement(tag, attrs, children){\n\n  var props = {}\n  var attrs = attrs || {}\n\n  const childrenLength = arguments.length - 2;\n  \n  if (childrenLength === 1) {\n    props.children = children;\n  } else if (childrenLength \u003e 1) {\n    var childArray = Array(childrenLength);\n    for (let i = 0; i \u003c childrenLength; i++) {\n      childArray[i] = arguments[i + 2];\n    }\n    props.children = childArray;\n  }\n\n  return {\n    tag,\n    attrs,\n    props,\n    key: attrs.key || null\n  }\n}\n\nexport default createElement;\n```\n\n这里同样进行一些初始化操作，但是对传进来的`children`进行了特殊的处理，利用`arguments`获得`children`长度，之后决定是转化成数组还是直接写到`porps`上去，最后将所有属性作为对象返回，当用户创建`React`对象时会自动调用这个函数\n\n### 渲染\n\n同样我们先定义一个`ReactDom`对象\n\n```Javascript\n// src/react-dom/index.js\n\nimport render from './Render'\n\nconst ReactDOM = {\n  render: ( nextElement, container ) =\u003e {\n      return render( nextElement, container );\n  }\n}\n\nexport default ReactDOM;\n```\n\n在这里定义了一个大名鼎鼎的`render`函数，传入两个参数分别为当前的元素和要插入的容器，然后调用`Render`文件中的`render`方法\n\n```Javascript\n// src/react-dom/Render.js\n\nimport {\n  createComponent,\n  setComponentProps\n} from './Diff'\nimport setAttribute from './Dom'\n\n/**入口render方法\n * @param {ReactElement} nextElement 要插入到DOM中的组件\n * @param {DOMElement} container 要插入到的容器\n */\nexport function render(nextElement, container){\n\n  if(nextElement == null || container == null) return;\n\n  if(nextElement.isComponent){\n    const component = nextElement;\n\n    if (component._container) {\n      if (component.componentWillUpdate){\n        component.componentWillUpdate();\n      } else if (component.componentWillMount) {\n        component.componentWillMount();\n      }\n    }\n\n    component._container = container;\n\n    nextElement = component.render()\n  }\n\n  const type = typeof nextElement\n\n  if(type === 'string' || type === 'number'){\n    let textNode = document.createTextNode(nextElement);\n    return container.appendChild(textNode);\n  }\n\n  if(typeof nextElement.tag === 'function'){\n    let component = createComponent(nextElement.tag, nextElement.attrs)\n    setComponentProps(component,nextElement.attrs, container)\n    return render(component.base, container);\n  }\n\n  const dom = document.createElement(nextElement.tag)\n\n  if(nextElement.attrs){\n    Object.keys(nextElement.attrs).map(key =\u003e {\n      setAttribute(key, nextElement.attrs[key], dom)\n    })\n  }\n\n  if(nextElement.props){\n    if(typeof nextElement.props.children == 'object'){\n      nextElement.props.children.forEach(item =\u003e {\n        render(item, dom)\n      })\n    }else{\n      render(nextElement.props.children, dom)\n    }\n  }\n\n  if(nextElement._component){\n    if(nextElement._component.isReplace){\n      var arr = Array.from(nextElement._component.parentNode.childNodes)\n      arr.map((item,index) =\u003e {\n        if(isSameDom(item,dom)){\n          return container.replaceChild(dom, nextElement._component.parentNode.children[index])\n        }\n      })\n    }\n  }\n  return container.appendChild(dom)\n}\n\nfunction isSameDom(item, dom){\n  return (item.nodeName == dom.nodeName \u0026\u0026 item.nodeType == dom.nodeType \u0026\u0026 item.nextSibling == dom.nextSibling)\n}\n\nexport default render;\n```\n\n代码比较长，我们这里分段分析一下\n\n```Javascript\nconst type = typeof nextElement\n\nif(type === 'string' || type === 'number'){\n  let textNode = document.createTextNode(nextElement);\n  return container.appendChild(textNode);\n}\n```\n\n如果元素类型为`string`或`number`则直接创建`TextNode`并直接`append`到`container`中里\n\n```Javascript\nif(typeof nextElement.tag === 'function'){\n  let component = createComponent(nextElement.tag, nextElement.attrs)\n  setComponentProps(component,nextElement.attrs, container)\n  return render(component.base, container);\n}\n```\n\n如果元素的`tag`类型为`function`即为`React`组件，则调用`Diff`中的方法来创建组件(后话)\n\n```Javascript\nconst dom = document.createElement(nextElement.tag)\n\nif(nextElement.attrs){\n  Object.keys(nextElement.attrs).map(key =\u003e {\n    setAttribute(key, nextElement.attrs[key], dom)\n  })\n}\n```\n\n如果都不是的话即为普通元素，则直接调用`document.createElement`创建`Dom`，之后遍历`attrs`调用`setAttribute`来设置属性，`Object.keys`将对象转化成数组方便遍历，接下来我们看一下`setAttribute`方法\n\n```Javascript\nfunction setAttribute(key, value, dom){\n  if(key === 'className'){\n    key = 'class'\n  }\n\n  if(typeof value === 'function'){\n    dom[key.toLowerCase()] = value || '';\n  }else if(key === 'style'){\n    if(typeof value === 'string'){\n      dom.style.cssText = value || '';\n    }else if(typeof value === 'object'){\n      for (let name in value) {\n        dom.style[name] = typeof value[name] === 'number' ? value[name] + 'px' : value[name];\n      }\n    }\n  }else{\n    if(value){\n      dom.setAttribute(key, value);\n    }else{\n      dom.removeAttribute(key, value);\n    }\n  }\n}\n\nexport default setAttribute;\n```\n\n- 先将`className`转化为`class`\n- 若绑定的类型为`function`则转化成小写后写入`dom`属性\n- 若`key`为`style`，则分类讨论，若属性为`string`则写入`cssText`，若为`object`则判断其是否为`number`，若是则自动在后面添加`px`，然后写入`style`\n- 若为其他则直接调用原生`setAttribute`方法\n- 若属性值为空则在`dom`上删除该属性\n\n```Javascript\nif(nextElement.props){\n  if(typeof nextElement.props.children == 'object'){\n    nextElement.props.children.forEach(item =\u003e {\n      render(item, dom)\n    })\n  }else{\n    render(nextElement.props.children, dom)\n  }\n}\n```\n\n顺着`render`往下看，这里遍历元素的子元素递归渲染\n\n```Javascript\nif(nextElement._component){\n  if(nextElement._component.isReplace){\n    var arr = Array.from(nextElement._component.parentNode.childNodes)\n    arr.map((item,index) =\u003e {\n      if(isSameDom(item,dom)){\n        return container.replaceChild(dom, nextElement._component.parentNode.children[index])\n      }\n    })\n  }\n}\nreturn container.appendChild(dom)\n```\n\n最后判断两次`render`的组件是否为同一个，若为同一个则调用`replaceChild`方法进行替换，否则`appendChild`到容器中\n\n回到上面`nextElement.tag === 'function'`中，其中有两个函数`createComponent`和`setComponentProps`\n\n```Javascript\n// src/react-dom/Diff.js\n\nexport function createComponent(component, props){\n  let instance;\n  if(component.prototype \u0026\u0026 component.prototype.render){\n    instance = new component(props)\n  }else{\n    instance = new component(props)\n    instance.constructor = component\n    instance.render = function() {\n      return this.constructor(props)\n    }\n  }\n\n  return instance;\n}\n```\n\n第一个`if`判断是不是`class`创建的组件，若是则直接`new`一个，若不是则为函数返回组件，调整一下`constructor`以及`render`方法，然后将新组件返回\n\n```Javascript\n// src/react-dom/Diff.js\n\nexport function setComponentProps(component, props, container){\n  if (!component.base){\n    if (component.componentWillMount) \n      component.componentWillMount();\n\t}else if(component.componentWillReceiveProps){\n\t\tcomponent.componentWillReceiveProps(props);\n  }\n  \n  component.props = props;\n  component.parentNode = container\n\n  renderComponent(component, container)\n}\n```\n\n首先判断组件的`base`是否存在，若存在则判断是否为初次挂载，否则判断是否为接受新的`props`，然后将`props`即`render`中的`attrs`和`container`作为成员添加到`component`上，`parentNode`用来定位父元素方便更新，然后调用`renderComponent`进行组件挂载或者更新\n\n```Javascript\n// src/react-dom/Diff.js\n\nexport function renderComponent(component, container){\n  let base;\n\n  if ( component.base \u0026\u0026 component.componentWillUpdate ) {\n    component.componentWillUpdate();\n  }\n\n  base = component.render()\n\n  if (component.base) {\n    if (component.componentDidUpdate){\n      component.componentDidUpdate();\n    }\n  }else if(component.componentDidMount) {\n    component.componentDidMount();\n  }\n\n  component.base = base;\n  base._component = component;\n\n  if(!container){\n    component.isReplace = true\n    render(base, component.parentNode)\n  }\n}\n```\n\n`base`为`createComponent`中的`component`渲染后结果，然后进行一下简单的生命周期判断，最后判断`container`是否为空，若为空则为更新组件，把`component.parentNode`作为`container`传回`render`\n\n### State更新\n\n在文章开始提到过，`Component`中的`setState`方法调用了`enqueueSetState`\n\n```Javascript\n// src/react/StateQueue.js\n\nconst batchingUpdates = [] // 需要更新的状态\nconst dirtyComponent = [] // 需要更新的组件\nvar isbatchingUpdates = false // 是否处于更新状态\n\nfunction callbackQueue(fn){\n  return Promise.resolve().then(fn);\n}\n\nexport function enqueueSetState(partialState, component){\n  if(!isbatchingUpdates){\n    callbackQueue(flushBatchedUpdates)\n  }\n\n  isbatchingUpdates = true\n\n  batchingUpdates.push({\n    partialState,\n    component\n  })\n\n  if(!dirtyComponent.some(item =\u003e item === component)){\n    dirtyComponent.push(component)\n  }\n}\n```\n\n`isbatchingUpdates`判断事务是否处于更新状态(初始值为`false`)，若不为更新则调用`callbackQueue`来执行`flushBatchedUpdates`函数来更新组件，然后设置更新状态为`true`，将当前状态和组件添加到`batchingUpdates`中，最后判断`dirtyComponent`中是否有当前组件，若无则添加进去\n\n\u003e `callbackQueue`使用了`Promise`来达到延时模拟`setState`的功能\n\n```Javascript\n// src/react/StateQueue.js\n\nfunction flushBatchedUpdates(){\n  let queueItem, componentItem;\n  while(queueItem = batchingUpdates.shift()){\n    const { partialState, component } = queueItem;\n\n    if(!component.prevState){\n      component.prevState = Object.assign({}, partialState)\n    }\n\n    if(typeof partialState == 'function'){\n      Object.assign(component.state, partialState(component.prevState, component.props))\n    }else{\n      Object.assign(component.state, partialState)\n    }\n\n    component.prevState = component.state\n  }\n\n  while(componentItem = dirtyComponent.shift()){\n    renderComponent(componentItem)\n  }\n\n  isbatchingUpdates = false\n}\n```\n\n遍历`batchingUpdates`数组排头(`shift`自查)，获取其中组件和状态，判断组件的前一个状态，若无之前的状态，则将空对象和当前状态合并设为该组件的初始状态，若前一状态为`function`，则调用该函数并将返回值和之前状态合并，若不为函数则直接合并，然后设置组件的上一状态为其之前的状态，最后遍历`dirtyComponent`更新组件，完成后设置`isbatchingUpdates`为`false`\n\n### 事件委托\n\n入口在为元素设置属性的setAttribute中\n\n```javascript\n// 若绑定的类型为function则挂载到事件委托上\nif(typeof value === 'function'){\n    setFuncBus(key, value, dom);\n}\n```\n\n我们看一下setFuncBus这个函数\n\n```javascript\n/**\n * @msg: 事件代理函数\n * @param {string} key 属性的key\n * @param {any} value 属性的值\n * @param {dom} dom 被设置属性的元素 \n * @return: null\n */\nfunction setFuncBus(key, value, dom) {\n  let funcKey = key.toLowerCase();\n  let domKey = dom.key;\n  \n  if(document.eventBus[funcKey]) {\n    document.eventBus[funcKey][domKey] = value || '';\n  } else {\n    document.eventBus[funcKey] = {};\n    document.eventBus[funcKey][domKey] = value || '';\n    addWindowEventListener(funcKey);\n  }\n}\n```\n\n\u003e 对于key, 我简单的在在render函数中生成dom时为其赋值了key，其为一个8位随机数(随便啦\n\neventBus是一个普通对象。先将函数名转为小写，然后判断eventBus中是否已经委托了该函数，若没有则初始化后赋值，这里简单的以key作为触发元素的唯一标识，最后将funcKey添加到全局的事件监听中。\n\n```javascript\n/**\n * @msg: 添加全局事件委托\n * @param {string} funcKey 委托事件名  \n * @return: null\n */\nexport function addWindowEventListener(funcKey) {\n  let listenName = funcKey.replace('on', '');\n  funcKey = funcKey.toLowerCase();\n\n  // 根据eventbus避免全局事件重复注册\n  (!document.eventBus[funcKey] || Object.keys(document.eventBus[funcKey]).length \u003c 2 ) ? \n  window.addEventListener(listenName, function(e){\n    // 判断当前元素是否为被委托事件\n    let func = document.eventBus[funcKey][e.target.key];\n  \n    // 如果当前元素被委托则执行\n    if(func) {\n      func();\n    } else {\n      // 向上冒泡寻找是否有适合条件的委托函数\n      // e.path为层级数组，索引从低到高为 子----\u003e父\n      e.path.forEach(item =\u003e {\n        document.eventBus[funcKey][item.key] ? \n          document.eventBus[funcKey][item.key]() : '';\n      });\n    }\n  }) : null;\n}\n```\n\n基本的事件委托到这里结束\n\n代码请移步[GitHub仓库](https://github.com/AddOneDn/react-like)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faddonewr%2Freact-like","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faddonewr%2Freact-like","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faddonewr%2Freact-like/lists"}