用100多行代码实现个简单的React,实现useState操作

134 阅读1分钟

image.png

超级简单的 React 简版本

  • 自持 tsx
  • 支持 useState

执行 npm run compile 编译
使用浏览器打开 index.html

Github地址: github.com/calidan-x/l…

tsconfig.json

{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "allowJs": false,
    "skipLibCheck": false,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "jsx": "react"
  },
  "files": ["./src/react.tsx"]
}

index.html

<!DOCTYPE html>
<head>
    <meta charset='UTF-8' />
</head>

<body>
    <script src="react.js" ></script>
</body>

</html>

react.tsx

// 完成简单的Virtual DOM
const componentStates: any = {}

let componentId = ''
let componentIdCounter = 1
let stateCounter = 0

function useState<T>(initVal: T) {
  if (!componentStates[componentId]) {
    componentStates[componentId] = []
  }

  const componentState = componentStates[componentId]

  if (componentState.length <= stateCounter) {
    componentState[stateCounter] = initVal
  }

  const val = componentState[stateCounter]

  // 缓存stateCounter的高阶函数
  const setVal = ((c: number) => (val: T) => {
    componentState[c] = val
    ReactDOM.refresh()
  })(stateCounter)

  stateCounter++
  return [val, setVal] as [T, (val: T) => void]
}

const React = {
  createElement(type, props, ...children) {
    return { type, props, children }
  }
}

const generateDOM = (vElement: any) => {
  const { type, props, children } = vElement
  let domElem = null
  // 是组件
  if (typeof type === 'function') {
    componentId = 'Comp' + componentIdCounter
    componentIdCounter++
    stateCounter = 0
    // 传入组件属性
    domElem = generateDOM(type(props))
  }
  // 是原生html标签
  else {
    domElem = document.createElement(type)
    if (props) {
      for (const p in props) {
        domElem[p.toLowerCase()] = props[p]
      }
    }
  }

  // 子节点
  if (children) {
    children.forEach((c) => {
      if (c) {
        if (typeof c !== 'object') {
          domElem.appendChild(document.createTextNode(c))
        } else {
          domElem.appendChild(generateDOM(c))
        }
      }
    })
  }
  return domElem
}

const ReactDOM = {
  virtualDOM: {},
  rootElement: null,
  refresh() {
    componentIdCounter = 1
    this.rootElement.innerHTML = ''
    this.rootElement.appendChild(generateDOM(this.virtualDOM))
  },
  render(elements: any, domElem: HTMLElement) {
    this.virtualDOM = elements
    this.rootElement = domElem
    this.refresh()
  }
}

function Comp({ text }: { text: string }) {
  const [title, setTitle] = useState('点击标题')
  const [count, setCount] = useState(1)

  return (
    <div>
      {text}
      <div
        onClick={() => {
          setTitle('标题改变')
        }}
      >
        {title}
      </div>
      <button
        onClick={() => {
          setCount(count + 1)
        }}
      >
        点击:{count}
      </button>
    </div>
  )
}


ReactDOM.render(
  <div>
    <button
      onClick={() => {
        alert('点击事件')
      }}
    >
      点击事件
    </button>
    <Comp text="AAA1" />
    <br />
    <Comp text="BBB2" />
    <br />
    <Comp text="CCC3" />
    <br />
    <Comp text="DDD4" />
    <br />
  </div>,
  document.body
)