react
virtual Dom 사용
virtual dom : 가상의 dom 이며 브라우저에 실제로 보여지는 dom이 아닌 메모리에 가상으로 존재하는 dom으로서 그냥 JS 객체이기 때문에 작동 성능이 실제로 브라우저에서 DOM을 보여주는 것 보다 속도가 빠르다.
리액트는 상태가 업데이트 되면 업데이트가 필요한 곳의 ui를 virtual dom을 통해 렌더링 한다. 이후 브라우저에 보여지고 있는 dom과 비교 후에 차이가 있는 곳을 확인하여 실제 dom 에 패치시킨다.
새로운 리액트 프로젝트 만들기
(begin-react 디렉터리가 생기면서 그 안에 리액트 프로젝트가 생성된다.)$ npx create-react-app begin-react
cd 명령어를 사용하여 생성된 디렉터리로 이동한 후 yarn start 명령 또는 npm start 명령어를 사용하여 브라우저를 시작한다.
시작되는 브라우저: http://localhost:3000/
(자동으로 페이지가 열리지 않는다면 브라우저에 주소를 직접 입력하여 들어간다.)$ cd begin-react $ yarn start
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;
코드에서 ReactDOM.render의 역할
: 브라우저에 있는 실제 DOM 내부에 리액트 컴포넌트를 렌더링하겠다는 의미
JSX는 리액트에서 생김새를 정의할 때 사용하는 문법이며 HTML과 비슷한 생김새를 갖는다. 리액트 컴포넌트 파일에서 XML형태로 코드를 작성하면 babel이 JSX를 JavaScript로 변환해준다.
Babel
: 자바스크립트의 문법을 확장해주는 도구
- 태그는 꼭 닫혀있어야 한다.
오류 예시<div> <Hello /> <Hello /> <Hello /> <div> </div>
- 태그와 태그 사이에 내용이 들어가지 않을 때에는 Self Closing 태그를 사용한다.
Self Closing 태그란 열리고 바로 닫히는 태그를 의미한다.
예시<Hello />
- 꼭 감싸져야하는 태그
두개 이상의 태그는 무조건 하나의 태그로 감싸져 있어야 한다.
오류 예시function App() { return ( <Hello /> <div>안녕히계세요.</div> ); }
올바른 예시
function App() { return ( <div> <Hello /> <div>안녕히계세요.</div> </div> ); }
단순히 감싸기 위해 불필요한 div로 감싸는것은 별로 좋지 않다. 이를 위해 fragment를 사용한다. fragment는 태그를 작성 할 때 이름 없이 작성을 하는 것이며 브라우저 상에서 따로 별도의 엘리먼트로 나타나지 않는다.
<></>function App() { return ( <> <Hello /> <div>안녕히계세요.</div> </> ); }
JSX 내부에 자바스크립트 변수를 보여줘야 할 때에는 {}로 감싸서 사용한다.
function App() {
return (
<>
<Hello />
<div>안녕히계세요.</div>
<div>{name}</div>
</>
);
}
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
//열리는 태그 내부에서는 이렇게 주석 작성이 가능하다.
/>
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.를 입력하는 것을 개선하기 위해 비구조화 할당 즉, 구조분해문법을 사용한다.
Hello.js
import React from 'react'; function Hello({ color, name }) { return <div style={{ color }}>안녕하세요{name}</div>; } export default Hello;
컴포넌트에 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을 조회하면 된다.
먼저 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;
조건부 렌더링
: 특정 조건에 따라 다른 결과물을 렌더링 하는 것
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 은 동일하다.
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라는 값을 업데이트 하는 함수를 파라미터로 사용하여 나타낼 수 있다.
리액트에서 사용자가 입력할 수 있는 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;
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 참고
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(); };
아래 코드는 재사용 되는 코드를 일일히 작성하여 구현한 좋지 않은 코드
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가 있다면 렌더링시 오류 메시지가 콘솔창에 뜨며 업데이트가 제대로 이루어지지 않는다.
useRef의 용도
1. 함수형 컴포넌트에서 DOM 선택
2. 컴포넌트 내에서 조회 및 수정 할 수 있는 변수 관리
리액트 컴포넌트에서의 상태는 상태를 바꾸는 함수를 호출 후에 그 다음 렌더링 이후에야 업데이트 된 상태를 조회 가능하다.
useRef로 관리하는 변수는 값이 바뀐다고 해서 컴포넌트가 리렌더링되지 않고 설정 후에 바로 조회가 가능하다.
useRef로 관리하는 변수를 사용하여 다음 값을 관리 가능하다.
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;