React
- 반응형 프로그래밍 (엑셀)
- 상태를 관찰하고 변화가 발생할 경우 연관된 곳에서 연산이 수행된다.
- 컴포넌트의 조합으로 view를 구성한다.
- 컴포넌트 : 재사용이 가능한 독립적인 객체. 런타임 시점에 사용.
- virtual dom(가상돔) : 필요한 부분만 한 번에 렌더링한다. => 성능보다는 개발을 편하게
처음에는 컴포넌트만 열심히 생각하자.
컴포넌트 구조, 아키텍처, 아름다운 코드는 잠깐 잊고 눈에 보이는 UI를 컴포넌트로 구현해보자.
그러면서 UI를 점차 추상적으로 바라보는 방법을 익히자.
create-react-app
리액트로 app을 만들 수 있는 가장 빠른 방법
웹팩 등을 통한 기초 빌드 설정을 직접하지 않아도 돼서 편하다.
JSX
React는 JSX라는 문법을 채택하여 사용
JSX는 html과 유사한 문법으로 꺽쇠 사이에 타입과 속성을 부여해 프로젝트 구조를 짜는 목적으로 사용
아래 코드들을 JSX 라고 부른다.(가상돔)
1. class가 이미 js내에서 예약어이기 때문에 className을 사용해야한다.
2. jsx에서는 최상위에 하나만 있어야한다. => <>,</> 로 묶어주면 <span></span>태그도 나타낼 수 있다.
3. 표현식을 넣을 수 있다.
4. 조건 => &&, 삼항연산자
5. 반복 => key 프로퍼티를 넣어주어야 한다.(성능 최적화)
function App() {
const name = '리액트'; //3
const showLink = true; //4
const showLogo = 'none'
const names = ['React', 'Vue', 'Angular']
return (
<>
<div className="App">
<header className="App-header">
{
showLogo === "show" ? (
<img src={logo} className="App-logo" alt="logo" />
) : (
<h1>React</h1>
)
}
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
{showLink && ( //4
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn {name}
</a>
)}
<ul>
{
names.map(item => (
<li key={item}>{item}</li>
))
}
</ul>
</header>
</div>
<span>test</span>
</> //2
);
}
컴포넌트
UI를 추상적으로 바라보기 => 공통점이 보임 => 재사용의 시작!
컴포넌트 분리 => 점차 자신만의 규칙을 정한다.
- 도메인으로 분류
- 역할로 분류(header, list)
- 크기로 분류
React의 컴포넌트 = 함수 => props 객체를 받아서 함수의 로직을 거쳐 JSX를 반환
defaultProps
props 값의 default 값을 미리 설정할 수 있다.
// App.js
<header className="App-header">
<Logo size={100}/>
<Logo />
// index.js
function Logo(props) {
return (
<img src={logo} classname="App-logo" alt="logo"
style={{ width: props.size, height: props.size }}/>
)
}
//프롭스를 안넘겼을 때
Logo.defaultProps = {
size: 200,
}
//타입 제한
Logo.propTypes = {
size: PropTypes.number,
}
비구조화 할당으로 default 값을 넣어주면 더 간단하게 코드를 작성할 수 있다.
function Logo({ size = 200 }) {
return (
<img src={logo} classname="App-logo" alt="logo"
style={{ width: size, height: size }}/>
)
}
//타입 제한
Logo.propTypes = {
size: PropTypes.number,
}
children
컴포넌트 사이에 들어온 데이터를 children props로 받아온다.
node 타입을 사용한다. => JSX, element 등을 받을 수 있다.
- props: 어떤 컴포넌트를 import해와서 사용하는 부모(상위) 컴포넌트 (ex. App.js)에서 정하는 값입니다. 부모 컴포넌트에서 설정해서 자식 컴포넌트로 전달하여, 자식 컴포넌트에서 쓰입니다.
- children: A 컴포넌트 사이에 B 컴포넌트가 있을 때, A 컴포넌트에서 B 컴포넌트 내용을 보여주려고 사용하는 props입니다.
분기와 반복
- 컴포넌트는 JSX를 반환하는 함수이다.
- 함수 내에서 JSX를 처리하는 과정에서 분기와 반복을 사용할 수 있다.
- JSX 내에서 if와 for 같은 문법 사용이 어렵기 때문에 편의를 위해 표현식인 삼항연산자와 map, filter 등을 사용한다.
import { useState } from 'react'
function App() {
const [visible, setVisible] = useState(false);
return (
<div>
<button onClick={() => setVisible(!visible)}>Toggle</button>
{visible && (
<h1>논리곱 연산자를 통해 쉽게 JSX 렌더링 여부를 결정할 수 있습니다.</h1>
)}
</div>
);
}
export default App;
삼항연산자
{visible ? (
<h1>논리곱 연산자를 통해 쉽게 JSX 렌더링 여부를 결정할 수 있습니다.</h1>
) : null}
가장 상위의 요소에 key를 넣어주어야한다. 각 요소를 데이터와 매칭시켜준다. => 성능 최적화
상태와 이벤트 바인딩
const [상태, 상태를 업데이트하기 위한 함수] = useState(0)
use-- : hook => 함수 내의 상태를 관리
- 컴포넌트에서 지역 상태 관리하는 법 : useState를 사용하여 지역적으로 상태 관리
- 컴포넌트에서 이벤트 바인딩하기 => 자식 컴포넌트에서 이벤트가 동작했을 때 부모 컴포넌트에 데이터를 전달
- 부모 컴포넌트에게 메시지 전달하기 : 부모 컴포넌트에서 props를 통해 메시지를 받을 수 있도록 함수를 전달
counter 예제
import { useState } from 'react'
function Counter({ onIncrease, onDecrease }) {
const [count, setCount] = useState(0);
// 이벤트가 발생했을 때 실행할 함수 정의
const handleIncrease = () => {
setCount(count + 1)
if (onIncrease) onIncrease(count + 1)
}
const handleDecrease = () => {
setCount(count - 1)
if (onDecrease) onDecrease(count - 1)
}
return (
<div>
<span style={{ fontSize: 50 }}>{count}</span>
<br />
<button onClick={handleIncrease}>+</button>
<button onClick={handleDecrease}>-</button>
</div>
)
}
export default Counter;
// App 컴포넌트
import { useState } from 'react'
import Counter from './components/Counter'
function App() {
const [totalCount,setTotalCount] = useState(0)
return (
<div>
TotalCount: {totalCount}
<Counter
onIncrease={(count) => setTotalCount(totalCount + 1)}
onDecrease={(count) => setTotalCount(totalCount - 1)}
/>
<Counter
onIncrease={(count) => setTotalCount(totalCount + 1)}
onDecrease={(count) => setTotalCount(totalCount - 1)}
/>
<Counter
onIncrease={(count) => setTotalCount(totalCount + 1)}
onDecrease={(count) => setTotalCount(totalCount - 1)}
/>
</div>
);
}
export default App;
useEffect
무언가 변화가 있을 때 감지하여 반응하는 hook
첫번째 파라미터 : 반응
두번째 파라미터 : 어떤 것을 감지할지 배열로
import { useEffect, useState } from 'react'
function App() {
const [count, setCount] = useState(0)
useEffect(() => {
console.log(`Clicked ${count}`)
}, [count]) //count 변화 감지
useEffect(() => {
console.log('Component Loaded')
const handelScroll = () => {
console.log(window.scrollY)
}
//전역적으로 이벤트를 생성했을 때 반드시 해제(remove)
document.addEventListener('scroll', handelScroll)
return () => document.removeEventListener('scroll', handelScroll)
}, []) // 컴포넌트가 처음 로드될 때 실행됨.
return (
<div>
<div>You cliked {count} </div>
<button onClick={() => setCount(count + 1)}>+</button>
<div style={{height: 10000}}></div>
</div>
);
}
export default App;
useRef
Dom에 직접 접근할 때와 지역 변수로 사용할 때 사용한다.
- useState는 값이 변경될 때 다시 렌더링
- useRef는 값이 변경되더라도 다시 렌더링
import { useRef } from 'react'
function App() {
const inputRef = useRef()
return (
<div>
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</div>
);
}
export default App;
컴포넌트를 통해서도 DOM에 직접 접근할 수 있다.
- React.forward 메서드를 통해 ref 전달
//index.js
import React from "react";
const Input = React.forwardRef((_, ref) => {
return (
<>
Input: <input ref={ref} />
</>
);
});
export default Input;
//App.js
return (
<div>
<Input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</div>
);
지역 변수로 사용
//AutoCounter.js
import { useRef, useState } from 'react'
const AutoCounter = () => {
const [count, setCount] = useState(0)
const intervalId = useRef()
const handleStart = () => {
intervalId.current = setInterval(() => { //intervalId가 변화하더라도 다시 렌더링하지 않는다.
setCount((count) => count + 1)
}, 1000)
}
const handleStop = () => {
clearInterval(intervalId.current)
}
return (
<>
<div>{count}</div>
<button onClick={handleStart}>Start</button>
<button onClick={handleStop}>Stop</button>
</>
)
}
export default AutoCounter;
페이지네이션
게시판
//App.js
import { useState } from 'react';
import './App.css';
import Board from './components/Board';
import Pagination from './components/Pagination';
function App() {
const [page, setPage] = useState(0);
const dummyData = Array.from({length: 100}, (_, i) => ({
id: i + 1,
title: `${i + 1}번째 게시물`
}))
const limit = 10;
const offset = page * limit;
console.log(limit, offset);
return (
<div className="App">
<Pagination defaultPage={0} limit={limit} total={dummyData.length} onChange={setPage} />
<Board dummyData={dummyData.slice(offset, offset + limit)} />
</div>
);
}
export default App;
//Board.js
const Board = ({dummyData}) => {
return (
<ul>
{dummyData.map(({id, title}) => (
<li key={id}>{id} | {title}</li>
))}
</ul>
)
}
export default Board;
//Pagination.js
import { useState } from 'react';
const Pagination = ({ defaultPage, limit, total, onChange}) => {
const [page, setPage] = useState(defaultPage);
const totalPage = Math.ceil(total / limit);
const handleChangePage = (newPage) => {
onChange(newPage);
setPage(newPage);
}
return (
<div>
<button onClick={() => page !== 0 && handleChangePage(page-1)}>이전</button>
{Array.from({length : totalPage}, (_, i) => i)
.filter((p) => {
if(page < 3) {
return p < 5;
} else if(page > totalPage - 3) {
return p >= totalPage - 5;
}
return p >= page - 2 && p <= page + 2;
})
.map(i => (
<button
key={i}
onClick={() => handleChangePage(i)}
style={{backgroundColor: page === i ? 'red' : ''}}>{i + 1}</button>
))}
<button onClick={() => page + 1 < totalPage && handleChangePage(page+1)}>다음</button>
</div>
)
}
export default Pagination
'데브코스 프론트엔드 5기 > React' 카테고리의 다른 글
231206 [Day57] React(3) (0) | 2023.12.13 |
---|---|
231205 [Day56] React(2) (1) | 2023.12.12 |