Today I Learned ... react.js
🙋♂️ React.js Lecture
🙋 My Dev Blog
// 🔻 이차원 배열의 경우
return (
<>
<ul>
{[
["사과", "맛있다"],
["바나나", "맛없다"],
["포도", "시다"],
["배", "달다"],
].map(v => {
return (
<li>
<b>{v[0]}</b>
- {v[1]}
</li>
);
})}
</ul>
</>
);
// 🔻 배열 안 객체의 경우 (보통 이렇게 씀)
return (
<>
<ul>
{[
{ fruit: "사과", taste: "맛있다" },
{ fruit: "바나나", taste: "맛없다" },
{ fruit: "포도", taste: "시다" },
{ fruit: "배", taste: "달다" },
].map(v => {
return (
<li>
<b>{v.fruit}</b>
- {v.taste}
</li>
);
})}
</ul>
</>
);
🙋♂️ TIP 1 - key 지정
- map 사용시 key를 반드시 지정해줘야 한다.
- key는 중복되지 않고 고유한 값이여야 한다.
- index를 key로 사용할수도 있지만, 성능최적화시 문제가 발생한다.
//[배열]
.map(v => {
return (
<li key={v.fruit}>
<b>{v.fruit}</b>
{v.taste}
</li>
);
🙋♂️ TIP 2 - arrow function에서 return 생략
- Arrow Function에서는 { } 를 생략하면 return을 생략해도 좋다.
- ()안에 적어준 것을 바로 return 한다.
.map(v => ( <li key={v.fruit}> <b>{v.fruit}</b> {v.taste} </li> ))
위 코드는 제대로 동작하지만, 가독성이 좋지 않다고 생각할 수 있다.
🔻
props를 이용하면 된다!
📝 FruitsInfo.jsx
import React from "react";
import Try from "./Try";
const FruitsInfo = props => {
const fruits = [
{ fruit: "사과", taste: "맛있다" },
{ fruit: "바나나", taste: "맛없다" },
{ fruit: "포도", taste: "시다" },
{ fruit: "배", taste: "달다" },
];
return (
<>
<ul>
{fruits.map((v, i) => (
<Try el={v} index={i} />
))}
</ul>
</>
);
};
export default FruitsInfo;
하위 컴포넌트인 Try에 el
과 index
라는 props를 넘겨주고,
<Try el={v} index={i} />
반복문의 v를 그대로 el
로 넘기고, i는 index
로 넘겨준 것이다.
📝 Try.jsx
import React from "react";
function Try({ el, index}) {
return (
<li key={el.fruit}>
<b>{el.fruit}</b>는 {el.taste} - {index}
<div>컨텐츠1</div>
<div>컨텐츠2</div>
<div>컨텐츠3</div>
<div>컨텐츠4</div>
</li>
);
}
export default Try;
여기서는 props.el과 props.index를 각각 {el, index} 로 받고나서,
el.fruit
와 el.taste
를 필요한 곳에 작성해주면 된다.
🙋♂️ 왜 이렇게 Try 컴포넌트로 따로 분리했는지?
- 가독성 / 재사용성 / 성능 최적화 용이
return (
<li key={this.props.el.fruit}>
<b>{this.props.el.fruit}</b>는 {this.props.el.taste} - {this.props.index}
<div>컨텐츠1</div>
<div>컨텐츠2</div>
<div>컨텐츠3</div>
<div>컨텐츠4</div>
</li>
);
this.props.el
과 같이 작성해야한다.
탑다운 (Top-Down)
큰 컴포넌트(상위 컴포넌트)에서 작성 후,
하위 컴포넌트로 기존의 값을 props로 보내는 등
컴포넌트를 분리하는 것. (위에서 했던 방식)
바텀업 (Bottom-Up)
작은 컴포넌트부터 만들기 시작함.
React가 숙련되면 Bottom-Up 방식으로 제작하게 됨.
🙋♂️ TIP 3 - props가 있다면?
= 부모 컴포넌트가 있다!
props
가 선언되는 순간 컴포넌트끼리 부모-자식 관계가 성립되는 것.- 점점 부모-자식 관계가 확장되면 복잡해진다.
- 이때는
context
,redux
등을 사용한다.
🙋♂️ TIP 4 - React에서 주석(Comment)처리는?
- javaScript에서는
// 주석
이지만,- React 에서는
{/* 주석 */}
으로 작성해야함.- 반드시 중괄호(curlyBrackets)로 감싸줌.
🙋♂️ TIP 5 - Class Component에서 메소드 선언시 Arrow Function으로 작성해야 함!
onInputChange = (e) => { this.setState({ value: e.target.value });}
()=> {}
로 작성해야 this가 바인딩된다.- 화살표함수는 bind(this)를 자동으로 해줌!
- 만약 this.state나 this값들이 필요 없다면,
일반 function으로 적어도 괜찮다.- 🔻
constructor
을 사용하는 방식도 있긴 함.class CompName extends Component { constructor(props) { super(props); this.state = { }; this.onInputChange = this.onInputChange.bind(this); }
- cf> render()안에서는 Arrow Function으로 안써도 된다. (extends React.Component에서 처리해줌.)
이 포스팅에 따로 작성해두었다.
💎 key point
array인 state에 직접
push
하면 안되고,
(원본의 값만 달라진다)
...(spread Operator)
로 새 배열을 선언해야 한다.
- refernce가 달라져서 array가 값이 달라졌음을 React가 알아채도록.
🙋♂️ strike, ball 판단 Logic
✅ 방법 1
// 반복 변수 하나로 OK. for (let i = 0; i < 4; i++) { if (valueArr[i] === answer[i]) { strike++; } else if (answer.includes(valueArr[i])) { // 포함하는지 ball++; }
✅ 방법 2
// 반복 변수(i,j) 두개 필요 for (let i = 0; i < 4; i++) { for (let j = 0; j < 4; j++) { if (valueArr[i] === answer[j]) { if (i === j) { strike++; } else { ball++; } } }
- 내가 작성했던 코드
- 나는 먼저 횟수가 10회미만인지를 검사했었는데,
이는 10회째에 맞춰도 게임오버가 된다.if (tries.length >= 9) { // tries.length가 9면 > 이전 로그가 9개니까 지금이 10회째 시도임! setResult(`게임오버! 정답은 ${answer.join("")} 였습니다.`); setValue(""); setTries([]); setAnswer(getNumbers()); } else { if (value == answer.join("")) { // 정답이면 } else { // 오답이면 - strike, ball
- 수정 코드
- 정답인지 아닌지 먼저 체크해야한다.
if (value === answer.join("")) { // 정답이면 } else { // 오답이면 if (tries.length >= 9) { // 횟수 초과시 - gameover } else { for (let i = 0; i < 4; i++) { // strike, ball
게임오버시
- 10회 초과시 게임오버 - 다시시작 버튼이 나타남
- 정답 맞춰도 게임오버 - 다시시작 버튼이 나타남
- 다시시작 버튼 클릭시
- value, tries, answer, result 전부 리셋
- ❗ 다시 버튼 안보이게
- 버튼은 삼항연산자로 렌더링 여부 결정
import React from "react";
import { useState, useRef } from "react";
function getNumbers() {
const candidate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const answerArr = [];
for (let i = 0; i < 4; i++) {
const chosen = candidate.splice(Math.floor(Math.random() * (9 - i)), 1)[0];
answerArr.push(chosen);
}
return answerArr; // = answer = [2,5,6,1]
}
const BullsAndCows = () => {
const [answer, setAnswer] = useState(getNumbers());
const [value, setValue] = useState("");
const [result, setResult] = useState("");
const [tries, setTries] = useState([]); // push쓰면 원본 바뀌므로 ...로 추가해주기
const [gameover, setGameover] = useState(false);
let inputRef = useRef(null);
const onSubmitForm = e => {
e.preventDefault();
let strike = 0;
let ball = 0;
const valueArr = value.split("").map(v => parseInt(v));
console.log(answer, valueArr);
if (value === answer.join("")) {
setResult("홈런! 정답입니다.");
setTries([...tries, { try: value, result: "홈런! 정답입니다." }]);
setValue("");
setGameover(true);
} else {
if (tries.length >= 9) {
setResult(`게임오버! 정답은 ${answer.join("")} 였습니다.`);
setTries([...tries, { try: value, result: `게임오버!` }]);
setGameover(true);
} else {
for (let i = 0; i < 4; i++) {
if (answer[i] === valueArr[i]) {
strike++;
} else if (answer.includes(valueArr[i])) {
ball++;
}
}
setValue("");
setResult(`${strike}스트라이크 ${ball}볼`);
setTries([
...tries,
{ try: value, result: `${strike}스트라이크 ${ball}볼` },
]);
}
}
};
const onChangeInput = e => {
setValue(e.target.value);
};
const onClickBtn = e => {
alert("게임을 다시 시작합니다!");
setResult("");
setValue("");
setTries([]);
setAnswer(getNumbers());
setGameover(false);
};
return (
<>
<div>
<h1>🎲숫자야구🏏</h1>
<p>남은 횟수: {10 - tries.length}회</p>
<form onSubmit={onSubmitForm}>
<input
required
type="number"
maxLength={4}
value={value}
placeholder="숫자를 맞혀보세요"
onChange={onChangeInput}
ref={inputRef}
/>
<button>입력</button>
</form>
</div>
<div>{result}</div>
<ul>
🔎 로그
{tries.map(el => (
<li key={el.try}>
{el.try} : {el.result}
</li>
))}
</ul>
{gameover ? (
<button type="button" onClick={onClickBtn}>
다시시작
</button>
) : null}
</>
);
};
export default BullsAndCows;
📃 details
// 렌더링 설정 {gameover ? ( <button type="button" onClick={onClickBtn}> 다시시작 </button> ) : null}
// state 설정 const [gameover, setGameover] = useState(false);
if (value === answer.join("")) { // 정답시 - 새게임 setGameover(true); } else { if (tries.length >= 9) { // 횟수 초과시 - 게임오버 - 새게임 setGameover(true);
// 🔻 onClickBtn 함수 const onClickBtn = e => { alert("게임을 다시 시작합니다!"); setResult(""); setValue(""); setTries([]); setAnswer(getNumbers()); setGameover(false); };
🔻 BullsAndCows.jsx
tryInfo
라는 prop으로 보냄.// tries.map 부분
<ul>
🔎 로그
{tries.map((v, index) => (
<Try key={`${index}차시도`} tryInfo={v} />
))}
</ul>
🔻 Try.jsx
props
를 받아와서 사용import React from "react";
const Try = ({ tryInfo, index }) => {
return (
<li key={`${index}차 시도:`}>
{tryInfo.try} : {tryInfo.result}
</li>
);
};
export default Try;
❗ 참고
원래index
를 key로 사용하면 안되지만,
이런 경우에는 사용해도 상관 없음.