Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
Tags
- Spring-Framework
- 웹 게임을 만들며 배우는 리액트
- 노드에 리액트 추가하기
- 계산맞추기 게임
- Concurrently
- react오류
- 모던자바스크립트
- googleColaboratory
- JS 개념
- props
- intellij
- 모두의 파이썬
- 거북이 대포 게임
- ReactDOM.render is no longer supported in React 18. Use createRoot instead
- 리액트
- spring-boot
- 인프런
- DB Browser
- 자바스크립트
- Python
- Colaboratory 글자 깨짐
- 타자 게임 만들기
- Do it 자바스크립트 + 제이쿼리 입문
- react
- node.js 설치
- intllij 내 Bean을 찾지 못해서 발생하는 오류
- You are importing createRoot from "react-dom" which is not supported. You should instead import it from "react-dom/client"
- node.js로 로그인하기
- vs code 내 node
- 따라하며 배우는 노드 리액트 기본 강의
Archives
- Today
- Total
프로그래밍 삽질 중
[웹 게임을 만들며 배우는 React] - 틱택토, useReducer, useCallback 본문
출처: https://www.inflearn.com/course/web-game-react/dashboard
* useReducer란?
- state가 많아지면 변수들이 많아짐 → 한 번에 모아서 관리하면 어떨까?
▶ 이 기능을 담당하는 것이 useReducer
* (1) state를 모아서 action을 통해 바꿈, (2) action을 dispatch하면 action이 실행됨
▶ (3) reducer에서 정의한대로 state를 바꿈
▶ state를 바꿀 때는 불변성이 항상 중요함
//(1) state를 하나로 모아둠
const initialState = {
winner: "",
turn: "O",
tableData: [
["", "", ""],
["", "", ""],
["", "", ""],
],
recentCell: [-1, -1],
};
//(2) action을 통해서만 바꿈
dispatch({ type: SET_WINNER, winner: "O" });
dispatch({ type: SET_WINNER, winner: turn });
dispatch({ type: RESET_GAME });
//(3) reducer에서 정의한대로 state를 바꿈
const reducer = (state, action) => {
switch (action.type) {
case SET_WINNER:
//state.winner = action.winner -> 이렇게 직접 바꾸면 안 됨
return {
...state,
winner: action.winner, //직접 바꾸는 것이 아닌 바뀌는 부분만 바꿈
};
case CLICK_CELL: {
//바꾸고자 하는 부분만 바꿈
//객체는 얕은 복사
const tableData = [...state.tableData]; //얕은 복사
tableData[action.row] = [...tableData[action.row]];
tableData[action.row][action.cell] = state.turn;
return {
...state,
tableData,
recentCell: [action.row, action.cell],
};
}
case CHANGE_TURN: {
return {
...state,
turn: state.turn === "O" ? "X" : "O",
};
}
case RESET_GAME: {
return {
...state,
turn: "O",
tableData: [
["", "", ""],
["", "", ""],
["", "", ""],
],
recentCell: [-1, -1],
};
}
default:
return state;
}
};
* useCallback
▶ 왜 사용? = 함수 onClickTd를 props로 넣을 때마다 불필요한 렌더링이 발생 → 함수를 기억할 필요 있음
▶ 아래의 예시는 cellData가 바뀔때마다 함수 초기화함
▶ 처음 데이터를 감지하고 바뀌는 데이터를 감지를 못하기 때문에 바뀔 여지가 있는 데이터(cellData)를 []에 넣음
import React, { useCallback } from "react";
import { CLICK_CELL } from "./T3";
const Td = ({ rowIndex, cellIndex, dispatch, cellData }) => {
const onClickTd = useCallback(() => {
console.log(rowIndex, cellIndex);
if (cellData) {
//한 번이상 못 누르게 하기
return;
}
dispatch({ type: CLICK_CELL, row: rowIndex, cell: cellIndex });
}, [cellData]);
return <td onClick={onClickTd}>{cellData}</td>;
};
export default Td;
/* {
console.log(rowIndex, cellIndex);
if (cellData) {
//한 번이상 못 누르게 하기
return;
}
dispatch({ type: CLICK_CELL, row: rowIndex, cell: cellIndex });
}
이 부분 기억함
*/
[Td.jsx]
▶ 칸만 눌렀는데 칸 전체가 렌더링됨(문제 있음)
▶ 렌더링이 무엇이 됬는지 파악(useEffect, useRef 사용)
▶ props 자체의 문제는 아님
▶ memo로 해결(memo가 되지 않으면 useMemo(컴포넌트 자체를 기억)도 고려) = 클릭하는 일부만 리렌더링됨
import React, { useCallback, useEffect, useRef, memo } from "react";
import { CLICK_CELL } from "./T3";
const Td = memo(({ rowIndex, cellIndex, dispatch, cellData }) => {
const ref = useRef([]);
useEffect(() => {
console.log(
rowIndex === ref.current[0],
cellIndex === ref.current[1],
dispatch === ref.current[2],
cellData === ref.current[3]
); //cellData가 바뀐 것으로 나옴
console.log("cellData", cellData, ref.current[3]);
ref.current = [rowIndex, cellIndex, dispatch, cellData];
}, [rowIndex, cellIndex, dispatch, cellData]); //모든 props 다 넣기
const onClickTd = useCallback(() => {
console.log("td rendered");
console.log(rowIndex, cellIndex);
if (cellData) {
//한 번이상 못 누르게 하기
return;
}
dispatch({ type: CLICK_CELL, row: rowIndex, cell: cellIndex });
}, [cellData]);
return <td onClick={onClickTd}>{cellData}</td>;
});
export default Td;
[Tr.jsx]
▶ 렌더링이 무엇이 됬는지 파악(useEffect, useRef 사용)
▶ props 자체의 문제는 아님
▶ useMemo 사용 : 값을 기억, 컴포넌트를 기억하는 것도 가능
▶ 셀을 갱신 : rowData[i]가 바뀌었을 때
import React, { useRef, useEffect, memo } from "react";
import Td from "./Td";
const Tr = memo(({ rowData, rowIndex, dispatch }) => {
console.log("tr rendered");
const ref = useRef([]);
useEffect(() => {
console.log(
rowData === ref.current[0],
rowIndex === ref.current[1],
dispatch === ref.current[2]
);
ref.current = [rowData, rowIndex, dispatch];
}, [rowData, rowIndex, dispatch]); //모든 props 다 넣기
return (
<tr>
{Array(rowData.length)
.fill()
.map((td, i) => (
<Td
key={i}
cellIndex={i}
rowIndex={rowIndex}
cellData={rowData[i]}
dispatch={dispatch}
>
{""}
</Td>
))}
</tr>
);
});
export default Tr;
[Table.jsx]
import React, { memo } from "react";
import Tr from "./Tr";
const Table = memo(({ tableData, dispatch }) => {
return (
//i가 몇 번째 줄인지 나타냄
<table>
{Array(tableData.length)
.fill()
.map((tr, i) => (
<Tr key={i} dispatch={dispatch} rowIndex={i} rowData={tableData[i]} />
))}
</table>
);
});
export default Table;
[T3.jsx]
import React, { useReducer, useCallback, useEffect } from "react";
import Table from "./Table";
const initialState = {
//state를 하나로 모아둠
winner: "",
turn: "O",
tableData: [
["", "", ""],
["", "", ""],
["", "", ""],
],
recentCell: [-1, -1],
};
export const SET_WINNER = "SET_WINNER";
export const CLICK_CELL = "CLICK_CELL"; //td에서도 사용
export const CHANGE_TURN = "CHANGE_TURN";
export const RESET_GAME = "RESET_GAME";
const reducer = (state, action) => {
switch (action.type) {
case SET_WINNER:
//state.winner = action.winner -> 이렇게 직접 바꾸면 안 됨
return {
...state,
winner: action.winner, //직접 바꾸는 것이 아닌 바뀌는 부분만 바꿈
};
case CLICK_CELL: {
//바꾸고자 하는 부분만 바꿈
//객체는 얕은 복사
const tableData = [...state.tableData]; //얕은 복사
tableData[action.row] = [...tableData[action.row]];
tableData[action.row][action.cell] = state.turn;
return {
...state,
tableData,
recentCell: [action.row, action.cell],
};
}
case CHANGE_TURN: {
return {
...state,
turn: state.turn === "O" ? "X" : "O",
};
}
case RESET_GAME: {
return {
...state,
turn: "O",
tableData: [
["", "", ""],
["", "", ""],
["", "", ""],
],
recentCell: [-1, -1],
};
}
default:
return state;
}
};
const T3 = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const { tableData, turn, winner, recentCell } = state;
const onClickTable = useCallback(() => {
dispatch({ type: SET_WINNER, winner: "O" }); //액션을 통해 바꿈
//액션만 있다고 자동으로 state가 바뀌는 것이 아님
//reducer = 액션을 해석해서 state를 직접 바꿈
}, []);
//recentCell 바뀔때마다
useEffect(() => {
const [row, cell] = recentCell;
if (row < 0) {
return;
}
let win = false;
if (
//가로줄 검사
tableData[row][0] === turn &&
tableData[row][1] === turn &&
tableData[row][2] === turn
) {
win = true;
}
if (
//세로줄 검사
tableData[0][cell] === turn &&
tableData[1][cell] === turn &&
tableData[2][cell] === turn
) {
win = true;
}
if (
//대각선 검사(\)
tableData[0][0] === turn &&
tableData[1][1] === turn &&
tableData[2][2] === turn
) {
win = true;
}
if (
//대각선 검사(/)
tableData[0][2] === turn &&
tableData[1][1] === turn &&
tableData[2][0] === turn
) {
win = true;
}
console.log(win, row, cell, tableData, turn);
if (win) {
//승리
dispatch({ type: SET_WINNER, winner: turn });
dispatch({ type: RESET_GAME });
} else {
//무승부 검사
let all = true;
tableData.forEach((row) => {
row.forEach((cell) => {
if (!cell) {
all = false;
}
});
});
if (all) {
dispatch({ type: RESET_GAME });
} else {
dispatch({ type: CHANGE_TURN });
}
}
}, [recentCell]);
return (
<>
<Table onClick={onClickTable} tableData={tableData} dispatch={dispatch} />
{winner && <div>{winner}님의 승리</div>}
</>
);
};
export default T3;
'과거 프로그래밍 자료들 > React' 카테고리의 다른 글
인라인 스타일링 시 리렌더링 문제(styled-components를 사용하는 이유) (1) | 2022.10.04 |
---|---|
[웹 게임을 만들며 배우는 React] - 지뢰찾기(1), contextAPI (0) | 2022.09.30 |
[웹 게임을 만들며 배우는 React] - 로또추첨기, useEffect, useCallback (1) | 2022.09.29 |
[웹 게임을 만들며 배우는 React] - 가위바위보, 커스텀 훅 (0) | 2022.09.29 |
[웹 게임을 만들며 배우는 React] - 가위바위보, 라이프사이클 (0) | 2022.09.28 |