리액트를 한번 경험해보고자 했었고 결과적으로 하길 잘했다는 생각이 들었다.
작업시간이 그렇게 오래걸리지 않았고 오히려 새로운 언어 형식이라 흥미로웠다.
추후에 시간이 된다면 Next.js를 한번 다뤄보고싶다.
설계 과정에서 헤맸던 문제
맨바닥에서 시작하려다보니 개념적인 부분에서 머리를 너무 많이 굴렸다.
결과적으로는 컴포넌트를 총 3개를 만들었고 데이터를 공유하기 위해서 게임판에서 모든 데이터를 만들어주었고 하위 컴포넌트인 게임 상태창과 게임 영역에 prop을 던졌다.
다만 초기에는 게임 영역에서의 게임 상태가 갱신이 되면 게임 상태창에는 영향을 주지 않고 싶어서 상위 컴포넌트에서 prop을 받아와 하위 컴포넌트에서 hook을 이용해 시간 경과, 레벨업 등등의 로직을 짜려고 했다.
문제는 여기서부터 발생했다. 게임 영역에서 로직을 짜게 되면 바뀐 정보를 상위에 올려서 반영 시켜야 하는데 값을 게임판에 올리거나 게임판에서 하위컴포넌트의 값이 수정이 된 것을 탐지하는 것이 React에서는 불가능했기 때문이다.
여기서 알아낸 점이 React에서의 모든 데이터의 흐름은 MVC와 다르게 단방향 흐름으로 이루어진다.
항상 데이터는 상위 컴포넌트에서 하위 컴포넌트로 움직이며 하위 컴포넌트에서 받은 prop은 읽기 전용으로만 쓰인다. 하위에서 받아온 prop으로는 상위로 던질 어떠한 작업도 수행하지 못하도록 막고있다. 그래서 상위 컴포넌트가 모든 데이터를 관리할 수 밖에 없다.
결국에 짱구를 굴린 결과로 상위 컴포넌트가 모든 로직을 관리한다는 것은 알게 되었고 상위에서 모든 로직을 다시 짰는데, 게임 상태창의 갱신이 일어나면(가령 시간이 갱신 될 때) 게임 영역도 새롭게 갱신이 일어나고 있었다. 두 컴포넌트는 상위 컴포넌트인 게임판에 속해있는 컴포넌트고 상위 컴포넌트 전체가 rendering이 되기 때문에 게임 영역도 갱신될 수 밖에 없는 구조였다.
초기에 게임 영역 컴포넌트를 만들었을 때 항상 random Color로 rendering하는 코드를 만들어놔서 무조건 새로운 색깔로 변했기 때문이다. 그걸 막기 위해 이전의 rendering된 값을 들고있어야 수정이 되지않고 그대로 유지가 가능했었다.
처음에는 하위 컴포넌트 2개중 1개가 변경될때 다른 1개가 렌더링 되는 것을 막으려고 React.memo, useMemo, useCallback과 같은 렌더링을 제어하는 기술을 사용하려고 했는데 해당 기술은 무조건 '성능 최적화' 용도에만 쓰여야 한다고 해서 React를 구축하는 방식에 대해 몸소 체험하게 되었다.
공부한 것 정리
<Cell onClick={onCorrect}/>
- State 끌어올리기 : 기본적으로 양방향 흐름이 안된다는 것은 알아냈다. 다만 하위 컴포넌트에서 상위 컴포넌트에 값을 전달하는 방법이 있기는 한데, 그건 상위에서 prop으로 던질때 함수 그 자체를 던진다는 뜻이다.
위 그림처럼 상위 컴포넌트의 이벤트인 onCorrect를 던지면 하위 컴포넌트에서 작업을 하더라도 상위 컴포넌트에 데이터가 들어간다는 말이다. 결과적으로 이것은 엄밀히 단방향 흐름을 유지하게 되는 작업이다.
- alert가 두번 뜬다거나 setTimeout이 2초씩 흐르는건 React.StrictMode 때문이다.
- dispatch시에 첫번째인자는 type, 두번째 인자로 변수를 던지면 reducer에서 action에 값을 받아서 쓸 수 있다.
- Styled-Component는 컴포넌트(function) 외부에 만들어줘야함. 안 그러면 콘솔에서 Warning 뜬다. 내부에서 만들면 렌더링이 이루어질 때마다 스타일을 다시 먹여버리는 불상사(메모리 과부하)가 생김.
- reducer의 return 값을 함수화 시키면 가독성이 더 좋아질 뿐만 아니라 각각의 Case안에 선언했던 변수들이 중복되어서 생겨나는 문제도 해결이 된다. ex( reducer내의 CORRECT 내에 선언된 변수와 INCORRECT 내에 선언된 변수가 중복되는 문제 발생 )
- redux는 Reducer와 Flux의 합성어다. reducer는 useState의 확장형이고 Flux는 ContextApi를 말하는 것이다. 둘을 합친 것이 redux이고 또 다른 차이가 있다면 하나는 비동기 하는 동기 방식으로 처리한다고 한다.
- 함수A 안에 함수B를 쓰는 경우는 함수A안의 변수를 B에서 전역변수처럼 사용하고 싶어서이거나 해당 A에서만 사용하는 함수이기 때문이다. Closure에 관한 내용이라 나중에 따로 정리.
- React Hook useEffect has a missing dependency: 'gameStatus'라는 Warning이 발생했는데, 이유는 useEffect 내에서 쓴 변수는 [] 의존성에 추가하라는 뜻이다. 처음엔 gameStatus.time을 적어줬고 time이 변할 때마다 useEffect를 다시 돌리고 싶었는데 결과적으로 gameStatus를 수정한 것이니 gameStatus를 넣어주는게 맞았다.
- app.js 파일은 리액트에서 공통 컴포넌트 불러오는 기능을 한다.
React에서의 스타일링
React에서 스타일링 하는 법은 3가지가 있다.
1. JSX에 lnline으로 넣어주는 방법.
let style={
width:360px,
flex-flow: row wrap; //이 코드가 안먹음
}
return(
<div style={style}></div>
)
일단 가장 비추천하는 방법이고 직접 써봤을 때도 특정 스타일이 위처럼 아예 안써지는 현상도 발생한다. 이 것 말고도 단점이 너무 많아서 안쓰는 방식.. 찾아보니까 flexFlow라고 쓰면 될 수도 있다. background-color도 안돼서 backgroundColor로 쓰라고 한다.
2. module 만들어서 className에 삽입하는 방식
이 또한 단점이 존재해서 대규모 애플리케이션 프로젝트에 경우 CSS 파일에 부피가 커지고, 서투르고, 복잡해지기 시작한다고 함. 이에 대한 솔루션은 SASS(SCSS)를 도입하는 것인데, 이것조차 프로젝트가 가질 수 있는 엄청난 수의 CSS 파일로 인해 그 자체로 복잡할 수도 있다고 함.
3. Styled-component
import styled from 'styled-components';
const Text = styled.div`
color: red,
background: black
`
styled-components를 많이 권장하는 편인 것 같다. 위의 두 방식에서의 단점을 보완한 기술이다. 근데 성능문제로 SCSS를 쓰는 곳도 많다고 한다.
타입스크립트 적용기
타입스크립트가 좋은 것은 알겠는데, 확실히 다른 라이브러리와의 의존성 문제의 복잡도로 인해 불편함이 좀 있다. 이게 내가 아는게 모자라서인지 or 진짜 불편한게 맞는건지는 잘 모르겠다. 후자라면 좀 더 편한 방법이 필요할 듯 하다.
npx create-react-app 프로젝트이름 --template typescript
- 처음엔 프로젝트를 Next.js로 하려고 했는데 Next.js의 SSR 방식으로 인해 CRA StrictMode처럼 rendering을 두번한다고 한다. 그래서 alert이 2번 뜨는 현상이 나타났다. 해결하기 귀찮아서 CRA로 작업을 했다.
Next.js든 CRA든 뒤에 --template typescript를 안붙이고 만들면 필요한 파일들을 타입스크립트를 위해 만들어줘야하는 불편함이 따른다.
npm i styled-components && npm i -D @types/styled-components
- 타입스크립트를 사용하면 CSS module 파일이나 styled-component를 쓸 때 제약사항이 생기는 것 같다. 위처럼 작성을 해줘야 styled-components가 사용 가능하다.
const GAME_FRAME_SIZE:number = 360;
- Number, String과 같이 앞에 대문자를 붙여선 안된다. 기본적으로 대문자로 시작하는건 Interface 이름을 할당할 때 쓰기 위함인 듯 하다.
interface GameStatusInfo{
stage?: number;
time: number;
score: number;
}
function GameStatus({ stage, time, score }:GameStatusInfo) {
return (
<div>스테이지: {stage}, 남은 시간: {time}, 점수: {score}</div>
);
}
- 물음표를 쓰면 데이터를 안 넣어도 된다는 뜻이다.
- 처음에는 { stage:number, time:number, score:number } 이런식으로 하려 했는데 컴파일 에러가 났다. 이유는 객체는 interface 혹은 type으로 만들어서 사용해야한다.
type과 interface중 interface가 확장성 측면에서 더 좋아서 interface 사용을 권장한다고 한다.
- 리액트같은데에선 interface대신 class를 쓴다고 한다. (근데 구글링 해보니까 다 interface던데..)
interface CellInfo{
onClick: React.MouseEventHandler<HTMLDivElement>;
}
- prop으로 onClick을 넘길 때가 있는데 해당 타입은 onClick과 같은 이벤트에 대한 타입은 마우스 대보면 알 수 있다.
'📜 Js' 카테고리의 다른 글
[JavaScript] 변수에 대한 불변성 정리(const, defineProperty) (0) | 2022.12.18 |
---|---|
[JavaScript] 프로토타입 쓰는 이유, 목적 그리고 사용법 (0) | 2022.12.18 |
alert 위치 모를때 위치 찾는 법 (0) | 2022.01.20 |
[ES6] Ajax(Callback, Promise, Async&Await) (0) | 2022.01.04 |
[ES6] 비구조화 할당과 Spread & Rest (0) | 2021.02.10 |