React (23/02/21)

nazzzo·2023년 2월 21일
0

리액트 입문



데이터가 바뀌면 화면도 바뀐다

리액트는 메타 사(구 페이스북)에서 개발한,
자바스크립트 DOM 조작을 간편하게 만들기 위해 탄생한 라이브러리입니다

리액트는 다른 라이브러리나 프레임워크와 함께 사용될 수 있으며
빠른 성능과 좋은 유지보수성을 제공합니다
현재 프론트엔드 웹 애플리케이션 개발에서 가장 널리 사용되고 있습니다



1. 리액트 써보기


간단 예제입니다
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
</head>
<body>
    <div id="root"></div>
    <script type="text/javascript">
        const root = ReactDOM.createRoot(document.querySelector("#root"))
        root.render(
            React.createElement("h1", null, "hello world")
            )
    </script>
</body>

*React.createElement의 두번째 인자인 null은 속성값입니다


실행 결과

 <div id="root">
   <h1>hello world</h1>
 </div>



2. 컴포넌트

컴포넌트는 리액트에서 기본적으로 제공하는 객체입니다
이 객체에는 컴포넌트의 속성(props) 및 상태(state) 값이 저장됩니다

상태(state)는 컴포넌트 내에서 변경될 수 있는 값으로, 상태가 변경될 때마다 리액트는 자동으로 컴포넌트를 다시 렌더링합니다
속성(props)은 컴포넌트 외부에서 전달되는 값으로, 부모 컴포넌트에서 자식 컴포넌트로 전달됩니다


+) props 추가설명

<MyComponent name="Kim" age={20}>

위 부모 컴포넌트의 name과 age 값은 props를 통해 자식 컴포넌트로 전달될 수 있습니다
(props는 객체 형태로 전달됩니다)

const MyComponent = (props) => {
  return (
    <div>
      <p>내 이름은 {props.name}이구요</p>
      <p>나이는 {props.age}살입니다</p>
    </div>
  );
}

그리고 props를 통해 전달받은 데이터가 바뀌면 화면도 자동으로 재렌더링됩니다
(부모 컴포넌트를 통해 정적 메서드도 상속받을 수 있지만 이것은 별개의 개념..)


정리하자면 리액트는 컴포넌트를 기반으로 하며 각 컴포넌트는 독립적으로 작동할 수 있습니다
이러한 컴포넌트들은 필요에 따라 재사용할 수 있으며 이를 통해 코드의 재사용성과 유지보수성이 향상됩니다



2-1. 컴포넌트 이해하기


먼저 일반 자바스크립트 DOM 조작으로 간단한 버튼을 하나 만들어보겠습니다

<body>
    <div id="root">
        <script>
            const root = document.querySelector('#root');
            const button = document.createElement('button');
            button.addEventListener('click', (e) => {
                if (e.target.innerHTML === "Login")
                e.target.innerHTML = "Logout"
                else {
                    e.target.innerHTML = "Login"
                }
            })
            button.innerHTML = 'Login'

            root.append(button)
        </script>
    </div>
</body>



리액트를 활용해서 동일한 결과를 내려면 아래와 같이 코드를 작성할 수 있습니다


<body>
    <div id="root"></div>
    <script type="text/javascript">
        // 컴포넌트
        class LoginBtn extends React.Component {
            constructor(props) {
                super(props)
                // 조상클래스(컴포넌트)에서 메서드를 상속받습니다

                // 상태가 바뀌먼 컴포넌트가 바뀐다 === 데이터가 바뀌면 화면이 바뀐다
                this.state = {
                    isLogin: false,
                }
            }

            render() {
                console.log(this.state.isLogin) // false true false true....
                // React.createElement(1.Element, 2.속성, 3.innerHTML)
                return React.createElement(
                    'button',
                    {
                        onClick: () => {
                            this.setState({ isLogin: !this.state.isLogin })
                            // this.state.isLogin = true
                        }
                    },
                    this.state.isLogin ? 'Logout' : 'Login'
                )
            }
        }

        const root = ReactDOM.createRoot(document.querySelector("#root"))
        root.render(
            React.createElement(LoginBtn)
            // 인스턴스화 ~ new LoginBtn()
        )
    </script>
</body>

앞으로는 객체지향적 사고가 필수...

리액트를 쓰는 것이 왜 좋은지에 대해서는 차차 알아봅시다



3. Babel & JSX

바벨은 자바스크립트 컴파일러입니다

JSX는 리액트에서 사용되는 자바스크립트 확장 문법으로,
JSX를 사용하면 HTML과 비슷한 구조를 가지는 코드를 작성하여 컴포넌트를 만들 수 있습니다

하지만 브라우저에서는 JSX 코드를 바로 실행할 수 없기 때문에
바벨을 사용하여 JSX 코드를 자바스크립트 코드로 변환해야 합니다


[babel]

<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<body>
    <div id="root"></div>
	// type="text/babel"에 주의!
    <script type="text/babel">
        class LoginBtn extends React.Component {
            constructor(props) {
                super(props)
                console.log(props)
                this.state = {
                    isLogin: false,
                }
            }

            render() {
                return <button 
                    onClick={()=>{ this.setState({
                       isLogin: !this.state.isLogin }) 
                    }}
                >
                {this.state.isLogin ? 'Logout' : 'Login'}
                </button>
            }
        }

        const root = ReactDOM.createRoot(document.querySelector("#root"))
        root.render(<LoginBtn id="1">인자값</LoginBtn>))
    </script>
</body>

JWX을 함께 사용하니 코드를 읽기가 한결 편해지네요


↓ 그리고 console.log(props)의 결과는

{id: '1', children: '인자값'}


4. 리액트 기본활용 ~ Virtual DOM

리액트로 1초 간격으로 시간을 표현하는 DOM 조작 코드를 만들어봅시다

먼저 바닐라 자바스크립트 코드로 구현하면 다음과 같습니다

<body>
    <div id="root"></div>
    <script type="text/javascript">
        const Clock = () => {
            const div = document.createElement("div");
            const h1 = document.createElement("h1");
            const h2 = document.createElement("h2");

            const txt = 'hello world'
            let time = `It is ${new Date().toLocaleTimeString()}`

            h1.innerHTML = txt
            h2.innerHTML = time

            div.append(h1)
            div.append(h2)

            document.querySelector("#root").innerHTML = div.innerHTML
        }

        setInterval(Clock, 1000)
    </script>
</body>

위 코드를 실행하면 시간이 담긴 h2 엘리먼트 뿐만이 아니라
div, h1 엘리먼트도 함께 재실행된다는 것을 확인할 수 있습니다

하지만 리액트를 사용하면 어떨까요


<body>
    <div id="root"></div>
    <script type="text/babel">
        class Clock extends React.Component {
            constructor(props) {
                super(props)
                this.state = {
                    currentTime: new Date().toLocaleTimeString()
                }
            }

            // 컴포넌트 사용시 최초로 실행될 코드영역
            componentDidMount() {
                setInterval(() => {
                    this.setState({ currentTime: new Date().toLocaleTimeString() })
                }, 1000);
            }
            
            render () {
                return (
                    <div>
                        <h1>Hello world!</h1>
                        <h2>It is {this.state.currentTime}</h2>
                    </div>   
                )
            }
        }

        const root = ReactDOM.createRoot(document.querySelector('#root'))
        root.render(<Clock />)
    </script>
</body>

겉보기에는 같은 결과로 보이지만 시간표현만이 1초마다 새로고침된다는 차이가 있습니다
(물론 바닐라 자바스크립트로도 가능하지만 그만큼 코드가 복잡해지기 마련...)

리액트에 내장된 virtual DOM 기능이 상태(state)가 바뀐 부분만 바꿔서 그려주기 때문입니다


버츄얼 DOM이란?

리액트에서는 Virtual DOM을 사용해서 DOM 조작을 최소화하고 성능을 향상시킵니다
Virtual DOM은 메모리 상에 존재하는 가벼운 복제본으로, 실제 DOM과 유사한 구조를 가지고 있습니다
컴포넌트의 상태나 속성이 변경되면, Virtual DOM에서는 이를 반영한 새로운 Virtual DOM을 생성합니다.
이후 상태와 속성을 비교하여 변경된 부분만 실제 DOM에 반영하게 됩니다



5. 생명주기


생명주기(Lifecycle)는 컴포넌트가 생성, 업데이트, 제거될 때 일어나는 일련의 과정을 뜻합니다

리액트의 생명주기에 관한 메서드는 여러가지가 있는데 호출 시점에 따라 크게 3가지로 구분됩니다

componentDidMount() : 컴포넌트가 생성되고 첫 렌더링이 완료된 후 호출됩니다
componentDidUpdate() : 컴포넌트가 업데이트된 후 호출됩니다
componentWillUnmount() : 컴포넌트가 제거되기 직전에 호출됩니다

이러한 생명주기 메서드를 잘 활용하면 컴포넌트의 상태 변화에 따라 필요한 작업을 수행할 수 있습니다




지금까지 배운 내용을 정리해서 리액트 컴포넌트의 구조를 추측하자면...

<div id="root"></div>
<script>
    class Component {
        props
        state

        constructor(_props) {
            this.props = _props
        }

        componentDidMount() {}
        componentDidUpdate() {}

        // 추상 메서드 .. 내부적인 코드는 자식클래스에서 만들겠다는 뜻입니다
        render() {}
        template() {
            this.componentDidMount()
            this.props.innerHTML = this.render()
        }

        setState(newState) {
            if (this.state === newState) return
            this.state = { ...this.state, ...newState } 
            this.template()
            this.componentDidUpdate()
        }
    }

    class LoginBtn extends Component {
        constructor(props) {
            super(props) // 부모 클래스를 인스턴스화
            this.state = {
                isLogin: false,
            }
        }

        componentDidMount() {
            console.log(`최초 렌더 완료`)
            this.setState({isLogin: true})
        }

        componentDidMount() {
            console.log(`상태가 바뀜`)
            this.setState({isLogin: false})
        }

        render() {
            console.log(`렌더 실행`)
            return `<button>${this.state.isLogin ? "logout" : "login" }</button>`
        }
    }

    class VirtualDOM {
        static createRoot(Props) {
            return {
                props: Props,
                render(Component) {
                    new Component(this.props).template()
                }
            }
        }
    }

    const root = new VirtualDOM.createRoot(document.querySelector("#root"))
    root.render(LoginBtn) // <- Componet
</script>

아마도 이런 형태일 듯 합니다

0개의 댓글