ES6 모듈 시스템: import는 ES6(ECMAScript 2015) 표준의 일부로 일부분은 node의 모듈 시스템과 호환이 된다.
import React, { Component } from 'react';
CommonJS 모듈 시스템: require는 CommonJS 표준의 일부로, Node.js에서 사용되는 모듈 시스템이다.
const React = require('react');
const { Component } = require('react');
우선 require에 대해서 살펴보자
const React = require('react');
const WordRelay = () => {
return (
<>
</>
)
}
module.exports = WordRelay;
이러한 코드가 있다고 가정해보자.
여기서는 현재 WordRelay라는 이름으로 모듈화를 해놓았기 떄문에 다른 파일에서 이 파일을 불러 올때
const WordRelay = require('./WordRealy'); 의 방식으로 불러오고 있다.
위의 코드들을 import로 바꿀수가 있다.
import React from 'react';
import WordRelay for './WordRelay';
--> 이처럼 import와 require는 호환이 되는 것을 확인할 수 있다!
어떤 것을 사용해야 할까?
- 최신 프로젝트: 대부분의 최신 React 프로젝트에서는 ES6 모듈 시스템을 사용하므로 import를 사용한다.
- Node.js 환경: 서버 사이드(Node.js) 코드를 작성할 때는 여전히 require를 많이 사용한다.
- 레거시 코드: 기존의 CommonJS 기반 프로젝트에서는 require를 계속 사용할 수 있다.
대부분의 경우, 특히 최신 React 프로젝트에서는 import를 사용하는 것이 좋다. 이는 코드의 가독성을 높이고, 빌드 도구의 최적화 기능을 최대한 활용할 수 있게 해준다.
이로써 import를 앞으로는 사용하도록 한다!!
자바스크립트에서 사용했떤 map을 기억하니?! map을 사용하면 된다.
const numbers = [1, 2, 3, 4, 5];
간단하게 숫자 배열이 있다고 가정해보자
이 배열의 각 요소를 순회하면서 새로운 JSX 요소를 생성하고 싶을 때 map() 메서드를 사용할 수 있습니다.
numbers.map((number) => {
return <li>{number}</li>;
});
위의 코드에서 map() 메서드는 numbers 배열의 각 요소를 순회하며, 각 요소를 <li> 태그로 감싸서 새로운 배열을 생성한다.
이를 숫자 야구에 적용해볼 예정이다.
이런 반복문이 있다고 가정해보자 (아무말 적은 거니 이해해달라)
음식 순서대로 순서 표시, 음식마다 각각 맛이 다르다.
즉, 인덱스, food, taste를 모두 표시를 해주어야 한다.
<ul>
{[
{ food: '사과', taste: '달다' },
{ food: '귤', taste: '시다' },
{ food: '포도', taste: '상큼하다' },
{ food: '어묵', taste: '생선맛이다' },
{ food: '고기', taste: '기름맛이다' },
{ food: '떡볶이', taste: '맛있다' },
].map((item, index) => {
return (
<li key={item.food}>
{index + 1} : {item.food} = {item.taste}
</li>
);
})}
</ul>
이렇게 객체로 food와 taste를 정해두고,
<ul>
{[
['사과', '달다'],
['귤', '시다'],
['포도', '상큼하다'],
['어묵', '생선맛이다'],
['고기', '기름맛이다'],
['떡볶이', '맛있다'],
].map((item, index) => {
return (
<li key={item[0]}>
{index + 1} : {item[0]} = {item[1]}
</li>
);
})}
</ul>
이렇게 배열로도 나타낼 수 있지만, 실무에서는 거의다 객체로 나타낸다고 한다!
map()을 사용할 때 가장 중요하게 생각해야 할 점은 ?
바로바로 key 값이다 !
--> 리액트에서 key는 각 요소를 식별하기 위한 고유한 값입니다. 리스트 아이템이 추가, 수정, 삭제되거나 재정렬될 때 리액트는 각 아이템의 key를 사용하여 효율적으로 변경 사항을 반영한다.
위의 코드에서는 map() 함수로 배열을 순회하면서 각 요소에 대해 <li> 요소를 생성하고 있다. 여기서 key 속성은 각 요소의 food 속성을 사용하여 설정되고 있다. 이렇게 하면 각 음식 항목이 고유하게 식별되게 된다.
예를 들어, '사과' 항목은 '사과'라는 고유한 key를 가지게 되고, '귤' 항목은 '귤'이라는 고유한 key를 가지게 된다. 이러한 key를 통해 리액트는 각 항목을 식별하여 효율적으로 렌더링한다.
food = [
{ food: '사과', taste: '달다' },
{ food: '귤', taste: '시다' },
{ food: '포도', taste: '상큼하다' },
{ food: '어묵', taste: '생선맛이다' },
{ food: '고기', taste: '기름맛이다' },
{ food: '떡볶이', taste: '맛있다' },
];
이렇게 따로 객체를 뺀 다음,

이렇게 this.food.map을 통해서 food를 불러와서 사용하면 반복문 코드 자체는 깔끔해진다.

만약, 이렇게 다양한 컨텐츠를 추가해서 여러 개 반복을 해야 한다면 ?

이런식으로 100개를 작성해야 한다고 한다면, 저 <div>컨텐츠</div>를 작성하는데에는 한계가 있을 것이다
try.jsx 파일을 성성 후, 반복해야 할 부분을 따로 작성하면 된다!

impoert React, { Component } from 'react' :eport default Try : Try 컴포넌트를 다른 파일에서 임포트할 수 있도록 내보낸다. 여기서 가장 중요한 부분은 바로
const { item, index} = this.props 부분이다.

Try class를 불러오면서, 사용할 item과 index의 값을 정의해두고,
Try 컴포넌트에서 this.props를 사용하여 전달받은 데이터를 접근하고 사용할 수 있다.
const { item, index } = this.props 는 this.props 객체에서 item과 index를 추출하는 구문이다.
Baseball -> 부모 컴포넌트에서 Try 컴포넌트를 사용하고, item과 index를 props로 전달하고,
Try -> 자식 컴포넌트에서 this.props를 통해 부모로부터 전달된 props를 받는다.

이런식으로 this.props.item.food 로 불러올 수 있다.
편한 방법대로 쓰면 될 것 같다!
이렇게 빼서 쓰는 점의 좋은 점은 무엇일까 ???
이렇게 간단하지만 귀엽게 만들어 보았다.
여기서 바뀌는 부분을 생각해보면

결과(정답, 실패), 입력값, 시도값(내가 입력한 시도들 출력), 매번 새로운 4개 숫자 뽑기!
이정도 생각할 수 있다.
먼저 class로 만들어보자 !!

class에 state로 변화할 값들을 정의해두고, getNumber()이라는 함수를 만들어서 1~9까지 숫자중에 랜덤으로 4개를 뽑을 수 있도록 했다/

렌더링 (Rendering):
render() 메서드는 React 컴포넌트의 UI를 정의한다. return 문 안에 JSX를 작성하여 화면에 어떻게 표시될지 정의할 수 있다.
폼 제출 (Form Submission):
<form> 태그의 onSubmit 속성에 함수를 연결하면, 폼이 제출될 때 해당 함수가 실행된다. 이 경우 onSubmit={this.onSubmitForm}으로, 폼 제출 시 onSubmitForm 함수가 호출된다.
입력값 변경 (Input Change):
<input> 태그의 onChange 속성에 함수를 연결하면, 입력값이 변경될 때마다 해당 함수가 실행된다.
여기서는 onChange={this.onChangeForm}으로, 입력값이 변경될 때 onChangeForm 함수가 호출된다.
시도 횟수 출력:
<div>시도 : {this.state.tries.length}</div> 부분에서, this.state.tries 배열의 길이를 통해 시도 횟수를 출력한다.
시도 목록 출력:
this.state.tries 배열을 map() 메서드를 사용하여 <Try> 컴포넌트로 렌더링한다. map() 메서드는 배열의 각 요소를 v와 i로 받아 <Try> 컴포넌트를 생성하고, tryInfo라는 prop을 통해 각 시도 정보를 전달한다.
Try 컴포넌트:
Try 컴포넌트는 tryInfo prop을 받아 각 시도를 개별적으로 렌더링한다. key prop은 React에서 각 요소를 고유하게 식별할 수 있도록 도와준다.
그럼 Try 클래스는 어떻게 정의하지?

const { tryInfo } = this.props 를 통해서 tryInfo를 가져온다.
곧 확인해 볼 부분이지만,

이처럼 tries를 try, result로 저장하기 떄문에,
return 값에 tryInfo.try(내가 입력한 값), tryInfo.result(결과)를 출력하는 것이다!
그럼 이제 onSubmitForm, onChangeInput 함수에 대해서 알아보자
onSubmitForm의 함수에서는
1. 성공했을 때,
2. 실패했을 때 - 2.1 10번 이상 실패했을 시, 2.2 10번 미만으로 실패했을 시
이렇게 세 부분으로 나눠서 코드를 작성해 보았다.

성공했을 때
if (this.state.value === this.state.answer.join('')) 조건문을 통해 현재 입력된 값 (this.state.value)이 정답 (this.state.answer.join(''))과 일치하는지 확인 한 후,
(this.state.answer.join('')은 내가 랜덤으로 뽑은 숫자 4개를 문자열로 치환한 값)
state의 상태를 변경한다!
이후, 3초가 지난 후에, alert창을 띄우면서 새로운 게임이 시작되는 것이다 !
10번 이상 틀렸을 때

입력값 처리: 사용자가 입력한 값을 문자열로 변환하여 배열로 만든 뒤, 각 요소를 정수로 변환한 후 다시 문자열로 변환하여 answerArray에 저장한다.
스트라이크와 볼 초기화: strike와 ball 변수를 0으로 초기한다.
시도 횟수 검사: 시도 횟수가 10번을 초과하는지 (this.state.tries.length >= 9) 확인한 뒤,
만약 시도 횟수가 10번을 초과하면, 결과를 표시한 뒤 3초 후에 alert 창을 띄우고 상태를 초기화한다.
10번 미만으로 틀렸을 때

10번 미만의 시도에서 사용자가 입력한 값과 정답을 비교하여 스트라이크와 볼의 개수를 계산하고 상태를 업데이트하는 부분이다.
사용자 입력 값 비교:
answerArray를 for 문을 사용하여 순회하면서 각 자리의 값이 정답(this.state.answer)과 일치하는지 확인한다.
스트라이크와 볼 계산: 자리와 숫자가 모두 일치하면 strike 값을 1 증가시킨다.
숫자는 일치하지만 자리가 틀린 경우 ball 값을 1 증가시킨다.
상태 업데이트: this.state.tries 배열에 현재 시도 값을 추가하고, value를 빈 문자열로 설정한다.
이제 onChangeInput을 살펴보자

여기서 질문 !!
class로 하게 되면, this.state 를 너어어어무 많이 사용하게 된다. 그럼 어떻게 해야할까 ?
이런식으로 분해 구조 할당을 사용하면 된다.
const {result, value, tries} = this.state 로 정의해두면, 사용할 때,
this.state가 이미 result, value, tries에 포함되어 있기 때문에 제외하고 사용이 가능해진다!

useState 훅에서 초기값으로 함수를 전달할 때에는 해당 함수를 호출하는 것이 아니라 함수 자체를 전달해야 한다. 따라서 괄호를 사용하여 함수를 호출하지 않고, 함수 자체를 넘겨야 한다.
예를 들어, useState(getNumber())와 같이 괄호를 사용하면, 컴포넌트가 렌더링될 때마다 getNumber 함수가 호출되어 새로운 값이 계속 생성된다. (하지만 재할당 되진 않는다).
--> 이는 성능 저하를 일으킬 수 있다.
이렇게 함으로써 컴포넌트가 처음 렌더링될 때 한 번만 해당 함수가 호출되고, 그 후에는 해당 함수의 반환값이 초기값으로 사용된다. 지연 초기화(lazy initialization)의 개념이다!
const onSubmitForm = (e) => {
e.preventDefault();
if (value === answer.join('')) {
setResult('홈런!');
setTries((prevTries) => [...prevTries, { try: value, result: '홈런!' }]);
setTimeout(() => {
alert('게임을 다시 시작합니다!');
setResult('');
setValue('');
setTries([]);
setAnswer(getNumber());
}, 3000);
} else {
//10 이상 실패
const answerArray = value.split('').map((v) => parseInt(v));
let strike = 0;
let ball = 0;
if (tries.length >= 9) {
setResult(`실패: 정답은 ${answer.join(',')} 입니다.`);
setTimeout(() => {
alert('게임을 다시 시작합니다!');
setResult('');
setValue('');
setTries([]);
setAnswer(getNumber());
}, 3000);
} else {
for (let i = 0; i < answerArray.length; i++) {
if (answerArray[i] === answer[i]) {
strike += 1;
} else if (answer.includes(answerArray[i])) {
ball += 1;
}
}
setTries((prevTries) => [
...prevTries,
{ try: value, result: `${strike} 스트라이크 ${ball} 볼` },
]);
setValue('');
}
}
};
const onChangeInput = (e) => {
const inputValue = e.target.value;
if (inputValue.length <= 4 && /^\d*$/.test(inputValue)) {
setValue(inputValue);
}
};
return (
<>
<h1>⚾️ Number BaseBall ⚾️</h1>
<div>{result}</div>
<form onSubmit={onSubmitForm}>
<input
type="text"
value={value}
onChange={onChangeInput}
maxLength={4}
/>
<button>입력</button>
</form>
<div>시도 : {tries.length}</div>
<ul>
{tries.map((v, i) => (
<Try key={`try-${i}`} tryInfo={v} />
))}
</ul>
</>
);
나머지 부분은 요로코롬 바꾸면 될 것 같다 !
여기서 주의깊게 생각해야 할 부분 :

갑자기 prevTries는 어디서 튀어나온거지?!
setTries 함수는 이전의 tries 상태를 업데이트하는 데 사용된다. 이전 상태를 업데이트할 때, 이전 상태의 값을 그대로 사용하면서 새로운 값을 추가해야 한다.
하지만 React에서 상태는 불변성을 유지해야 하기 때문에 직접적으로 이전 상태를 수정할 수 없다. 대신, 이전 상태를 복사하고 원하는 변경을 적용한 후 새로운 상태를 설정해야 한다. 이 때, 함수형 업데이트를 사용하면 이전 상태에 안전하게 접근할 수 있다.
prevTries는 이전 상태인 tries 배열을 나타낸다. setTries 함수 내에서 함수형 업데이트를 사용하여 이전 상태를 받아와서 해당 상태를 기반으로 새로운 상태를 설정한다. 이렇게 함으로써 이전 상태를 안전하게 참조하면서 새로운 값을 추가할 수 있습니다.
따라서 setTries((prevTries) => [...prevTries, { try: value, result: '홈런!' }]) ; 코드는 이전 상태인 prevTries 배열을 복사하고, 새로운 객체 { try: value, result: '홈런!' }를 추가한 후 새로운 tries 상태로 설정하는 역할을 한다.
맞다. try 함수도 변경해야 한다!

함수형 컴포넌트에서 훅스를 사용할 때, 컴포넌트의 매개변수로 props를 직접 받아와 사용할 수 있다.
--> const Try = ({ tryInfo }) =>{}
크롬에서 react developer tools를 설치 후 Components 탭에 들어가면 애플리케이션의 현재 React 컴포넌트 트리를 시각적으로 탐색할 수 있다. 각 컴포넌트의 props, state 및 context를 확인할 수 있습니다.

컴포넌트가 렌더링이 될 때에는, state가 변경될 때, props가 변경될 떄, 그리고 부모 props가 렌더링 될 때 이다.
여기서 
1234 0스트라이크 1볼 은 try 즉 자식 컨포넌트 인데, 부모 컨포넌트가 계속 렌더링 됨에 따라서 자식 컨포넌트도 계속 렌더링이 되고 있는 것이다.
이렇게 필요 없는 렌더링이 자주 일어나게 된다면 ? 불필요 하지 않을 까!
이를 막기 위해서 hooks에서는 memo를 사용한다

memo는 함수 컴포넌트의 리렌더링을 최적화하는 데 사용되는 React 훅 중 하나이다.
부모 컴포넌트가 다시 렌더링될 때 자식 컴포넌트도 렌더링되는 상황을 방지하기 위해 memo를 사용하면, 자식 컴포넌트가 props의 변경 여부를 확인하여 해당 props가 변경되지 않았다면 리렌더링을 하지 않는다.

memo 를 사용하게 되면, 이렇게 이름이 변경되기 때문에,
Try.displayName = 'Try' 로 변경시켜야 된다!
그렇다면, class 일 때는 어떻게 사용해야 할까 ?
class 일 때에는 PureComponent를 사용한다.
import React, { PureComponent } from 'react';
class Try extends PureComponent {
render() {
const { tryInfo } = this.props; // props로부터 tryInfo를 받아옴
return (
<li>
<div>{tryInfo.try}</div>
<div>{tryInfo.result}</div>
</li>
);
}
}
export default Try;
이렇게 작성해두면, 부모 컨포넌트만 리렌더링이 되고, 자식 컨포넌트는 부모 컨포넌트가 리렌더링 됬다고 같이 리렌더링 되지 않는다.
state나 props의 변경이 있어야만 리렌더링이 된다.
class --> PureComponent
hooks --> memo
hooks --> useRef 사용하기
class --> createRef 사용하기
useRef는 주로 다음과 같은 상황에서 사용된다:
종합하면, useRef는 React 함수 컴포넌트에서 DOM 요소에 접근하거나 컴포넌트 내부에서 변수를 유지하기 위해 사용되며, 이를 통해 더 유연하고 효율적인 코드를 작성할 수 있습니다.
import React, { useState, useRef } from 'react'; 로 useRef를 불러온 뒤,
const inputRef = useRef() 로 정의해 둔뒤,
사용할 위치에서 inputRef.current.focus() 를 사용하게 되면, input 박스에 focus가 가게 된다!
createRef를 사용하면 DOM 요소를 참조할 수 있는 Ref 객체를 생성할 수 있다. 이 Ref 객체는 해당 DOM 요소를 가리키며, 이를 통해 DOM 요소에 접근하고 조작할 수 있다.
일반적으로 클래스 컴포넌트에서는 createRef를 사용하여 Ref를 생성하고, 함수 컴포넌트에서는 useRef 훅을 사용하여 Ref를 생성한다.
createRef의 주요 특징은 다음과 같다:
import React, { Component, createRef } from 'react'; 를 불러온 뒤,
inputRef = createRef()로 정의해둔 뒤,
사용할 위치에서 inputRef.current.focus() 를 사용하게 되면, input 박스에 focus가 가게 된다

이와 같이 props의 내용을 자식이 변경하면 안된다 부모가 변경해주어야 한다!!

만약, 변경해야 할 상황이 생긴다면,
이렇게 useState 안에 담아준 뒤, 사용해야 한다.
컴포넌트 내부에서 useState 훅을 사용하여 result라는 상태를 정의한다. result 상태는 tryInfo.result 값으로 초기화되며, 상태가 변경될 때마다 setResult 함수를 사용하여 값을 업데이트할 수 있다.
onclick 함수는 result 상태를 변경하는 역할을 한다. 해당 함수는 div 요소에서 클릭 이벤트가 발생할 때 호출되며, 호출되면 setResult를 사용하여 result 상태를 업데이트한다.
프롭스를 스테이트로 바꾼 뒤, 그 스테이트를 변경한다!!