Vue도 재밌었지만 실무에서 많이쓰는건 결국 리액트다!
리액트는 반응형 프레임워크임. 헷갈리면 엑셀을 떠올리자 ㅎㅎ
처음엔 컴포넌트만 열심히 생각해보자. 구조, 아키텍처, 아름다운 코드는 잠깐 잊고 UI를 구현해보자!
리액트로 애플리케이션 만들때 가장 빠른방법 => webpack등의 번들링 설정을 모두 대신해준다. 마치 vite같구먼?
리액트는 JSX를 기본 문법으로 채택함.
//원래 이렇게 작성되는데..
React.createElement('div', {options...})
//마치 html처럼 작성하면 파싱해서 위처럼바꿔준다.
<div></div/>
공통점을 찾는 것이 재사용의 시작. 제약 없이 계속 추상화하면 곤란.
리액트 컴포넌트는 결국 함수다! 간단하게 설계하고 코딩하다가 리팩토링하자
import logo from "./logo.svg";
import PropTypes from "prop-types";
function Logo({ size }) {
return (
<img
src={logo}
className="App-logo"
alt="logo"
style={{ width: size, height: size }}
/>
);
}
Logo.defaultProps = {
size: "100px",
};
Logo.propTypes = {
size: PropTypes.string,
};
export default Logo;
기본 타입을 지정해줄수도있고 타입을 정해줄수도 있음. 하지만 TS처럼 강제력은 없다...그냥 브라우저 콘솔창에 오류가 남ㅎㅎ
특수한 props임. 예약어다. Vue의 slot기능과 유사하다.
//Paragraph.js
function Paragraph({ children, size }) {
return <p style={{ fontSize: size }}>{children}</p>;
}
export default Paragraph;
//App.js
...
<Paragraph>
Edit <code>src/App.js</code> and save to reload.
</Paragraph>
이렇게 컴포넌트 사이에 들어온 데이터를 children 프롭스로 받아온다!
참고로 children의 타입은 node
다
vue
에서는 분기처리를 v-if, v-show
로 행했다.
리액트에서는 삼항연산자, 논리연산자를 사용하면 된다.
const [visible, setVisible] = useState(false);
return (
<div className="App">
<button onClick={() => setVisible(!visible)}>토글</button>
{visible && <h1>조건부 렌더링</h1>}
</div>
반복문도 vue와 유사하다
<ul>
{articles.map((article) => (
<li key={article.id}>
{article.title} | {article.author}
</li>
))}
</ul>
리액트도 마찬가지로 key
프로퍼티를 넣어주어야한다. 리스트에 변경사항이 일어났을때 최적화를 도와준다.
아마 객체처럼 키-밸류로 저장하니 그런거겠지?
정확히는 부모가 setState함수를 자식에게 전달하여, 자식이 업데이트된 값을 부모의 상태로 전달하는 것이다.
//App.js
const [totalCount, setTotalCount] = useState(0);
return (
<div>
{`총합 : ${totalCount}`}
<Counter onChange={setTotalCount} />
<Counter onChange={setTotalCount} />
<Counter onChange={setTotalCount} />
<Counter onChange={setTotalCount} />
</div>
);
//Counter.js
const [count, setCount] = useState(0);
const increase = () => {
setCount(count + 1);
if (onChange) {
onChange(count + 1);
}
};
const decrease = () => {
setCount(count - 1);
if (onChange) {
onChange(count - 1);
}
};
return (
<div style={{ fontSize: "50px" }}>
{count}
<button onClick={increase}>+</button>
<button onClick={decrease}>-</button>
</div>
$emit을 사용하지 않으니까 더 간결한것 같기도 하고 ㅎㅎ
대신 함수를 전달해줘야하는 점이 다르다.
훅이라는 함수가 리액트에는 내장되어있음. 가령 useState
처럼... 그런 훅들을 파헤쳐보자 ㅎㅎ
useEffect(setup, dependencies?)
setup
은 컴포넌트가 마운트될때 일어나는 함수
dependencies
는 배열인데, 의존값이 변경되면 useEffect
가 재호출됨!
나중에 자세히 다뤄본다!
useState
는 state
값이 변경되면 다시 렌더링함.
하지만 useRef
는 가져온 값이 변경되도 리렌더링이 일어나지 않는다.
const inputRef = useRef();
return (
<div>
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</div>
이렇게 DOM에 접근할때 사용한다.
또한 컴포넌트 내부의 ref
를 가져올수도 있다.
//App.js
const inputRef = useRef();
return (
<div>
<Input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</div>
);
//Input.js
const Input = React.forwardRef((_, ref) => {
return (
<>
Input: <input ref={ref} />
</>
);
});
그런데 여기서 의문이 생김. ref
를 React.forwardRef
라는 키워드 없이 프롭스로 전달하면 안되는걸까?
const Input = ({ refs }) => {
return (
<>
Input: <input ref={refs} />
</>
);
};
ref
는 예약어라 refs
프롭으로 전달해주었다. 이렇게하니 된다. 왜 React.forwardRef
를 쓰는거지..??
라우팅인줄 알았는데, 콘텐츠를 페이지별로 나눠놓는것이다(저번 과제에서 영화 목록 받아올때 페이지별로 받아왔다).
//App.js
const [page, setPage] = useState(0);
const articles = new Array(100)
.fill()
.map((_, i) => ({ id: i, title: `${i}번 게시물` }));
const limit = 10;
const offset = page * limit;
return (
<div>
<Pagination
defaultPage={0}
limit={limit}
total={articles.length}
onChange={setPage}
/>
<Board articles={articles.slice(offset, offset + limit)} />
</div>
);
//Board.js
const Board = ({ articles }) => {
return (
<ul>
{articles.map((article) => (
<li key={article.id}>
{article.id} | {article.title}
</li>
))}
</ul>
);
};
//Pagination.js
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(new Array(totalPage), (_, i) => i).map((i) => (
<button
key={i}
style={{ backgroundColor: page === i ? "red" : undefined }}
onClick={() => handleChangePage(i)}
>
{i + 1}
</button>
))}
<button
onClick={() => page + 1 !== totalPage && handleChangePage(page + 1)}
>
다음
</button>
</div>
);
};
서버에서 내려준 건 아니고 한번에 내려준 값을 페이지네이션한 것이다. 보통 서버측에서 내려주긴하는데 클라이언트측에서도 하는 일이 있으니 알아두면 좋다 ㅎㅎ