zustand를 이용해서 틱택토를 간단하게 만들어봤습니다
import {create} from "zustand";
export const useTurnStore = create((set) => ({
isTurn: false,
setIsTurn: (isTurn) => set({isTurn})
}))
전역변수로 Turn을 생성해주고
그리드를 이용해서 틱택토 화면을 그려줍니다
'use client'
import Square from "../../component/square";
import {useState} from "react";
export default function DocsPage() {
return (
<div className="p-5">
<div className="grid grid-cols-3 grid-rows-3 w-20">
<Square/>
<Square/>
<Square/>
<Square/>
<Square/>
<Square/>
<Square/>
<Square/>
<Square/>
</div>
</div>
)
}
전역변수인 turn을 불러와서
지역변수로 mark를 선언해서 택택토를 재현했습니다
import {useState} from "react";
import {useTurnStore} from "../app/hooks/turn.store";
export default function Square() {
const turn = useTurnStore();
const [mark, setMark] = useState("ㅁ")
const handleClick = () => {
console.log(turn.isTurn)
if (mark === "ㅁ"){
if (turn.isTurn === false){
setMark("X")
turn.setIsTurn(true)
}else{
setMark("O")
turn.setIsTurn(false)
}
}
}
return (
<div>
<button onClick={handleClick}>{mark}</button>
</div>
)
}
default 가 무엇인지
export default 에서 export는 다른 파일에서 참조가 가능하단걸 알았지만 default는 정확히 알지 못했는데
다른 파일에서 해당파일을 참조할때 이 파일의 주요기능임을 알려주는걸 알았습니다
state에 배열로 9개의 크기로 Null을 넣기
const [squares, setSquares] = useState(Array(9).fill(null))
상태관리 변화와 참조
const [squares, setSquares] = useState(Array(9).fill(null))
function handleClick() {
// 1
const newSquares = [...squares];
newSquares[0] = 'X';
setSquares(newSquares);
// 2
setSquares(squares => {
squares[0] = 'X';
return squares;
});
}
1번과 2번을 볼때 둘다 배열의 0번째를 변화하려고하는 행동이지만 2번째 경우는 잘못된 경우입니다
이유는
불변성(Immutability)의 개념이 중요하게 작용합니다
React는 새로운 객체 또는 배열이 이전 객체 또는 배열과 다른지(즉, 참조가 변경되었는지)를 확인하여 변화를 감지합니다.
1번같은 경우는 새로운 배열로 참조가 변경이 되어서 컴포넌트의 변경을 감지할 수 있지만
2번같은 경우는 참조가 변경되지않고 안의 값만 변경되었기에 컴포넌트의 변경을 감지할 수 없습니다
랜더링
기본적으로 모든 하위 구성 요소는 상위 구성 요소의 상태가 변경되면 자동으로 다시 렌더링됩니다. 여기에는 변경의 영향을 받지 않은 하위 구성 요소도 포함됩니다. 재렌더링 자체가 사용자에게 눈에 띄지는 않지만(적극적으로 피하려고 하면 안 됩니다!), 성능상의 이유로 분명히 영향을 받지 않은 트리 부분을 재렌더링하는 것을 건너뛰고 싶을 수도 있습니다. 불변성은 구성 요소가 데이터 변경 여부를 비교하는 것을 매우 저렴하게 만듭니다. API memo참조 에서 React가 구성 요소를 다시 렌더링할 시기를 선택하는 방법에 대해 자세히 알아볼 수 있습니다 .
-gpt4
특정 데이터를 가지고 react 구상해보기
[
{ category: "Fruits", price: "$1", stocked: true, name: "Apple" },
{ category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit" },
{ category: "Fruits", price: "$2", stocked: false, name: "Passionfruit" },
{ category: "Vegetables", price: "$2", stocked: true, name: "Spinach" },
{ category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin" },
{ category: "Vegetables", price: "$1", stocked: true, name: "Peas" }
]
다음데이터를 참고해서 어떻게 화면에 뿌려줄것인지 생각해봅니다
다음과 같은 화면을 뿌려준다고 했을때 생각해야할것
1단계: UI를 구성 요소 계층 구조로 나누기
2단계: React에서 정적 버전 빌드
3단계: UI 상태에 대한 최소한이지만 완전한 표현 찾기
4단계: 주가 어디에 거주해야 하는지 확인하세요.
5단계: 역방향 데이터 흐름 추가
https://react.dev/learn/thinking-in-react
다음 링크를 참고해서 개발에 유의할것
상태관리 할것을 찾기
원래 제품 목록은 props로 전달되므로 상태가 아닙니다.
검색 텍스트는 시간이 지남에 따라 변하고 어떤 것에서도 계산할 수 없기 때문에 상태인 것 같습니다.
확인란의 값은 시간이 지남에 따라 변경되고 어떤 것에서도 계산할 수 없기 때문에 상태인 것 같습니다.
필터링된 제품 목록은 원래 제품 목록을 가져와 검색 텍스트와 확인란 값에 따라 필터링하여 계산할 수 있으므로 상태가 아닙니다 .
이는 검색 텍스트와 체크박스의 값만 상태임을 의미합니다
Hook
주로 사용하는 훅들
useState: 가장 기본적인 훅으로, 컴포넌트의 로컬 상태를 관리할 때 사용됩니다. 상태를 생성하고, 그 상태를 변경할 수 있는 함수를 반환합니다.
useEffect: 컴포넌트가 렌더링된 후에 어떤 작업을 수행해야 할 때 사용합니다. 주로 데이터를 불러오는 비동기 작업, 이벤트 리스너 등을 처리할 때 사용됩니다.
useRef: 참조를 생성하고, 그 참조를 관리할 때 사용됩니다. 주로 DOM 요소에 접근하거나, React 컴포넌트의 라이프사이클 동안 값이 유지되어야 하는 변수를 관리할 때 사용됩니다.
useMemo: 복잡한 연산의 결과를 메모이제이션(재사용)하는 데 사용됩니다. 이 훅을 사용하면 연산 결과를 재활용하여 컴포넌트의 성능을 최적화할 수 있습니다.
useCallback: 함수를 메모이제이션하는 데 사용됩니다. 이 훅을 사용하면 함수를 재활용하여 컴포넌트의 성능을 최적화할 수 있습니다. 주로 이벤트 핸들러 함수 등을 메모이제이션할 때 사용됩니다.
useContext: React의 Context API를 사용하여 상위 컴포넌트에서 내려주는 값을 컴포넌트 트리의 깊은 곳에서 바로 사용할 수 있게 합니다.
useReducer: 복잡한 상태 로직을 관리할 때 유용하며, useState의 대체제로 사용할 수 있습니다. 현재의 상태와 그 상태를 업데이트하기 위한 액션을 받아 새로운 상태를 반환하는 함수를 사용합니다.
useEffect
useEffect는 최상위 수준이나 자체 Hook에서만 호출할 수 있습니다.
if나 for문에서 실행할 수 없습니다
// 다음코드에서 3가지의 상황으로 두번째 인자가 어떤 상황에서 랜더링되는지 확인해봅니다
'use client'
import {useEffect, useState} from "react";
export default function Page(){
const [test, setTest] = useState("")
const [test2, setTest2] = useState("")
// 1
useEffect(() => {
console.log("useEffect")
setTest2("hello")
console.log("setTest2")
},[test1])
// 2
useEffect(() => {
console.log("useEffect")
setTest2("hello")
console.log("setTest2")
},[])
// 3
useEffect(() => {
console.log("useEffect")
setTest2("hello")
console.log("setTest2")
},)
const handleClick = () => {
console.log("button click")
setTest("test")
console.log('setTest')
}
return(
<div className="p-10">
<button onClick={handleClick}>test</button>
</div>
)
}
1번 test1번값이 변경되면 실행됩니다
2번 컴포넌트가 처음 렌더링 될때만 실행되고, 그 이후에는 실행되지 않습니다
3번 컴포넌트가 리렌더링 될 때마다 실행됩니다 즉 상태 변경, 프롭스 변경이 발생할때마다 실행됩니다.