본문 바로가기
Web Study/한입크기로잘라먹는리액트

한입 크기로 잘라먹는 리액트 - 3

by 쿠리의일상 2023. 3. 29.

Props

컴포넌트에 데이터를 전달하는 방법

부모 컴포넌트에서 자식 컴포넌트에게 속성과 값을 넘겨주는 것

props 를 사용하여 데이터를 전달하면 컴포넌트는 해당 데이터를 활용하여 UI를 동적으로 생성하고 업데이트 할 수 있다.

 

기본적으로 키=값 형태로 넘겨주게 되는데,

<자식컴포넌트 속성1={속성값1} 속성2={속성값2} ... />

넘겨줄 속성이 많아지면 속성:속성값 형태의 객체로 만들어서 전개 연산자(...)와 함께 사용해준다.

const customProps = {
	속성: 속성값,
    속성2: 속성값2,
};

// ...
<자식컴포넌트 {...customProps} />

 

결과적으로 자식 컴포넌트에서는 위에서 받아준 속성값을 props 객체에 담아서 넘겨주게 된다.

비 구조화 할당을 활용하여 props 객체에 담겨오는 속성값만 접근해줄 수 있다.

// children 은 props 객체에서 children 키만 받아오게 된다.
const Container = ({ children }) => {
	return (
    	<div>
        	{ children }
        </div>
    );
};

 

자식 컴포넌트에서 props를 사용하는데, 혹여 부모 컴포넌트에서 속성을 넘겨주는 것을 잊어버린다고해도, 오류가 나지 않게 하기 위해

자식컴포넌트.defaultProps = { } 형태로 디폴트값을 객체로 넣어줄 수 있다.

 

이때, 부모 컴포넌트가 리렌더링 되면, 자식 컴포넌트 또한 리렌더링 된다!

 

React 에서 사용자 입력 처리

다양한  사용자 입력 처리

- 한줄 입력 처리

- 여러 줄 입력 처리

- 선택 박스 입력 처리

- 사용자 입력 데이터 핸들링하기

 

 

컴포넌트로 한줄 입력 처리하기

<input 
  type='text' 
  value={author}
  onChange={(e) => {
    setAuthor(e.target.value);
  }}
/>

 

 

 

입력을 리액트로 받아주기

import React, {useState} from 'react';

export default function DiaryEditor() {
  const [author, setAuthor] =useState('');

  return (
    <div className='DiaryEditor'>
      <h2>오늘의 일기</h2>
      <div>
        <input 
          name='author'
          value={author}
          onChange={(e) => {
          	setAuthor(e.target.value);
          }}
        />
      </div>
    </div>
  );
}

인풋 태그에 입력되는 값인 value 속성을 state 로 두고, 입력 필드므로 onChange 이벤트에 e.target.value 값으로 상태 변화 함수를 주면 입력되는 내용이 제대로 입력되게 된다.

 

import React, {useState} from 'react';

export default function DiaryEditor() {
  const [author, setAuthor] = useState('');
  const [content, setContent] = useState('');

  return (
    <div className='DiaryEditor'>
      <h2>오늘의 일기</h2>
      <div>
        <input 
          name='author'
          value={author}
          onChange={(e) => {
          	setAuthor(e.target.value);
          }}
        />
      </div>
      <div>
      	<textarea
        	name='content'
            value={content}
            onChange={(e) => {
            	setContent(e.target.value);
            }}
        />
      </div>
    </div>
  );
}

추가로 입력칸을 만들고 보면 동일한 상태 관리가 중복됨을 알 수 있다.

그래서 각각의 상태를 하나의 상태 객체로 만들어서 관리해주면,

 

import React, {useState} from 'react';

export default function DiaryEditor() {
  const [state, setState] = useState({
  	author : '',
    content : '',
  });
  
  const changeValue = (e) => {
  	setState({
    	...state,
        [e.target.name] : e.target.value,
    });
  }

  return (
    <div className='DiaryEditor'>
      <h2>오늘의 일기</h2>
      <div>
        <input 
          name='author'
          value={state.author}
          onChange={changeValue}
        />
      </div>
      <div>
      	<textarea
        	name='content'
            value={state.content}
            onChange={changeValue}
        />
      </div>
    </div>
  );
}

객체 형태로 상태를 핸들링하기 위해선 메모리 주소값으로 되었기 때문에 키에 직접 접근하여 값을 수정해주는 것은 리액트와 JS 특성상 값이 변화되었다고 판단하지 않는다.

 

그래서 상태 변화 함수를 사용하려면, 새로운 객체를 매번 재선언해줄 필요가 있는데, 이 때 전개 연산자(...) 를 활용하여

원래 프로퍼티를 copy 한 다음에, 바꿔줄 키값만 [ ] 대괄호로 키에 접근하여 변경해주는 방식을 사용할 수 있다.

키에 접근하기 위해서 e.target.name 값을 활용해주고 변화되는 값은 e.target.value 를 활용해준다.

 

import React, {useState} from 'react';

export default function DiaryEditor() {
  const [state, setState] =useState({
    author: '',
    content: '',
    emotion: 1,
  });

  const handleChangeState = (e) => {
    setState({
      ...state,
      [e.target.name] : e.target.value,
    });
  }

  const handleSubmit = () => {
    console.log(state, '저장 성공');
  }

  return (
    <div className='DiaryEditor'>
      <h2>오늘의 일기</h2>
      <div>
        <input 
          name='author'
          value={state.author}
          onChange={handleChangeState}
        />
      </div>
      <div>
        <textarea 
          name='content'
          value={state.content} 
          onChange={handleChangeState}
        />
      </div>
      <div>
        오늘의 감정 점수 : 
        <select 
          name="emotion"
          value={state.emotion}
          onChange={handleChangeState}
        >
          <option value={1}>1</option>
          <option value={2}>2</option>
          <option value={3}>3</option>
          <option value={4}>4</option>
          <option value={5}>5</option>
        </select>
      </div>
      <div>
        <button onClick={handleSubmit}>일기 저장</button>
      </div>
    </div>
  );
}

한 가지 상태와 상태 변화 함수를 활용하여 여러 조건을 핸들링 할 수 있다.

 

결과적으로 버튼을 눌렀을 때 state 에 담기는 값은 현재 입력한 값이 들어가게 된다.

 

 

 

useRef

리액트에서 DOM 조작하기

import React, {useState, useRef} from 'react';

const authorInput = useRef();
const contentInput = useRef();

1. 가장 먼저 useRef() 훅을 사용하여 DOM에 접근하겠다고 선언해준다.

 

2. 그 다음 태그 안에 ref 라는 속성을 사용하여 위에 선언한 변수를 넣어준다.

<div>
<input 
  name='author'
  ref={authorInput}
  value={state.author}
  placeholder='작성자'
  onChange={handleChangeState}
/>
</div>
<div>
<textarea 
  name='content'
  ref={contentInput}
  value={state.content} 
  placeholder='내용'
  onChange={handleChangeState}
/>
</div>

 

3. useRef 변수를 사용해주려면 current 에 접근해주면 된다.

해당 경우는 일기를 저장하는 버튼을 눌렀을 경우, 입력을 다 받지 못했을 때 해당 인풋 필드로 focus() 를 주는 예제임

  const handleSubmit = (e) => {
    if(state.author.length < 1) {
      authorInput.current.focus();
      return;
    }

    if(state.content.length < 5) {
      contentInput.current.focus();
      return;
    }

    console.log(state, '저장 성공');
  }

 

 

써준 일기를 리스트에 추가하기

 

일단, 객체의 배열로 만든 더미 데이터를 Props 로 받아와서 처리해준다.

import './App.css';
import DiaryEditor from './components/DiaryEditor';
import DiaryList from './components/DiaryList';

const dummyList = [
  {
    id : 1,
    author : '가나다라',
    content :'첫글',
    emotion: 2,
    created_date: new Date().getTime(),
  },
  {
    id : 2,
    author : '2일',
    content :'뭐쓰지',
    emotion: 5,
    created_date: new Date().getTime(),
  },
  {
    id : 3,
    author : '모두모두',
    content :'3빠',
    emotion: 1,
    created_date: new Date().getTime(),
  },
]

function App() {
  return (
    <div>
      <DiaryEditor/>
      <DiaryList diaryList={dummyList} />
    </div>
  );
}

export default App;

받아온 props는 비 구조화 할당을 사용하여, props 자체가 아닌 매개변수로 받아와준다.

import React from 'react'

export default function DiaryList({diaryList}) {
  return (
    <div>
      <h2>일기 리스트</h2>
      <h4>총 {diaryList.length}개의 일기가 있습니다.</h4>
      <div>
        {diaryList.map((e) => {
          return (
            <div key={e.id}>
              <p>작성자 : {e.author}</p>
              <p>내용 : {e.content}</p>
              <p>감정 점수 : {e.emotion}</p>
              <p>작성 날짜 : {e.created_date}</p>
            </div>
          );
        })}
      </div>
    </div>
  )
}

JSX 에서 반복문으로는 주로 배열 내장메서드인 map 함수를 사용해주며

위와 같은 방식으로 해당 e에 하나씩 키로 접근하여 그려준다.

다만 map 함수를 사용할 때 꼭 return 에 그려줄 요소를 넣어줘야 웹페이지에 그려진다.

 

여기서 diaryList 에 예기치 못하게 undefined 가 들어간다면? -> 심각한 에러를 발생한다.

이를 방지하기 위해선 함수형컴포넌트.defaultProps 기능을 사용하여 디폴트값을 미리 지정해줄 수 있다.

defaultProps 를 사용하기 위해선 함수형 컴포넌트 바깥에 써주며, props에서 받아준 변수를 초기화시키면 된다.

DiaryList.defaultProps = {
  diaryList: [],
};

 

DiaryList 에서 DiaryItem 분리하기

리스트 부분에 아이템을 분리해주면 유지보수가 더욱 쉬워진다.

들어갈 내용 자체는 DiaryEditor 에서 가져와서, List 측에서는 그 정보를 DiaryItem 의 props 로 보내주는 형태로 만들어준다.

import React from 'react'
import DiaryItem from './DiaryItem';

export default function DiaryList({diaryList}) {
  return (
    <div className='DiaryList'>
      <h2>일기 리스트</h2>
      <h4>총 {diaryList.length}개의 일기가 있습니다.</h4>
      <div>
        {diaryList.map((e) => <DiaryItem key={e.id} {...e} />)}
      </div>
    </div>
  )
}

DiaryList.defaultProps = {
  diaryList: [],
};
import React from 'react'

export default function DiaryItem({ author, content, emotion, created_date, id}) {
  return (
    <div className='DiaryItem'>
      <div className='info' key={id}>
        <span>작성자 : {author} | 감정 점수 : {emotion}</span>
        <br/>
        <span className='date'>{new Date(created_date).toLocaleString()}</span>
      </div>
      <div className='content'>{content}</div>
    </div>
  )
}

 


컴포넌트&데이터 구조 생각해보기

리액트는 부모와 자식 계층이 얽힌 트리 구조

 

리액트에선 같은 레벨(형제?)에선 데이터를 주고 받을 수 없다.

부모 <-> 자식 컴포넌트 방향으로 데이터를 주고 받을  수 있다! (단방향)

 

그러므로 같은 레벨일 때 데이터를 주고 받기 위해선 공통 부모 컴포넌트로 데이터를 끌어와서

state 와 setState 를 각각 나눠 가져서 처리한다.

즉.. 

<App /> 에 [ data, setData ] 를 선언해주고

<DiaryEditor /> 에는 setDate 를 활용하여 일기를 작성해주고

<DiaryList /> 에는 작성된 일기의 data 를 줌으로써

데이터 처리를 구현해줄 수 있는 것이다.

 

이를 정리하면 Event 는 자식 -> 부모 컴포넌트로 흐르는 단방향

data 는 부모 -> 자식 컴포넌트로 흐르는 단방향

서로 반대 방향이란 것을 알 수 있다.

 

----> state 끌어올리기 라고 해준다!