Props 뽀개기(부모, 자식 양방향으로 넘기기)

Romuru·2022년 11월 15일
3

FE tech

목록 보기
1/3
post-thumbnail

모든 예제코드는 직접 작성 했으며, gitHub에서 편하게 훔쳐볼 수 있습니다.
https://github.com/PastelBlue4/blog_post_react_code

22.11.15

by Romuru

Props

Props?

Props(프롭스)란 부모(상위)component에서 자식(하위)component에게 데이터를 전달하기 위한 통로와 비슷한 개념이다

부모,자식(상위,하위)?


// First.js
function First() {
  return <Second/>;
}

export default First;
// Second.js
function Second() {
  return <Third/>;
}

export default Second;
// Third.js
function Third() {
  return <span>응애 나 아기 Third</span>;
}

export default Third;

위 코드에서

First.js 컴포넌트 내부에는 Second.js 컴포넌트가 있고,

Second.js 안에는 Third.js 컴포넌트가 존재한다.

이때, First는 Second와 Third기준에서는 부모(상위) 컴포넌트가 되는 것이고.

Second는 First에게는 자식(하위)컴포넌트, Third 컴포넌트에게는 부모 컴포넌트가 되는것이다.

Third는 First과 Second의 자식 컴포넌트가 된다.

그럼 props는 언제 써야할까?


import Son from "./Son";

function Mother() {
  return (
    <>
      <Son/>
    </>
  );
}
export default Mother;

function Son() {
  return (
    <>
      <span>hi From son!</span>
    </>
  );
}
export default Son;

와 같은 부모,자식관계 컴포넌트가 있다.

출력 결과는 당연하게도 아래와 같이 나온다.

props를 본격적으로 쓰기전에 Son.js를 잠깐 스타일을 입히겠다.

간단하게 꾸며봤다. 여기서 이제 저 스타일을 재활용에서 사용하고 싶다면

계속 css파일을 생성하고 스타일을 복사하고 붙혀놓고 className을 달아줘야 하나?

만약에 내용(데이터)만 다르게 하고 같은 컴포넌트를 계속 호출하고 싶다면 props를 사용해야 상황이다.

부모 > 자식에게 props 전달하기

import Son from "./Son";

function Mother() {
  return (
    <>
      <Son text="hi From Mother!" />
    </>
  );
}
export default Mother;

위와 같이 html 요소에서 onChange, type 등 속성을 주는 방법으로

<Son text="hi From Mother"/>

Son 컴포넌트에게 text라는 상자(props)로 포장해서 "hi From Mother"라는 문자열을 넣어서
호출했다. 이게 부모 컴포넌트가 자식 컴포넌트에게 props를 전달하는 방법이다.


끝~

라고 하기에는 바뀌는게 없다. 우리는 보내기만 했지 받아서 어떻게 쓰겠다는 코드를 작성한적이 없기 때문이다.

넘겨받은 자식컴포넌트에서 props 사용하기.

function Son(props) { // 여기에 있는 props
  return (
    <>
      <div >
        <span >hi From Son!</span>
      </div>
    </>
  );
}
export default Son;
// 	props에 집중하기위해 일단 className 태그는 제거했다. 

다른 많은 글들을 보면 컴포넌트를 선언한 부분에서 인자로 props를 넣는것이 사용방법 이라고 한다.


이론은 어떻게 알았다지만 그래서 props가 어떻게 생겼고 어떻게 쓰는건데?

모를때는 일단 Son 컴포넌트 내부에서 콘솔에 출력해보자.

console.log(props)

출력 결과 :

콘솔에서는 오브젝트가 출력 되었고.

그 오브젝트 내부에는 우리가 설정한 상자(속성)의 이름이 key로,

값으로 넣었던 문자열이 value로 들어가있다!

좀 더 다양한 값을 넣어보자.

const object = { id: "1", title: "title1" };

let moreText = "bback";

function Mother() {
  return (
    <>
      <Son text="hi From Mother!" object={object} moreText={moreText} />
    </>
  );
}
export default Mother;

한번 더 콘솔로 출력해보면

우리가 넘겨준 object, moreText, text 모두 잘 들어가있다!

정리하자면 props는 속성을 객체 형태로 전달하는 상자라고 할 수 있다.

일단 다른 속성들은 잠시 제거하고 text만 사용해보자.

객체 내부의 값을 호출하는 법?

props는 객체형태로 있다 그럼 내부에서 text속성만 빼오면 되는데 객체사용법과 동일하다.

잠깐 설명하고 넘어가자면

const object = {
  				id: "1",
                text : "Some Text", 
                date : "molu"
               }

위와 같은 형태를 가진 객체에서 "Some Text"를 가져와서 사용하고 싶다면

object.text

위와 같이 점(.)을 사용해 "Some Text"의 key인 text를 호출하면 된다.


그렇다면 우리가 사용하고 싶은 오브젝트는 props고 그 안에 key인 text를 호출해서

아래와 같은 형태로 사용하면 되는건가?

props.text

이번에는 콘솔에 말고 span태그에 직접 사용해보자.

function Son(props) {
  return (
    <>
      <div>
        <span>{props.text}</span> //태그 사이에서 js문법을 사용하려면 {}로 묶어야한다.
      </div>
    </>
  );
}
export default Son;

결과 :

결과를 보기전에 스스로 props를 사용하는법을 생각해냈다면 짜장 잘해따.

여기서 우리의 보너스 목표인 동일한 컴포넌트(Son)을 각각 다른 데이터를 넣어서 여러번 호출하기를 해보자!

import Son from "./Son";

function Mother() {
  return (
    <>
      <Son text="hi From Mother!" />
      <Son text="another hello" />
      <Son text="oxxun ba-bo" />
    </>
  );
}
export default Mother;

각각 다른 문자열을 넣어줬다.

브라우저 결과는?

캬 이제 우리는 text만 props로 전달하면 얼마든지 재활용 할 수 있는 컴포넌트를 만들어냈다!

좀 더 깊게 전달하기.

지금까지 한단계 바로 아래의 자식들에게만 Props를 넘겨줬다. 프로젝트를 하다보면

5~6단계의 부모,자식 관계가 생기는건 흔한일이다. 3번째 이후로는 반복이므로 3단계 까지만 예제코드로 준비했다.


// First.js
function First() {
  return (
    <>
      <Second item="earlgrey ourtea is so good!" />;
    </>
  );
}

export default First;
// Second.js
function Second(props) {
  return <>
  		<Third />;
  		</>
}

export default Second;

처음의 부모자식 관계를 설명했던 예시 컴포넌트를 다시 가져왔다.

위 코드에서는 일단 부모인 First에서 위에 방법들과 동일하게 "earlgrey ourtea is so good!"라는 문자열을 item이라는 상자(속성)으로 담아서 자식 컴포넌트인 Second에게 전달했다.

Second에서는 item을 받기 위해 props를 받는 코드를 작성했지만 아직 사용하지는 않았다.

한번 더 반복으로 오브젝트의 사용법인 props.item으로 Thrid 컴포넌트에게 넘겨주면 된다.

// Second.js
function Second(props) {
  return (
    <>
      <Third itemFromSecond={props.item} />
    </>
  );
}

export default Second;

여기서 다시 이해가 안되고 햇갈릴꺼 같은데,

정상이다 햇갈리라고 일부러 이름 이상하게 지어놨다.

// in Second.js
      <Third itemFromSecond={props.item} />

props를 넘기는 부분만 천천히 살펴보면 itemFromSecond라는 속성으로, value는
props.item.

즉 가장 최상단인 First에서 내려받은 데이터를 그대로 내려 보내겠다는 것이다.

이제 Third 에서 받아보자.

function Third(props) {
  return (
    <>
      <span>{props.itemFromSecond}</span>
    </>
  );
}

export default Third;

Second에서 itemFromSecond라는 속성으로 보냈으니 props.itemFromSecond를 사용해 props를 내려 받았다. 근데 이렇게 속성 이름을 막 바꿔도 괜찮은...가...?

결과:

갠찬다. 굳이 이름 어렵게 써놓은 이유는 데이터만 잘 가지고 내려갈 수 있다면 속성 이름은
어떻게 지어도 상관없고 상위 컴포넌트에서 바로 내려받은 데이터(props)일지라도 이름은 마음대로 지어도 된다고 알려주고 싶었다. 이제 그만 때려.

props의 데이터를 수정없이 그대로 하위 컴포넌트를 전달 한다면 속성명도 그대로 가져가는 것도 좋겠지만

[1,2,3,4,5]라는 배열을 itemArray라는 props로 내려받고, 중간에서 첫번째 인덱스의 값만 추출해서

"1"만 하위 컴포넌트로 전달 하고 싶을때는 itemArray라는 속성명 보다는 item이나 currentItem(현재 아이템)등 다른 직관적인 속성명이 더 적합 하다.

자식 > 부모에게 props 전달하기

이론 먼저 설명하면 위에서 사용한 방법으로는 자식에서 부모에게로 props 전달은 못한다.

props를 사용하려면 부모가 자식에게 우선 넘겨주는게 필수적이다. 그럼 어떻게 정보를 넘겨 주고 받을수 있다는걸까?

바로 함수를 사용하면된다. 맞다 그 평소에 하루에도 수십번씩 쓰는 그 함수다. 특정 함수가 아니라 얼마든 만들어서 사용할 수 있다.

우선 순서 먼저 설명하자면.

1.데이터를 인자로 받는 함수를 부모 컴포넌트에서 선언

2.자식 컴포넌트에게 선언한 함수를 props로 전달.

3.자식 컴포넌트에서 전달받은 함수를 자식 컴포넌트 내에 있는 데이터를 담아서 실행.

4.함수 실행 자체는 부모컴포넌트에 있기때문에 데이터를 받아와서 함수는 정상적으로 실행 된다!

일단 이론은 이정도 지만 무슨 말인지 모르겠다는거 알고 있다. 코드로 같이 봐보쟈.

Example Code

import { useState } from "react";

function BeforeDivide() {
  const [isLogin, setIsLogin] = useState(false); //로그인 유무확인
  const [userName, setUserName] = useState(""); // userName을 담을 useState

  const onSubmit = (e) => {
    e.preventDefault(); //새로고침 방지
    setIsLogin(true);
  }; // 제출버튼을 누르면  isLogin state에 true를 넣어서 login form 안보이게.
  

  const onChange = (e) => {
    setUserName(e.target.value);
  }; // 입력을 하면 input의 값을 userName에 담기.

  return (
    <>
      {isLogin ? (
        <span>{userName}</span>
      ) : ( // 삼항 연산자 isLogin이 true면 userName을 보여줌, false면 login form을 보여줌.
        <div>
          <span>닉네임을 입력해 주세요.</span>
          <form onSubmit={onSubmit}>
            <input onChange={onChange} />
            <button>제출</button>
          </form>
        </div>
      )}
    </>
  );
}

export default BeforeDivide;

간단하게 로그인 페이지 흉내를 내는 컴포넌트를 만들어 봤다. 근데 login form의 태그들을

따로 LoginPage 컴포넌트로 분리하고 로그인후 userName을 보여주는 컴포넌트를 MainPage라고 이름을 짓고 구분을 하고싶다.

여기서 문제는.. 우리는 사용자의 입력을 LoginPage에서 받고,

그 입력된 데이터를 부모 컴포넌트인 MainPage로 넘겨줘야 한다. 즉 자식 컴포넌트에서 부모 컴포넌트로 데이터를 넘겨줘야 한다.

위에 써놨듯 부모가 자식에게 편하게 data={itme} 꼴로 props를 넘겨주듯 자식이 부모에게 편하게 넘겨줄수 없고, 함수르르 사용해서 데이터를 주고 받아야한다.

그냥 함수처럼 쓰면댐

말은 쉽다. 천천히 해보쟈.

우리가 함수를 평소에 어떻게 사용을 했던가?

간단하게 두 숫자를 받아서 더하고, 리턴을 하는 함수를 예로 들면

fuction add(a, b){
  
  return a+b
}

add(2,3) // return 5

처럼 구성 되어있다. 여기서 누가 함수를 "호출" 하는가? 데이터를 집어넣고 실행과 결과는 "어디서" 결정 하는가?

같은 파일이라 누가, 어디서 라고 하면 햇갈리겠지만..

add 함수를 부모에서 자식에게 props로 전달하고 add 함수를 자식 컴포넌트에서 실행을 한다면,

우리는 부모가 가지고 있는 add함수를, 자식 컴포넌트에서만 가지고 있는 값을 넣어서 실행 할 수 있게된다.

즉 우리는 함수를 통해서 자식에서 부모로 데이터를 넘길 수 있다.

이제 여기서 add함수를,

1.함수 실행시에 받은 인자를 부모 컴포넌트에 있는 useState에 담음

2.데이터가 넘어온다면 조건부 렌더링을 통해 보여줌

라는 요구사항을 충족시키는 함수로 바꾸면 컴포넌트 나누기가 성공적으로 될 것이다.

아직 감이 잘 안올 수도 있다. 걱정마라 예제코드는 아직 짜장 많다.

function MainPage() {
  const [loginID, setLoginID] = useState("");

  function loginHandler(userName) {
    setLoginID(userName); // 함수 실행시에 userName을 입력받아서 loginId State에 담음.
  }

  return (
    <>
      {loginID ? (
        <span>{`${loginID}님 환영합니다.`}</span>  
      ) : (
        <LoginPage loginHandler={loginHandler} /> 
      )} //loginID가 있다면 환영 문구를 아니라면 LoginPage 컴포넌트를 리턴한다.
    </>
  );
}

export default MainPage;

자 위에서 봤던 코드에서 사용자의 입력을 받는 input form은 LoginPage 컴포넌트로 분리했다.

함수 실행시에 인자로 입력받은 값을 loginID에 담는 간단한 loginHandler라는 함수를 만들어서

함수명과 동일한 속성명으로 자식 컴포넌트 LoginPage에게 props로 넘겨줬다.

그리고 우리가 힘들게 나누고 있는 이유인. loginID에 값이 있다면(true) 환영 문구를 출력하고

없다면 LoginPage를 보여주는 가독성 좋은 코드가 완성이 되었다! 하지만 이제 45% 왔다.

부모에게서 넘겨받은 함수 리모컨으로 사용하기.

일단 부모 컴포넌트에서 loginHandler라는 함수를 props로 넘겨 받았다.

사용자의 입력을 받아서 제출 버튼이 눌렸을때 loginHandler에 인자로 넣고 실행만 하면 된다. 간단하다!

import { useState } from "react";

function LoginPage(props) {
  const [userName, setUserName] = useState(""); //  input입력 값을 받을 state 

  const onSubmit = (e) => {
    e.preventDefault();
    props.loginHandler(userName);
  }; // 제출시에 새로고침을 막고, props로 받은 loginHandler라는 함수에 입력받은 userName을 받아서 실행.

  const onChange = (e) => {
    setUserName(e.target.value);
  }; // 입력값을 userName state에 담는 코드
  return (
    <>
      <span>닉네임을 입력해 주세요.</span>
      <form onSubmit={onSubmit}>
        <input onChange={onChange} />
        <button>제출</button>
      </form>
    </>
  );
}

export default LoginPage;

부모에게서 데이터를 넘겨받을때 사용한 것처럼 함수도 동일하게

props.anyFunction() 식으로 사용하면 된다.

사용자의 입력을 받고, 버튼을 눌러 제출을 하게된다면 props로 받은 loginHandler라는 함수에

userName을 넣어 실행을 하게 된다면


function loginHandler(userName) {
    setLoginID(userName); // 함수 실행시에 userName을 입력받아서 loginId State에 담음.
  }

부모 컴포넌트에서 받아온 userName을 loginID state에 담게 되고, 조건부 렌더링 로직에 따라

받아온 userName이 보이게 된다!

Refactoring 해보면서 이해하기(숙제)


import { useState } from "react";
import DataFetch from "./DataFetch";

const data = [
  {
    id: "1",
    comment: "first data",
  },
  {
    id: "2",
    comment: "second data",
  },
  {
    id: "3",
    comment: "third data",
  },
];

function PostPage() {
  const [postItem, setPostItems] = useState();

  const getData = () => {
    setPostItems(data);
  };

  return (
    <>
      {postItem &&
        postItem.map((postData) => (
          <div>
            <h2>{postData.id}</h2>
            <span>{postData.comment}</span>
          </div>
        ))}

      <button onClick={getData}> 데이터 받아오기 </button>
    </>
  );
}
export default PostPage;

아 여기까지 설명해주고 싶었는데 요즘 파이썬 장고 맛에 들려가지고 쓰는 내내 장고하러 가고 싶어졌다.

리팩토링 코드를 숙제로 내어줄테니 아마 이번주 안에는 해답과 함께 주저리로 돌아올 듯 싶다.

절대 귀찮고 장고가 하고 싶어서 그런건 딱히 맞다. 궁금한점은 언제나 아무 연락처로 편하게 물어봐도 대고.

모든 코드는 github에 올려놨으니 편하게 봐도 댄다.

좋아 친구들 오늘은 여기까지 그럼 안냥!

profile
늘 새로운 호기심을 찾고, 기술적 한계에 도전하고, 하늘색이 잘 어울리는 사람입니다.

0개의 댓글