본문 바로가기
개발공부일지/React

React - 합성/상속 알아보기

by Hynn1429 2023. 3. 7.
반응형

안녕하세요.

Hynn 입니다.

 

이번 포스팅에서는 React 에서 상속,합성 개념을 어떻게 다루고 어떻게 사용하는지에 대해서 기본적인 사항을 알아보도록 하겠습니다.

React는 사실 강력한 합성 모델을 가지고 있다고 공식 홈페이지에서 소개하고 있습니다.

이를 한번 살펴보고, 어떻게 사용해야하는지도 알아보도록 하겠습니다. 

 

==============

==============

 

 

먼저 React 에서 말하는 강력한 합성 모델의 핵심은 우리가 이전에 계속 다루었던 Component 에 기반하고 있습니다.

즉, Component 에 기반하고 있는 React 에서는 기존의 A 라는 Component 에 B Component 를 끼워넣을 수 있습니다.

끼워넣는다는 의미는, 마치 레고조립과 같이, 블록을 끼워넣는 의미처럼, React 에서는 Component 를 이용해 재사용을 함으로서 얻는 이점을 극대화하는 것이기도 합니다.

 

이로 인해 상속을 권장하지 않는 이유는 몇가지 존재합니다.

이전 포스팅에서 활용한 예제에서도 몇가지를 이용할 수 있습니다. 

 

먼저 이전 포스팅들에서 사용한 예제 코드 두개를 살펴보도록 하겠습니다.

const { useState } = React
        const Panel = ({ title, children, isActive, setIsActive }) => {
        return (
            <section className="panel">
                <h3>{title}</h3>
                {isActive ? (<p>{children}</p>) : (<button onClick={() => setIsActive(true)}>Show</button>)}
            </section>
            )}
        const Sales = () => {
            const [isAboutActive, setIsAboutActive] = useState(false)
            const [isTermsActive, setIsTermsActive] = useState(false)

            return (
                <>
                    <h2> Hynn Tistory Blog</h2>
                    <Panel title="About" isActive={isAboutActive} setIsActive={setIsAboutActive}>
                        이 이용약관은 어디어디~저기저기~요리보고~저리봐도~둘리~둘리
                    </Panel>
                    <Panel title="TermsOfSales" isActive={isTermsActive} setIsActive={setIsTermsActive}>
                        빙하타고~내려와~만났지만~둘리~둘리~
                    </Panel>
                </>
                )}
        class Login extends React.Component {
            constructor(props) {
                super(props)
                this.state = {
                    appId: '',email: '',password: '',appIdError: '',emailError: '',passwordError: '',
                }
            }

            handleInputChange = (e) => {
                const { name, value } = e.target;
                let errors = {}
                switch (name) {
                    case 'appId':
                        if (value === 'Hynn') {
                            errors.appIdError = 'App ID는 "Hynn"이 될 수 없습니다.'
                        } else {
                            errors.appIdError = ''
                        }
                        break;
                    case 'email':
                        if (!value.match(/^\S+@\S+\.\S+$/)) {
                            errors.emailError = '올바른 이메일 주소 형식이 아닙니다. (예: example@example.com)';
                        } else {
                            errors.emailError = ''
                        }
                        break
                    case 'password':
                    if (!value) {
                        errors.passwordError = ''
                    } else if (!value.match(/^(?=.*\d)(?=.*[a-zA-Z]).{8,}$/)) {
                        errors.passwordError = '비밀번호는 8자 이상이어야 하며 문자와 숫자를 모두 포함해야 합니다.'
                    } else {
                        errors.passwordError = ''
                    }
                        break
                    default:
                        break
                }
                this.setState({
                    [name]: value,
                    ...errors,
                });
            }

            handleSubmit = (e) => {
                e.preventDefault()
                const { appId, email, password } = this.state;
                let errors = {}
                if (appId === 'Hynn') {
                    errors.appIdError = 'App ID는 "Hynn"이 될 수 없습니다.'
                }
                if (!email.match(/^\S+@\S+\.\S+$/)) {
                    errors.emailError = '올바른 이메일 주소 형식이 아닙니다. (예: example@example.com)'
                }
                if (!password.match(/^(?=.*\d)(?=.*[a-zA-Z]).{8,}$/)) {
                    errors.passwordError = '비밀번호는 8자 이상이어야 하며 문자와 숫자를 모두 포함해야 합니다.'
                }
                if (Object.keys(errors).length > 0) {
                    this.setState(errors)
                } else {
                    console.log({
                        appId: appId,
                        email: email,
                        password: password,
                    })
                    this.setState({
                        appId: '',email: '',password: '',appIdError: '',emailError: '',passwordError: '',
                    })
                }
            }

            render() {
                const { appId, email, password, appIdError, emailError, passwordError } = this.state
                return (
                    <form onSubmit={this.handleSubmit}>
                        <label>App ID : <input type="text" name="appId" value={appId} onChange={this.handleInputChange} /></label>
                        {appIdError && <p>{appIdError}</p>}
                        <label>App Email : <input type="email" name="email" value={email} onChange={this.handleInputChange} /></label>
                        {emailError && <p>{emailError}</p>}
                        <label>App Password : <input type="password" name="password" value={password} onChange={this.handleInputChange} /></label>
                        {passwordError && <p>{passwordError}</p>}
                        <input type="submit" value="로그인" />
                    </form>
                )}}

각각 Form, State 끌어올리기에서 활용한 예제코드입니다.

이 두개는 각각의 다른 특징을 가지고 있습니다.

 

위 두 코드는 각각 Class Component, Function Component 로 작성되어 있습니다.

이 두가지는 각각의 차이가 존재합니다.

 

물론 작성법도 다르지만, "상속"의 차이가 조금은 다르게 작성됩니다.

함수형 컴포넌트는 조각조각이 보다 간단하게 작성이되고, 가독성이 좋습니다.

class 의 경우 하나의 component 에 생명주기(Life-Cycle)을 위한 method, 이벤트를 위한 method, render 와 같이 여러개의 단위로 이루어져 있다보니, 길어질수록 가독성이 떨어질 수 있습니다.

 

하지만 함수형 컴포넌트의 경우, 상태관리나, 생명주기와 같은 method 처리를 최소화할 수 있어, 코드의 가독성이 더 좋습니다.

결정적으로 이후 포스팅에서 다루게 될 Hooks API 를 활용하면 함수형 컴포넌트에서도 이러한 상태관리, 생명주기 처리가 가능하다 보니, 이러한 이유들이 복합적으로 발생하면서, 클래스보다 함수형 컴포넌트 사용이 대세가 되었습니다.

 

또한 함수형 컴포넌트 사용으로 인해 "상속"의 여지를 더 줄이고 사용할 수 있게 됩니다.

 

 

사실 합성에 적합한, 상속에 적합한 컴포넌트가 정해진 바는 없습니다.

하지만 일반적으로 라이브러리 사용을 최소화하더라도 위 두개의 컴포넌트에 따른 상속/합성의 차이는 존재할 수 있습니다.

 

예시를 들어보면, 일반적으로 React 에서는 Class Component 보다 Functional Component 사용이 선호됩니다.

이러한 이유는 궁극적으로 코드가 더 간결하고, 의존성이 최소화될 수 있는 작성 뿐 아니라, 별도의 인스턴스를 갖지 않는 특성상, "this"와 상속에 관련된 문제를 방지할 수 있고, 개별적으로 유지보수가 쉽기 때문입니다.

 

하지만 몇몇 Library 에서는 Class Component 를 필요로 하는 라이브러리도 존재합니다. 

혹은 기존의 코드와 통합을 할 경우에는 Class Component 가 더 좋은 사용성을 지닙니다. 이러한 이유로 정답은 존재하지 않습니다.

 

Class Component 는 하나의 클래스 내에 Custom Method 와 같이 이벤트 동작을 위한 함수가 포함되어 있을 수 있고 생명주기로 인해 사용하는 method 가 필요 이상으로 많아질 수 있습니다. 

하지만 Functional Component 는 그런 부분을 최소화 할 수 있습니다.

 

따라서 정답은 없습니다. 

 

다음 포스팅부터는 WebPack, Hooks 에 대한 기본사용법이나 접근을 해보도록 하겠습니다.

기본적인 React 의 개념을 모두 학습했다면 크게 어렵지 않을 것으로 생각합니다.

 

다음 포스팅에서 뵙도록 하겠습니다.

 

감사합니다.

반응형

댓글