React | 리액트 한달살기 - 2

Lumpen·2024년 12월 19일
0

React

목록 보기
20/26

react render

createElement 의 결과인 가상 돔 객체를
실제 DOM 으로 변경하는 함수

// {
//   "type": "div",
//   "props": {
//     "id": "app",
//     "children": {
//       "type": "h1",
//       "props": {
//         "children": "Hello, React Clone!"
//       }
//     }
//   }
// }

const render = tree => {
  const rootElement = document.createElement(tree.type)

  const { children, ...elementProps } = tree.props
  Object.keys(elementProps).forEach(key => {
    rootElement.setAttribute(key, elementProps[key])
  })
  if (children) {
    if (typeof children[0] === 'string') {
      rootElement.appendChild(document.createTextNode(children[0]))
    } else {
      children.map(child => rootElement.appendChild(render(child)))
    }
  }
  return rootElement
}

export default render
class React {
  states = new Map()
  stateIndex = 0
  listeners = []

  constructor() {
    this.currentComponent = null
  }

  setState = (index, newState) => {
    console.log(newState, 'newState')
    const states = this.states.get(this.currentComponent)
    if (typeof newState === 'function') {
      states[index] = newState(states[index])
    } else {
      states[index] = newState
    }
    this.listeners.forEach(listener => listener())
  }

  subscribe = listener => {
    this.listeners.push(listener)
    return () => {
      this.listeners = this.listeners.filter(l => l !== listener)
    }
  }

  useState = initialValue => {
    if (!this.states.has(this.currentComponent)) {
      this.states.set(this.currentComponent, [])
    }

    const states = this.states.get(this.currentComponent)

    if (states.length <= this.stateIndex) {
      states[this.stateIndex] = initialValue
    }

    const index = this.stateIndex
    const setState = this.setState.bind(this, index)

    this.stateIndex++
    return [states[index], setState]
  }

  render = tree => {
    const rootElement = document.createElement(tree.type)

    const { children, onClick, onChange, checked, ...elementProps } = tree.props

    if (onClick) {
      rootElement.addEventListener('click', onClick)
    }

    if (onChange) {
      rootElement.addEventListener('change', e => onChange(e))
    }

    if (checked) {
      rootElement.checked = checked
    }

    Object.keys(elementProps).forEach(key => {
      rootElement.setAttribute(key, elementProps[key])
    })
    if (children) {
      if (Array.isArray(children)) {
        children.map(child => {
          if (typeof child !== 'object') {
            rootElement.appendChild(document.createTextNode(child))
          } else {
            rootElement.appendChild(this.render(child))
          }
        })
      } else {
        if (typeof children !== 'object') {
          rootElement.appendChild(document.createTextNode(children))
        } else {
          rootElement.appendChild(this.render(children))
        }
      }
    }
    return rootElement
  }

  renderComponent = component => {
    this.currentComponent = component
    this.stateIndex = 0
    return this.render(component())
  }
}

const react = new React()

export default react

listeners, subscribe() 로 옵저버 패턴 구현
setState() 실행 시 listener() 를 실행시킨다

main.js

// src/main.js
import App from './App.jsx'
import React from './utils/react.js'

// 초기 렌더링

const app = document.getElementById('app')
const rootElement = React.renderComponent(App)
app.appendChild(rootElement)

// 상태 변경 시 재렌더링을 위한 리스너 등록
React.subscribe(() => {
  const newRootElement = React.renderComponent(App)
  app.innerHTML = ''
  app.appendChild(newRootElement)
})

main.js 에서는 React.subscribe() 로
리스너에 등록할 콜백 함수를 정의하는데
여기에 renderComponent() 함수를 사용하여
setState() 시 컴포넌트를 재렌더링 할 수 있도록 한다
현재는 app 전체가 리렌더링 되므로 수정이 필요함

createElement

const createElement = (type, props, children) => {
  if (typeof type === 'function') {
    // props와 children을 합쳐서 컴포넌트에 전달
    const componentProps = {
      ...props,
      children,
    }
    // 컴포넌트 함수 실행
    return type(componentProps)
  }

  // 일반 HTML 엘리먼트의 경우
  return {
    type,
    props: {
      ...props,
      children,
    },
  }
}

export default createElement

자식 컴포넌트를 import 해오면
함수 컴포넌트이기 때문에 함수가 전달된다
이 경우를 위해 type 이 'function' 인 경우 처리 추가

profile
떠돌이 생활을 하는. 실업자, 부랑 생활을 하는

0개의 댓글