Day 6 - Container-Presenter 패턴 / props / setState 동작 원리 / Nullish coalescing

오늘은 Container-Presenter 패턴, 그리고 state와 같이 리엑트 컴포넌트에서 데이터를 다루는 props에 대해서 배웠습니다!

Container-Presenter 패턴

Container-Presenter 패턴은 React를 개발할 때, 그리고 실무에서 많이 사용되는 폴더 구조입니다.

하나의 파일에서 코드를 작성을 할 경우, 코드가 길어지고, 가독성이 떨어지며, 유지보수에 어려움이 발생합니다.

Container-Presenter 패턴은 크게 기능 부분을 담당하는 js부분과 화면에 render가 되는 UI를 담당하는 jsx부분을 분리하여 개발하는 방법입니다.

Container-Presenter 패턴 미 적용

// index.js
import { useState } from 'react';
export default function StateSignup() {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [errorEmail, setErrorEmail] = useState('');
    const [errorPassword, setErrorPassword] = useState('');
    const [join, setJoin] = useState('');
    function changeEmail(event) {
        console.log(event.target.value); 
        setEmail(event.target.value);
    }
    function changePassword(event) {
        console.log(event.target.value);
        setPassword(event.target.value);
    }
    function btnClick() {
        let check = true;
        console.log(`email : ${email}`);
        console.log(`password : ${password}`);
        if (email.includes('@') === false) {
            setErrorEmail('잘못된 이메일 형식입니다.');
            check = false;
        } else {
            setErrorEmail('');
        }
        if (password.length < 8) {
            setErrorPassword('비밀번호를 8자리 이상으로 설정해주세요');
            check = false;
        } else {
            setErrorPassword('');
        }
        if (check) {
            setJoin('회원가입이 완료되었습니다!!');
        }
    }
    return (
        <>
            이메일 : <input type="text" onChange={changeEmail} placeholder="이메일을 입력해주세요" />
            <br />
            <span>{errorEmail}</span>
            <br />
            <br />
            비밀번호 : <input type="password" onChange={changePassword} placeholder="비밀번호를 입력해주세요" />
            <br />
            <span>{errorPassword}</span>
            <br />
            <span>{join}</span>
            <br />
            <br />
            <button onClick={btnClick}>회원가입</button>
        </>
    );
}
간단한 회원가입 페이지 코드입니다.
코드가 길어지니 가독성이 많이 떨어집니다.
이 코드에 Container-Presenter 패턴을 적용하겠습니다.

Container-Presenter 패턴 적용

// index.js
import StateSignupContainer from "./StateSignupContainer"
export default function StateSignup() {
    return (
        <>
            <StateSignupContainer>
        </>
    );
}
// StateSignupContainer.js
import { useState } from 'react';
import StateSignupPresenter from "./StateSignupPresenter"
export default function StateSignupContainer() {
  const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [errorEmail, setErrorEmail] = useState('');
    const [errorPassword, setErrorPassword] = useState('');
    const [join, setJoin] = useState('');
    function changeEmail(event) {
        console.log(event.target.value); 
        setEmail(event.target.value);
    }
    function changePassword(event) {
        console.log(event.target.value);
        setPassword(event.target.value);
    }
    function btnClick() {
        let check = true;
        console.log(`email : ${email}`);
        console.log(`password : ${password}`);
        if (email.includes('@') === false) {
            setErrorEmail('잘못된 이메일 형식입니다.');
            check = false;
        } else {
            setErrorEmail('');
        }
        if (password.length < 8) {
            setErrorPassword('비밀번호를 8자리 이상으로 설정해주세요');
            check = false;
        } else {
            setErrorPassword('');
        }
        if (check) {
            setJoin('회원가입이 완료되었습니다!!');
        }
    }
    return (
        <>
            <StateSignupPresenter changeEmail={changeEmail} changePassword={changePassword} btnClick={btnClick}>
        </>
    );
}
// StateSignupPresenter.js
export default function StateSignupPresenter(props) {
    return (
        <>
            이메일 : <input type="text" onChange={props.changeEmail} placeholder="이메일을 입력해주세요" />
            <br />
            <span>{errorEmail}</span>
            <br />
            <br />
            비밀번호 : <input type="password" onChange={props.changePassword} placeholder="비밀번호를 입력해주세요" />
            <br />
            <span>{errorPassword}</span>
            <br />
            <span>{join}</span>
            <br />
            <br />
            <button onClick={props.btnClick}>회원가입</button>
        </>
    );
}
이런식으로 js와 jsx를 분리해서 2개의 컴포넌트로 나누고, pages에 있는 index.js에는 Container component를 import헤서 사용합니다.
Container component에서 기능(api 등)을 구현하고, UI를 담당하는 Presenter component에 데이터를 props를 통해서 전달을 합니다.
Container component에서 Presenter component에 데이터를 props를 통해 전달을 한다고 하는데... props가 뭔가요?

props

props는 리엑트에서 state와 같이 다루는 데이터의 종류입니다.

state가 컴포넌트 안에서 선언해서 해당 컴포넌트에서만 사용이 가능하고,

props는 부모컴포넌트에서 데이터를 자식컴포넌트로 데이터를 전달해주는 객체입니다.

(* 부모컴포넌트, 자식컴포넌트는 예를 들어, A, B, C라는 컴포넌트가 있습니다. A 컴포넌트 안에 B, C 컴포넌트를 사용(import를 해서 가지고 옵니다)을 하면, A는 부모컴포넌트, B, C는 자식컴포넌트 입니다.)
(* 주로 만들어진 함수나, State를 자식컴포넌트에 전달을 해서 사용합니다!)

위 Container-Presenter 패턴의 경우, Presenter를 Container안에서 사용을 하기 때문에 Container가 부모컴포넌트, Presenter이 자식컴포넌트가 됩니다.

props를 사용하는 방법

props 사용방법은 <자식컴포넌트이름 props이름 = {value}> 이런 형태로 사용이 가능합니다.

HTML의 속성을 정의하듯이, props는 자식 컴포넌트의 속성처럼 사용하면 됩니다.

자식컴포넌트에서는 처음에 컴포넌트를 선언할 때, 매개변수로 props를 추가하고,

필요한 곳에서 props.props이름을 사용하면 됩니다.

부모컴포넌트
import Children from "./Children";
import { useState } from "react";
export default function Parents(){
	const[hello,setHello] = useState("Hello")
    const onClickBtn = (){
      setHello("안녕하세요");
    }
  return(
    	<Children hello={hello} onClickBtn={onClickBtn} />
    );
}
자식컴포넌트
export default function Children(props){
 	 return(
    	<button onClick={props.onClickBtn}/>
       	<div>{props.hello}</div>
    );
}
```

setState 동작 원리

지난번 State에 이어서 setState가 어떤 방법으로 동작이 되는지 간단하게 정리하겠습니다.

React state에서 setState로 state를 변경을 할 경우, React에서 가상의 임시저장공간에 실행 내용을 저장하고, 실행 내용이 다 끝난 이후에 최종적으로 한번만 리렌더링을 합니다.

쉽게 얘기해서 어느 한 함수안에 setState를 이용해서 하나의 state를 여러번 반복해서 변경을 할 경우, setState가 매번 실행이 되는 것이 아니라, 실행해야되는 내용을 임시저장공간에 저장을 하고, 함수가 종료가되면 그때 setState의 내용을 실행합니다.(setState는 이벤트 핸들러 안에서 비동기로 작동합니다.)

const [A, setA] = useState(0)
function test(){
	setA(1)
	console.log(A) // 0
}
위 코드와 같이 setA(1)로 state A의 값을 변경을 해도 test의 함수가 끝나기 전까지는 setA가 실행이 되지 않아서 console.log(A)가 0이 나옵니다.

|| 연산자, Nullish coalescing

지난번 조건부 렌더링에서 배운 다양한 연산자들이 있었는데 그 외에 2개의 연산자를 추가로 알아보겠습니다.

|| 연산자

|| 연산자 && 연산자의 반대의 기능입니다.
&& 연산자는 조건이 참일 경우에 렌더링을 하는 연산자이고,
|| 연산자는 조건이 거짓(null, 0, '', undefined, NaN, false)일 경우에 렌더링을 하는 연산자입니다.
export default function TestANDRender() {
    const a = true;
    const b = false;
    return (
        <>
            {a || <div>안녕하세요</div>} //화면에는 "반갑습니다"만 렌더링이 되고, 
            {b || <div>반갑습니다</div>} //"안녕하세요"는 렌더링이 되지 않습니다.
        </>
    );
}

Nullish coalescing

Nullish coalescing은 ||와 조금 비슷하지만 한가지 다른 점이 있습니다.
|| 연산자는 모든 false 값 일때 렌더링을 한다면, Nullish coalescing은 null과 undefined일 때만 렌더링을 합니다.
Nullish coalescing은 ??로 사용합니다.

// Nullish coalescing
export default function TestANDRender() {
    const a = null;
    const b = undefined;
    const c = 0;
    return (
        <>
            {a ?? <div>안녕하세요</div>} //화면에는 "안녕하세요", "반갑습니다"가 렌더링이 되고, 
            {b ?? <div>반갑습니다</div>} //"잘부탁합니다"는 렌더링이 되지 않습니다.
            {c ?? <div>잘부탁합니다</div>}
        </>
    );
}

Jaenk의 한마디
드디어 props를 배웠어요 👏 👏 👏 👏 👏
props를 이용하니까 Container-Presenter 패턴도 가능해졌네요!
state와 props 많이 연습해야겠습니다🔥 🔥 🔥

좋은 웹페이지 즐겨찾기