[React] 입문-1

semi·2021년 1월 13일
0

React

목록 보기
2/4

작성코드참고-github

1. 리액트란?

react
virtual Dom 사용
virtual dom : 가상의 dom 이며 브라우저에 실제로 보여지는 dom이 아닌 메모리에 가상으로 존재하는 dom으로서 그냥 JS 객체이기 때문에 작동 성능이 실제로 브라우저에서 DOM을 보여주는 것 보다 속도가 빠르다.
리액트는 상태가 업데이트 되면 업데이트가 필요한 곳의 ui를 virtual dom을 통해 렌더링 한다. 이후 브라우저에 보여지고 있는 dom과 비교 후에 차이가 있는 곳을 확인하여 실제 dom 에 패치시킨다.

2. 새 프로젝트 만들기

새로운 리액트 프로젝트 만들기
(begin-react 디렉터리가 생기면서 그 안에 리액트 프로젝트가 생성된다.)

$ npx create-react-app begin-react

cd 명령어를 사용하여 생성된 디렉터리로 이동한 후 yarn start 명령 또는 npm start 명령어를 사용하여 브라우저를 시작한다.
시작되는 브라우저: http://localhost:3000/
(자동으로 페이지가 열리지 않는다면 브라우저에 주소를 직접 입력하여 들어간다.)

$ cd begin-react
$ yarn start

3. 나의 첫번째 리액트 컴포넌트

src 디렉터리에 Hello.js 파일을 작성한다.

Hello.js

import React from 'react';
function Hello() {
  return <div>안녕하세요</div>;
}
export default Hello;

리액트 컴포넌트를 만들 때는 다음 코드를 사용하여 리액트를 불러와야한다.

import React from 'react';

리액트 컴포넌트는 함수 형태 또는 클래스 형태로 작성 가능하다.
생성한 컴포넌트를 다른 컴포넌트에서 불러와 사용하기 위해서는 컴포넌트 코드의 최하단에 export default를 해주어야 한다.

//Hello라는 컴포넌트를 내보내겠다는 의미
export default Hello;

다른 컴포넌트에서 생성한 컴포넌트를 사용할 때에는

import Hello from './Hello';

처럼 import를 사용하여 사용할 수 있으며
컴포넌트는 일종의 UI 조각이고 쉽게 재사용이 가능하다.

App.js

import React from 'react';
import Hello from './Hello';
function App() {
  return (
    <div>
      <Hello />
    //재사용가능함
      <Hello />
      <Hello />
    </div>
  );
}
export default App;

index.js

코드에서 ReactDOM.render의 역할
: 브라우저에 있는 실제 DOM 내부에 리액트 컴포넌트를 렌더링하겠다는 의미

4. JSX의 기본 규칙 알아보기

JSX는 리액트에서 생김새를 정의할 때 사용하는 문법이며 HTML과 비슷한 생김새를 갖는다. 리액트 컴포넌트 파일에서 XML형태로 코드를 작성하면 babel이 JSX를 JavaScript로 변환해준다.

Babel
: 자바스크립트의 문법을 확장해주는 도구

JSX에서 지켜야할 규칙

  1. 태그는 꼭 닫혀있어야 한다.
    오류 예시
    <div>
      <Hello />
      <Hello />
      <Hello />
      <div>
    </div>
  1. 태그와 태그 사이에 내용이 들어가지 않을 때에는 Self Closing 태그를 사용한다.
    Self Closing 태그란 열리고 바로 닫히는 태그를 의미한다.
    예시
<Hello />
  1. 꼭 감싸져야하는 태그
    두개 이상의 태그는 무조건 하나의 태그로 감싸져 있어야 한다.
    오류 예시
function App() {
  return (
    <Hello />
    <div>안녕히계세요.</div>
  );
}

올바른 예시

function App() {
  return (
    <div>
      <Hello />
      <div>안녕히계세요.</div>
    </div>
  );
}

Fragment

단순히 감싸기 위해 불필요한 div로 감싸는것은 별로 좋지 않다. 이를 위해 fragment를 사용한다. fragment는 태그를 작성 할 때 이름 없이 작성을 하는 것이며 브라우저 상에서 따로 별도의 엘리먼트로 나타나지 않는다.
<></>

function App() {
  return (
    <>
      <Hello />
      <div>안녕히계세요.</div>
    </>
  );
}

JSX안에 자바스크립트 값 사용하기

JSX 내부에 자바스크립트 변수를 보여줘야 할 때에는 {}로 감싸서 사용한다.

function App() {
  return (
    <>
      <Hello />
      <div>안녕히계세요.</div>
      <div>{name}</div>
    </>
  );
}

style과 className

JSX에서 태그에 style과 CSS class를 설정하는 방법은 인라인 스타일은 객체 형태로 작성해야 하며 -로 구분되어 있는 이름들(background-color)은 붙여서 camelCase형태(backgroundColor)로 네이밍 해주어야 한다.
CSS class를 설정 할 때에는 class=가 아닌 className=으로 설정을 해주어야 한다.

App.js

import React from 'react';
import Hello from './Hello';
import './App.css';
function App() {
  const name = 'react';
  const style = {
    backgroundColor: 'black',
    color: 'aqua',
    fontSize: 24,
    padding: '1rem',
  };
  return (
    <>
      <Hello />
      <div style={style}>{name}</div>
      <div>안녕히계세요.</div>
      <div>{name}</div>
      <div className='gray-box'></div>
    </>
  );
}

주석

{/* JSX내부의 주석 
주석은 화면에 보이지 않고 중괄호로 감싸지 않으면 화면에 보인다.*/}
<Hello
//열리는 태그 내부에서는 이렇게 주석 작성이 가능하다.
/>

5. props를 통해 컴포넌트에게 값 전달하기

props
: properties의 줄임말이며 어떠한 값을 컴포넌트에게 전달할때 props를 사용한다.

props의 기본 사용법

App컴포넌트에서 Hello컴포넌트를 사용할 때 name이라는 값을 전달한다고 가정

App.js

import React from 'react';
import Hello from './Hello';
import './App.css';
function App() {
  return (
    <>
      <Hello name='react' color="red"/>
    </>
  );
}

컴포넌트에 전달되는 props는 파라미터를 통해 조회 가능하고 props는 객체 형태로 전달되며 name 값을 조회하려면 props.name을 조회하면 된다.

Hello.js

import React from 'react';
function Hello(props) {
  return <div style={{color: props.color}}>안녕하세요{props.name}</div>;
}
export default Hello;

여러개의 props, 비구조화 할당(=구조 분해)

props 내부의 값을 조회 할때마다 props.를 입력하는 것을 개선하기 위해 비구조화 할당 즉, 구조분해문법을 사용한다.

Hello.js

import React from 'react';
function Hello({ color, name }) {
  return <div style={{ color }}>안녕하세요{name}</div>;
}
export default Hello;

defaultProps로 기본값 설정

컴포넌트에 props를 지정하지 않았을 때 기본적으로 사용 할 값을 설정하고 싶으면 컴포넌트에 defualtProps 값을 설정하면 된다.

Hello.js

import React from 'react';
function Hello({ color, name }) {
  return <div style={{ color }}>안녕하세요{name}</div>;
}
Hello.defaultProps = {
  name: '이름없음',
};
export default Hello;

App.js

import React from 'react';
import Hello from './Hello';
function App() {
  return (
    <>
      <Hello name='react' color='red' />
      {/*name이 없는 Hello 컴포넌트
      ->defalutProps에 의해 값이 배정된다.*/}
      <Hello color='pink' />
    </>
  );
}
export default App;

props.children

컴포넌트 태그 사이에 넣은 값을 조회하고 싶을 경우 props.children을 조회하면 된다.
먼저 Wrapper.js를 src디렉터리에 추가한다.

Wrapper.js(props.chileren추가안한)

import React from 'react';
function Wrapper() {
  const style = {
    border: '2px solid black',
    padding: '16px',
  };
  return <div style={style}></div>;
}
export default Wrapper;

App.js

import React from 'react';
import Hello from './Hello';
import Wrapper from './Wrapper';
import './App.css';
function App() {
  return (
    <Wrapper>
      <Hello name='react' color='red' />
      <Hello color='pink' />
    </Wrapper>
  );
}
export default App;

Hello 컴포넌트들이 보이지 않는다.

내부의 내용이 보여지게 하기 위해 Wrapper.js에 props.children을 렌더링해준다.

Wrapper.js

import React from 'react';
function Wrapper({ children }) {
  const style = {
    border: '2px solid black',
    padding: '16px',
  };
  return <div style={style}>{children}</div>;
}
export default Wrapper;

6. 조건부 렌더링

조건부 렌더링
: 특정 조건에 따라 다른 결과물을 렌더링 하는 것

App 컴포넌트에서 Hello 컴포넌트 사용 시 isSpecial 라는 props 사용
(true 값은 자바스크립트 값이기 때문에 사용시에 {}로 감싸준다.)

App.js

import React from 'react';
import Hello from './Hello';
import Wrapper from './Wrapper';
function App() {
  return (
    <Wrapper>
      <Hello name="react" color="red" isSpecial={true}/>
      <Hello color="pink" />
    </Wrapper>
  )
}
export default App;

Hello컴포넌트에서 isSpecial값에 따라 컴포넌트의 좌측에 *가 표시되도록 한다.

Hello.js

import React from 'react';
function Hello({ color, name, isSpecial }) {
  return (
    <div style={{ color }}>
      {/*{isSpecial ? <b>*</b> : null}*/}
      {/*단축평가논리계산법*/}
      {isSpecial && <b>*</b>}
      안녕하세요 {name}
    </div>
  );
}
Hello.defaultProps = {
  name: '이름없음'
}
export default Hello;

(JSX에서 null, false, undefiend를 렌더링하면 아무것도 나타나지 않게 된다.)

props 값 설정을 생략하면 항상 true 이다.

isSpecial={true}와 isSpecial 은 동일하다.

7. useState를 통해 컴포넌트에서 바뀌는 값 관리하기

useState
: 리액트의 Hooks 중 하나이며 컴포넌트에서 보여줘야 하는 내용이 사용자 인터랙션에 따라 동적으로 바뀔 수 있도록 한다.

버튼을 누르면 숫자가 바뀌는 Counter 컴포넌트

src디렉터리에 Counter.js를 추가한다.

이벤트 설정

onClick을 사용하여 버튼에 클릭 이벤트를 추가해준다.
리액트에서 엘리먼트에 이벤트를 설정해줄때는
on이벤트이름 = {실행하고싶은함수}
형태로 설정하며 {}안에는 함수형태를 넣어야지 함수를 실행하면 렌더링되는 시점에서 함수가 호출되버리기 때문에 안된다.
잘못된 예시 : onClick={onIncrease()}

동적인 값 주기, useState

state(상태) : 컴포넌트에서의 동적인 값
useState : 리액트에서 컴포넌트의 상태를 관리해주는 함수

import React, {useState} from 'react';

위 코드처럼 리액트 패키지에서 useState라는 함수를 불러와 사용한다.

const [number, setNumber] = useState(0);

useState를 사용할 때는 상태의 기본값을 파라미터로 넣어서 호출하고 함수를 호출하면 배열이 반환되며 첫번째 원소는 현재 상태이고 두번째 원소는 Setter 함수이다.(배열 비구조화 할당을 사용하여 각 원소를 추출하였다.)
Setter 함수 : 파라미터로 전달 받은 값을 최신 상태로 설정한다.

Counter.js

import React, { useState } from 'react';
function Counter() {
  const [number, setNumber] = useState(0);
  const onIncrease = () => {
    //console.log('+1');
    setNumber(number + 1);
  };
  const onDecrease = () => {
    //console.log('-1');
    setNumber(number - 1);
  };
  return (
    <div>
      <h1>{number}</h1>
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
    </div>
  );
}
export default Counter;

App.js

import React from 'react';
import Counter from './Counter';
function App() {
  return (
    <Counter />
  );
}
export default App;

함수형 업데이트

함수형 업데이트는 주로 컴포넌트를 최적화 하게 될 때 사용한다.

Setter함수 사용시 업데이트 하고 싶은 새로운 값을 파라미터로 넣어주는 대신 기존 값을 어떻게 업데이트 할 지에 대한 함수를 등록하여 값을 업데이트 할 수 있다.

const onIncrease = () => {
    setNumber(number + 1);
  };

위 코드를 함수형 업데이트를 사용하면

const onIncrease = () => {
    setNumber(prevNumber => prevNumber + 1);
  }

위와 같이 prevNumber라는 값을 업데이트 하는 함수를 파라미터로 사용하여 나타낼 수 있다.

8. input 상태 관리하기

리액트에서 사용자가 입력할 수 있는 input 태그의 상태를 관리한다.

input에 입력하는 값이 하단에 나타나게 하고, 초기화 버튼을 누르면 input 값이 비워지도록 구현한다.
useState를 사용하여 input의 값을 관리한다.
input에는 onChange라는 이벤트를 사용하며 이벤트 객체 e를 파라미터로 받아와서 사용하고 e.target은 이벤트가 발생한 input DOM을 가리킨다. e.target.value를 조회하면 현재 input에 입력된 값을 알 수 있다.

InputSample.js

import React, { useState } from 'react';
function InputSample() {
  const [text, setText] = useState('');
  const onChange = (e) => {
    setText(e.target.value);
  };
  const onReset = () => {
    setText('');
  };
  return (
    <div>
      <input onChange={onChange} />
      <button onClick={onReset}>초기화</button>
      <div>
        <b>: {text}</b>
      </div>
    </div>
  );
}
export default InputSample;

App.js

import React from 'react';
import InputSample from './InputSample';
function App() {
  return (
    <InputSample />
  );
}
export default App;

9. 여러개의 input 상태 관리하기

input의 개수가 여러개일때 useState를 여러번 사용하고 onChange도 여러개 만들어서 구현할 수 있지만 좋지 않다.
좋은 방법은 input에 name을 설정하고 이벤트가 발생했을 때 이 값을 참조하는 것이다.
그리고, useState에서는 문자열이 아닌 객체 형태의 상태를 관리해야 한다.

InputSample.js

import React, { useState } from 'react';
function InputSample() {
  const [inputs, setInputs] = useState({
    name: '',
    nickname: '',
  });
  const { name, nickname } = inputs; //비구조화할당
  const onChange = (e) => {
    const { value, name } = e.target;
    setInputs({
      ...inputs, //기존의 input 객체 복사
      [name]: value, //name 키를 가진 값을 value 로 설정
    });
  };
  const onReset = () => {
    setInputs({
      name: '',
      nickname: '',
    });
  };
  return (
    <div>
      <input placeholder='이름' onChange={onChange} value={name} />
      <input placeholder='닉네임' onChange={onChange} value={nickname} />
      <button onClick={onReset}>초기화</button>
      <div>
        <b>:</b>
        {name} ({nickname})
      </div>
    </div>
  );
}
export default InputSample;

리액트에서 객체 수정시 새로운 객체를 만들어서 새로운 객체에 변화를 주고 이것을 상태로 사용해야 한다.

이러한 작업을 "불변성을 지킨다"라고 하며 불변성을 지켜야만 리액트 컴포넌트에서 상태가 업데이트 됐음을 확인하고 필요한 리렌더링이 진행될 수 있다. 잘못된 예시로 기존 상태를 직접 수정할 경우 값을 바꿔도 리렌더링이 되지 않는다.

spread 문법

'...' 문자를 사용한다.

...inputs,

객체의 내용을 모두 펼쳐서 기존 객체를 복사해준다. 즉, 기존의 것을 건들지 않고 새로운 객체를 만든다.
spread연산자는 배열에서도 사용할 수 있으며 여러번 사용도 가능하다.
spread 참고

10. useRef로 특정 DOM 선택하기

JavaScript의 경우 특정 DOM을 선택해야될 때
getElementById,querySelector
같은 DOM Selector함수를 사용하여 DOM을 선택한다.

React에서도 특정 엘리먼트의 크기를 가져오거나 스크롤바 위치를 가져오거나 설정하거나 또는 포커스를 설정해야되는 등의 경우에 DOM을 직접 선택해야 한다.

이때, React에서는 ref라는 것을 사용한다.

함수형 컴포넌트에서 ref를 사용 할 때는 useRef 라는 Hook 함수를 사용한다.

클래스형 컴포넌트에서는 콜백 함수나 React.createRef 라는 함수를 사용한다.

InputSample 에서 초기화 버튼을 눌렀을 때 input에 포커스가 잡히도록 useRef를 사용해 구현해본다.

InputSample.js

import React, { useState, useRef } from 'react';
function InputSample() {
  const [inputs, setInputs] = useState({
    name: '',
    nickname: '',
  });
  const nameInput = useRef();
  const { name, nickname } = inputs; //비구조화할당
  const onChange = (e) => {//내용 위 코드 참조
  };
  const onReset = () => {
    setInputs({
      name: '',
      nickname: '',
    });
    nameInput.current.focus();
  };
  return (
    <div>
      <input
        name='name'
        placeholder='이름'
        onChange={onChange}
        value={name}
        ref={nameInput}
      />
      <input
        name='nickname'
        placeholder='닉네임'
        onChange={onChange}
        value={nickname}
      />
      <button onClick={onReset}>초기화</button>
      <div>
        <b>:</b>
        {name} ({nickname})
      </div>
    </div>
  );
}
export default InputSample;

useRef()를 사용하여 Ref 객체를 만든다.

const nameInput = useRef();

만든 객체를 선택하고 싶은 DOM에 ref값으로 설정해준다. 그러면 Ref 객체의 .current 값은 우리가 원하는 DOM을 가리키게 된다.

<input
        name='name'
        placeholder='이름'
        onChange={onChange}
        value={name}
        ref={nameInput}
      />

onReset 함수에서 특정 DOM에 포커스하는 focus() DOM API를 호출하였다. 여기서 Ref 객체의 .current 값은 우리가 원하는 DOM을 가리킨다.

const onReset = () => {
    setInputs({
      name: '',
      nickname: '',
    });
    nameInput.current.focus();
  };

11. 배열 렌더링하기

아래 코드는 재사용 되는 코드를 일일히 작성하여 구현한 좋지 않은 코드

UserList.js

function UserList() {
  const users = [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
    },
  ];
  return (
    <div>
      <div>
        <b>{users[0].username}</b> <span>({users[0].email})</span>
      </div>
      <div>
        <b>{users[1].username}</b> <span>({users[1].email})</span>
      </div>
      <div>
        <b>{users[2].username}</b> <span>({users[1].email})</span>
      </div>
    </div>
  );
}

컴포넌트를 재사용 가능하도록 수정한다.

UserList.js

function User({ user }) {
  return (
    <div>
      <b>{user.username}</b> <span>({user.email})</span>
    </div>
  );
}
function UserList() {
  const users = [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
    },
  ];
  return (
    <div>
      <User user={users[0]} />
      <User user={users[1]} />
      <User user={users[2]} />
    </div>
  );
}

두번째 코드와 같은 구현은 배열이 고정적인 경우에는 괜찮지만
이렇게 배열의 인덱스를 하나하나 조회하면서 렌더링하는 방법은 동적인 배열을 렌더링하지 못한다.
동적인 배열을 렌더링해야 할 때는 자바스크립트 배열의 내장함수인 map() 을 사용하여 구현한다.

UserList.js

function User({ user }) {
  return (
    <div>
      <b>{user.username}</b> <span>({user.email})</span>
    </div>
  );
}
function UserList() {
  const users = [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
    },
  ];
  return (
    <div>
      {users.map((user) => (
        <User user={user} />
      ))}
    </div>
  );
}

위와 같이 구현을 할 경우 동적으로 렌더링이 가능하지만 아래와 같은 경고메시지를 확인할 수 있다. 이 경고 메시지는 각 고유 원소에 key가 있어야만 배열이 업데이트 될때 효율적으로 렌더링 될 수 있기 때문에 나타난다.

리액트에서 배열을 렌더링 할때는 key 라는 props를 설정해야 한다.
여기서 key 값은 각 원소들마다 가지고 있는 고유값으로 설정해야한다.
만약 배열 안의 원소가 가지고 있는 고유 값이 없다면 map() 함수를 사용할 때 설정하는 콜백함수의 두번째 파라미터 index를 key 로 사용하면된다.

UserList.js

function User({ user }) {
  return (
    <div>
      <b>{user.username}</b> <span>({user.email})</span>
    </div>
  );
}
function UserList() {
  const users = [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
    },
  ];
  return (
    <div>
      {users.map((user) => (
        <User user={user} key={user.id} />
      ))}
    </div>
  );
}

배열을 렌더링 할때는 고유한 key 값이 있는것이 중요하다. 만약 배열안에 중복되는 key가 있다면 렌더링시 오류 메시지가 콘솔창에 뜨며 업데이트가 제대로 이루어지지 않는다.

12. useRef로 컴포넌트 안의 변수 만들기

useRef의 용도
1. 함수형 컴포넌트에서 DOM 선택
2. 컴포넌트 내에서 조회 및 수정 할 수 있는 변수 관리

리액트 컴포넌트에서의 상태는 상태를 바꾸는 함수를 호출 후에 그 다음 렌더링 이후에야 업데이트 된 상태를 조회 가능하다.
useRef로 관리하는 변수는 값이 바뀐다고 해서 컴포넌트가 리렌더링되지 않고 설정 후에 바로 조회가 가능하다.

useRef로 관리하는 변수를 사용하여 다음 값을 관리 가능하다.

  • setTimeout, setInterval을 통해 만들어진 id
  • 외부 라이브러리를 사용하여 생성된 인스턴스
  • scroll 위치

App컴포넌트에서 useRef를 사용한 변수 관리
목적 : 배열에 새 항목을 추가할 때 새 항목에서 사용 할 고유 id 관리
먼저 users 배열을 App에서 선언하고 UserList에 props로 전달하고
App에서 useRef()를 사용하여 nextId라는 변수 생성한다.
이때 useRef()안에 파라미터를 넣어주면 .current값의 기본값이 된다. 즉, nextId의 기본값이 된다.
nextId 값을 수정하기 위해 값을 조회하려면 .current를 조회하면 된다.

App.js

import React from 'react';
import UserList from './UserList';
import './App.css';
function App() {
  const users = [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
    },
  ];
    const nextId = useRef(4);
  const onCreat = () => {
    nextId.current += 1;
  };
  return (
    <Wrapper>
      <UserList users={users} />
    </Wrapper>
  );
}
export default App;

UserList.js

import React from 'react';
function User({ user }) {
  return (
    <div>
      <b>{user.username}</b> <span>({user.email})</span>
    </div>
  );
}
function UserList({ users }) {
  return (
    <div>
      {users.map((user) => (
        <User user={user} key={user.id} />
      ))}
    </div>
  );
}
export default UserList;

0개의 댓글