프로젝트가 끝나고 간만에 수업 코드를 저장한 리포에서 git pull을 받았다. 그리고 간만에 에러를 만났다.
Found a swap file by the name
요며칠 git push/pull 할 때마다 UNIX 화면으로 바뀌던 게 이때문인 것 같다.
Q
를 누르면 vim을 빠져나온다는데 나는 :q
를 추가로 입력해야 했다.ls -al
을 입력해 .swp 파일을 찾았는데, 남아있는 게 없었다.d
를 눌러 지울 수 있는 듯하다) 오늘은 리액트 첫 수업이었다. 이전에 리액트 강의를 짧게 수강한 적이 있는데 실습 위주였던지라 이론을 한번 짚고 넘어가는 게 좋았다.
오직 부모 컴포넌트에서 자식 컴포넌트로만 전달 가능하다. 자식이 부모로 보낸다거나 자식 간의 전달 또한 불가하다.
ref. angular js는 양방향 데이터 바인딩
리액트는 한 페이지를 만들 때 여러 Component를 부품처럼 조립해서 만든다. 컴포넌트는 쉽게 말해 공통 부분을 묶은 일종의 함수라고 볼 수 있다. 기능/UI 단위로 캡슐화해서 재사용성 높고, 일괄 수정도 용이하다.
동작 원리의 핵심 중 하나가 아닐까 싶다. 기존 DOM을 복제한 가상 돔을 만들어 저장할 때마다 이전과 달라진 부분만 새로 렌더링한다. 변화가 생길 부분만 렌더링을 하기 때문에 자연히 반응 속도가 빠르다.
Javascript + XML이지만, XML이 HTML과 거의 흡사하다.
Babel
리액트의 JS 코드를 HTML로 변환 & 최신 JS 문법을 이전 버전의 JS 문법으로 변환.
Webpack
코드 변경할 때마다 바로 반영해준다. 여러 파일과 모듈을 하나의 파일로 합쳐주기 때문에 리액트 프로젝트 배포에도 쓰인다.
function FuncComponent() {
const
return (
<>
<div>첫번째 상자</div>
<div>두번째 상자</div>
</>
}
const name = 'une';
return <>
{name}입니다.
</>
{ 3+4 === 7 ? '정답' : '땡'}
JSX에서는 for문이나 if문을 직접 사용할 수 없다. 대신 JSX가 시작되기 전, 즉 return 이전에 결과값을 변수에 저장해 가져다{}
쓸 수 있다.
function FruitButton () {
const fruit = () => {
if(name==='peach') {
console.log('yummy');
}
else if (name==='lemon') {
console.log('Oops);
}
else {
console.log('what is this?");
}
}
return
<>
<button onClick={fruit}>과일 버튼</button>
</>
}
논리곱은 전자가 true일 때 다음으로 넘어간다. 첫 조건이 false였다면 논리곱 이후의 두번째 조건은 아예 읽지 않는다. 이점을 이용해 true 일 때만 결과를 보이게 코드를 짤 수 있다.
{ 4+5 === 9 && '정답은 9' }
조건이 거짓일 때 예외처리가 필요 없는 경우 간단하게 논리곱을 사용해도 되겠다는 걸 처음 배웠다. 처음이 true일 때만 다음 조건을 읽는다는 건 알았는데 이런 식의 응용은 생각 못해봐서 흥미로웠다!
HTML에서 style은
<div style="font-size: red; background-color: black;"></div>
이런 식으로 따옴표 안에 나열한다. 하지만 JSX는 중괄호 안 중괄호로 처리한다. 첫번째 중괄호는 자바스크립트 코드를 사용하기 때문(2번 참조)이고, 내부에 덧댄 중괄호는 객체임을 나타낸다.
<div style={{fontSize: '20px' color: 'red'}}></div>
또한 camelCase로 작성하기 때문에 하이픈은 사용하지 않는다.
JSX에서 class와 onclick은 예약어라서 사용이 불가하다. 그래서 className
, onClick
이라 적는다.
또, html에서는 함수를 "호출"하는 부분이 필수적이었다. 선언만 하면 실행하지 않는다. 하지만 리액트에서는 함수를 "선언"하는 것으로 충분하다. 파일을 저장할 때마다 자동으로 리렌더링하기 때문에 함수를 호출하면 이벤트 발생하기 이전에 바로 실행된다. 마치 addEventListener
에서 함수를 선언만 하고 실행하지 않는 것과 같은 이유다.
이미지는 보통 public 폴더 안에 담는다. 슬래시로 경로를 시작하거나 이미지 자체를 import할 수도 있다.
리액트는 컴포넌트의 집합이다. 그러니까, 한 페이지를 전체가 아닌 부분으로 나누어 필요할 때마다 가져다 쓴다. 우리가 반복되는 코드를 묶어 함수로 만들듯이 말이다. 페이지 자체를 컴포넌트로 만들기도 하고, 그렇게 만든 페이지 컴포넌트 내부 요소에 또 컴포넌트를 만들기도 한다.
card 형식이 대개 그렇다.
물론 29CM가 어떤 라이브러리로 개발하는지는 모르지만.. 여기서도 리액트 관점으로 컴포넌트를 짚어낼 수 있다. 내용만 바뀌지 구성과 크기는 모두 동일하다.
비슷한 뭉치를 상황에 따라 다르게 보여주려고 할 때에 새삼 리액트가 유용하겠거니 싶다. 직접 DOM 조작을 하지 않아도 알아서 변화가 있을 때마다 렌더링을 해주니 말이다.
부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때 대개 props를 이용한다고 했다. 함수형 컴포넌트를 생성한다면 매개변수에 props를 담는다. 개발자도구로 크기를 참고해가며 작성해봤다.
// App.js
import './App.css';
import ProductCard from './components/My29cm';
function App() {
return (
<div>
<ProductCard
brandName="유라고"
productName="[7차 리오더] Jacquard hoodie cardigan_2colors"
productPoster="/kuromi.jpg"
index={0}
Ranking=""
originalPrice={115000}
discountRate={10}
currentPrice={103500}
productLike={33931}
productComment={88}
/>
</div>
);
}
export default App;
// components/ProductCard.js
function ProductCard(props) {
return (
<section className="product-card">
<div className="product-card__img-body">
<div className="product-card__img">
<img src={props.productPoster} alt="{props.index} img" />
</div>
<div className="product-card__ranking">
<span>{props.ranking}</span>
</div>
</div>
<div className="product-card__info">
<span className="product-card__brand-name">
{props.brandName}
</span>
<span className="product-card__product-name">
{props.productName}
</span>
<span className="product-card__original-price">
{props.originalPrice}
</span>
<span className="product-card__discount-rate">
{props.discountRate}%
</span>
<span className="product-card__current-price">
{props.currentPrice}원
</span>
</div>
<div className="product-card__icon-body">
<div className="product-card__like-body">
<i className="heart"></i>
<span>{props.productLike}</span>
</div>
<div className="product-card__comment-body">
<i className="message"></i>
<span>{props.productComment}</span>
</div>
</div>
</section>
);
}
export default ProductCard;
컴포넌트별 CSS 파일이 아니라서 BEM 네이밍 규칙을 생각하여 클래스명을 작성했는데 컴포넌트는 대문자로 시작한다는 점을 까먹었다.
/* App.css */
.product-card {
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 0 40px 20px;
}
.product-card__img-body {
position: relative;
}
.product-card__img {
width: 250px;
height: 250px;
}
.product-card__ranking {
width: 40px;
height: 40px;
background-color: red;
color: whitesmoke;
position: absolute;
bottom: 0px;
left: 0px;
font-weight: 600;
}
.product-card__info {
width: 250px;
display: flex;
flex-direction: column;
}
.product-card__brand-name {
text-decoration: underline;
margin-bottom: 7px;
font-size: 13px;
}
.product-card__product-name {
font-size: 12px;
height: 32px;
padding-right: 25px;
word-break: break-all;
}
.product-card__original-price {
opacity: 0.2;
font-size: 12px;
font-family: 'Lucida Sans', sans-serif;
}
.product-card__discount-rate,
.product-card__current-price {
color: tomato;
font-weight: 700;
font-size: 14px;
font-family: 'Lucida Grande', sans-serif;
}
.product-card__icon-body {
display: flex;
}
.product-card__like-body,
.product-card__comment-body {
font-size: 11px;
opacity: 0.9;
}
.product-card__like-body {
margin-right: 50px;
}
이렇게 작성한 결과물.
처음에 이미지 파일이 안 떴다.
src="{props.~~}"
로 받아옴.JSX 문법은 변수와 따옴표가 공존하기 때문에 이 점을 주의해야겠다.
받는 쪽이 아니라 넘기는 쪽(부모 컴포넌트)에서 함수를 전달해야 한다. 간략하게 주고받는 부분만 예시로 작성해보면 이렇다.
<ProductCard function={() => {console.log('this is function')}} />
<button onClick={props.function}>함수 props</button>