React 강의 듣고 나만의 사이트 만들기 시작!! 

먼저 초반부 Front 작업을 진행한다.

쪼랩이라 라이브러리 다운받을때 빼먹는건 진행하면서 차차 고치도록 해야겠다.

친절하게 코드가 다 들어있지않은 이유는 스스로 공부하는 목적이기때문에 사소한 부분이 많이 빠져있다.

 

1.

내 사이트명의 폴더를 생성한다.

OKAYOON

 

2. 

Front 와 Back 따로 진행해야하기에 폴더를 두개 생성한다.

OKAYOON

-front

-back

// front

npm init
npm i next@9 react react-dom 
npm i prop-types
npm i eslint eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks -d

npm i -D babel-eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-react-hooks eslint-plugin-jsx-a11y

npm i antd styled-components @ant-design/icons

npm init 해준 뒤 install해주자

내가 본 제로초님 강의에서는 next9버전을 사용했다.

next@9의 @는 특정 버전을 지정하여 install 할 수 있다.

eslint를 사용하지 않는다면 install 할 필요없다.

-D는 개발모드에서만 사용할 모듈들이다.

antd를 통해 스타일 작업을 진행하였다. (bootstrap과 같이 웹, 앱 디자인 개발을 위한 프레임워크이다.)

 

index.js

pages 폴더 예시

 

pages 폴더 하위에 파일을 생성하면 next가 자동으로 인식해준다.

라우터 필요없이 주소/login 식으로 접근 가능하다.

next를 통해 localhost로 확인하기위해서는 package.json의 script 부분 수정이 필요하다.

(물론 명령어를 쳐도된다.)

 

package.json

package.json 예시

아래와 같이 하면 포트를 바꿀 수 있다.

// package.json

"scripts": {
	"dev": "next -p 3060"
},

그 후 cmd에서 run 시켜준다.

npm run dev

 

작업을 하다보면 콘솔에 에러가 뜨는것을 확인할 수 있다.

className 콘솔창 예시

babel를 통해 해결해주자.

관련 플러그인을 install 해준다.

npm i babel-plugin-styled-components

설정도 빼먹지말자

 

/front 하위로 .babelrc 파일을 생성하여 아래 내용을 작성한다.

{
    "presets": ["next/babel"],
    "plugins": [
        ["styled-components", {
            "ssr": true,
            "displayName": true
        }]
    ]
}

ssr: 서버사이드 렌더링

displayName: 렌더링이 된 후 보호된 네이밍들을 컴포넌트 이름으로 변경한다.

 

 

redux, saga를 통해 state관리 및 통신작업을 할 것이다.

npm i redux next-redux-wrapper react-redux redux-devtools-extension
npm i redux-saga next-redux-saga axios immer

npm i -d redux-devtools-extension

 

front 하위에 store 폴더를 생성한 뒤 파일을 하나 만들자.

네이밍은 configureStore.js인데, 알아서하자.

내용은 아래와 같다.

import { createWrapper } from 'next-redux-wrapper';
import { applyMiddleware, createStore, compose } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import createSagaMiddleware from 'redux-saga';

import reducer from '../reducers';
import rootSaga from '../sagas';


const loggerMiddleware = ({ dispatch, getState }) => (next) => (action) => {
  console.log(action);
  return next(action);
};

const configureStore = () => {
  const sagaMiddleware = createSagaMiddleware();
  const middlewares = [sagaMiddleware, loggerMiddleware];
  const enhancer = process.env.NODE_ENV === 'production'
    ? compose(applyMiddleware(...middlewares))
    : composeWithDevTools(applyMiddleware(...middlewares));
  const store = createStore(reducer, enhancer);
  store.sagaTask = sagaMiddleware.run(rootSaga);
  return store;
};


const wrapper = createWrapper(configureStore, {
  debug: process.env.NODE_ENV === 'development',
});


export default wrapper;

이렇게 미들웨어를 만든 다음 

_app.js 파일에서 사용해주자.

_app.js 예시

페이지에 _app.js나 _document.js등과 같이 Next.js에 내장되어 있는 _document.js, _app.js, _error.js를 커스터마이징하여 레이아웃을 새롭게 구성하는 방법이 있다.

그냥 pages 폴더 하위에 _app.js라는 네이밍으로 파일을 생성하면된다.

(next에서 제공하는 것으로 특정 컴포넌트 렌더링 시 Layout을 적용 제외하기 위한 방법이라고 한다.)

 

여튼 저기에다가 미들웨어 만들어 둔 것을 wrapper.withRedux로 감싸줘야 프로젝트의 모든 컴포넌트와 페이지에 적용된다고 한다. 

Next.js에서 Redux 사용하기에 대해 읽어보자 --> velog.io/@jehjong/Next.js%EC%97%90%EC%84%9C-Redux%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-Redux-wrapper

 

Next.js에서 Redux사용하기 (Redux wrapper) (1/2)

컴포넌트에서 공통적으로 쓰이는 데이타가 흩어져있기 때문에 부모 컴포넌트에서 데이타를 받아서 자식 컴포넌트에게 각각 보내줘야한다컴포넌트끼리 데이타를 전달하는 과정도 매우 복잡하

velog.io

 

 

드디어 리액트 기초강의끝!!

이젠 typescript를 보려고하는데 보는 매일 안쓰다보니 ㅠ 글이 엉망이다.

강의 볼때마다 글 쓰도록 해봐야지...

 

강의 유튜브 주소 :

https://www.youtube.com/watch?v=V3QsSrldHqI&list=PLcqDmjxt30RtqbStQqk-eYMK8N-1SYIFn



리액트 라우터

라우터를 쓰기위해서 모듈을 설치해준다.

필요한 것은 react-router와 react-router-dom이다.

npm i react-router react-router-dom

 

react-router는 웹, 앱 모두 사용할 수 있다고 한다.

react-router-dom이 웹브라우저에서 사용하기 위해 필요한것들이 있다고 한다.

react-router만 설치해도 종속성에 의해 react-router-dom이 설치된다고 한다.

 

staticRouter와 hashRouter, browserRouter가 있다고 한다.

import React from 'react';
import { BrowerRouter, hashRouter, staticRouter }  from 'react-touter-dom';

제일많이쓰는건 browserRouter.

 

import React from 'react';
import { BrowerRouter }  from 'react-touter-dom';

const Test () => {
    retern (
        <BrowserRouter>
            //이렇게 감싸줘야한다.
        </BrowserRouter>
    )
};

혹은 ReactDOM으로 render감싸주는 곳에서 컴포넌트를 감싸줘도 된다고 한다.

// app.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';

ReactDOM.render(<BrowerRouter><App/></BrowserRouter, document.querySelector('#root'));

 

라우터는 페이지로 만들고 싶은 것들의 경로를 임의로 만들어 작업해준다.

가상의 페이지주소이기때문에 사용자가 주소로 접속할 경우 에러가 난다.

import React from 'react';
import { BrowerRouter, Route }  from 'react-touter-dom';
import TestChild from './TestChild';

const Test () => {
    retern (
        <BrowserRouter>
            <Route path="./경로" component={TestChild}></Route>
        </BrowserRouter>
    )
};

 

가상의 주소로 넘어갈 수 있도록 앵커 태그들이 필요한데, a태그를 사용할 경우에는 에러가 난다.

리액트 라우터는 페이지가 존재하는게 아니라 페이지가 이동하는 것처럼 흉내내는 것이기 때문이다.

따라서 브라우저에서 제공하는 a태그를 사용할 경우 라우터가 인식하지 못한다.

리액트 라우터에서 제공하는 <Link>태그를 사용해준다.

// (X)
retern (
        <BrowserRouter>        	
            <a href="주소"></a>
            <Route path="./경로" component={TestChild}></Route>
        </BrowserRouter>
)
import React from 'react';
import { BrowerRouter, Route, Link }  from 'react-touter-dom';
import TestChild from './TestChild';

const Test () => {
    retern (
        <BrowserRouter>
            <Link to="주소">라우터보기</Link>
            <Route path="./경로" component={TestChild}></Route>
        </BrowserRouter>
    )
};

 

<Link>태그는 페이지의 이동이 아니라 보여지게 흉내내는 것이다.

라우터에게 보여져야할 주소값을 전달한다.

브라우저상에서는 Link가 a태그로 표현된다.

이때 Link태그를 클릭하여 주소를 이동한 후에 새로고침을 하게되면 에러가 발생한다.

또한 주소창에 주소를 입력하고 접근해도 에러가 발생한다.

이것은 주소창으로 접근시에(새로고침도 포함) 서버에 요청이 가기 때문인데, 가상의 주소는 프론트에서만 알고 있기 때문에 서버에서는 에러를 반환한다.

이 부분은 실제 페이지가 여러개가 아니기때문이다.

 

hashRouter

browserRouter와 거의 비슷하지만 주소사이에 hash값이 들어가게 된다.

[ localhost:8080/#/주소 ] 이런형식으로 보여진다.

import React from 'react';
import { HashRouter, Route, Link }  from 'react-touter-dom';
import TestChild from './TestChild';

const Test () => {
    retern (
        <HashRouter>
            <Link to="주소">라우터보기</Link>
            <Route path="./경로" component={TestChild}></Route>
        </HashRouter>
    )
};

 

hashRouter의 장점은 주소를 이동한 후에 새로고침을 해도 에러가 나지 않고 화면이 나타난다.

이유는 해쉬값(#)때문이다.

browserRouter는 주소가 깔끔한 대신에 새로고침시 서버에 요청이 들어가게되는데 hashRouter는 해쉬값때문에 서버에 요청이 가지않는다. 단점은 서버가 주소의 이동을 모르는 것이다(페이지 존재유무)

이렇게 되면 SEO에 불이익을 받게되고 검색엔진에 접근이 안되게 되면.... 사이트 검색이 안될수도 있다.

이것은 실무에서 엄청난 불이익이다.

따라서 보통 실무에서는 해쉬라우터를 사용안하고 브라우저라우터를 사용한다.

 

관리자페이지나 SEO에 전혀 상관이없는 사이트같은경우에는 해쉬라우터를 사용하기도 한다.

해쉬라우터는 배포시에 편하다고 한다. 새로고침때 브라우저만 동작하기때문에 어떤 브라우저든 잘 동작한다고 한다.

 

브라우저 라우터를 쓰면 검색엔진에는 검색이 되나 새로고침이나 주소값 접근의 문제를 해결해야한다.

이것은 서버에 추가적인 세팅으로 해결할 수 있다.

페이지가 존재하는 것을 서버에 알려줘야한다고한다.

서버쪽 세팅시 검색엔진도 신경써야한다.

 

라우터가 100개가 되고 200개가 되고 방대해질 경우에는 동적 라우터매칭을 사용한다.

:name 값은 파라미터로 동적으로 변경가능하다.

import React from 'react';
import { HashRouter, Route, Link }  from 'react-touter-dom';
import GameMatcher from './GameMatcher';
import TestChild from './TestChild';

const Test () => {
    retern (
        <HashRouter>    
            <Link to="/game/number">게임1</Link>
            <Link to="/game/lottory">게임2</Link>
            <Link to="/game/paper">게임3</Link>

            <Link to="/game/index">게임 매처</Link>
            <Route path="/game/:name" component={GameMatcher}></Route>
        </HashRouter>
    )
};

동적 라우터를 사용해서 라우터 하나로 여러개를 처리한다.

this.props.history.match는 라우터 to에 담았던 경로값이 포함되어있다.

 

history, location, match가 undefined가 된다면 라우터 컴포넌트에 cameMatcher를 넣어준다.

라우터랑 연결이 안되어있는 라우터의 경우 withRouter를 사용해서 hoc형식으로 감싸주게되면 사용할 수 있다.

import React, {component} from 'react';
import {withRouter} from 'react-router-dom';

class GameMatcher extends Component{
    render(){
        return(
            <div>
            </div>
        );
    }
}

export default withRouter(GameMatcher);

 

location, match, history

history에는 앞으로가기, 뒤로가기 등의 페이지 이동에 대한 이력이 남아있다.

제공하는 메서드들이 있어서 go나 go back등 (앞으로가기)의 기능을 제공할 수 있다.

그래서 history를 통해 눈속임으로 페이지의 이동을 구현할 수 있다.

이때 브라우저가 제공하는 기본 메서드를 사용하면 안된다.(라우터는 브라우저 기본기능을 통해 이 기능을 구현하고 있다)

 

match는 path가 실제 라우트에 등록한 값을 알 수 있다.

:name 파라미터에 실제 어떠한 값이 들어왔는지 알 수 있다.

이 값은 match의 params에 들어가있다.

 

location은 주소에 대한 정보나 서치, 해쉬값등이 들어있다.

라우터에서 import하던것들을 똑같이 import해준다.

그후에 분기처리한다.

import React, {component} from 'react';
import {withRouter} from 'react-router-dom';

import Number from './number';
import RSP from './RSP';
import Lotto from './Lotto'

class GameMatcher extends Component{
    render(){
        if(this.props.match.params.name === 'number'){
            return <Number />    
        }else if(this.props.match.params.name === 'RSP'){
            return <RSP />
        }else if(this.props.match.params.name === 'Lotto'){
            return <Lotto />
        }else{
            return <div>일치하는 게임이 없다</div>;
        }
    }
}

export default withRouter(GameMatcher);

 

길어지는것은 어쩔수없다고 하며 어느 부분이던 길어지는 부분이 존재하기 때문에 선택사항이라고 한다.

history.pushAPI를 사용할 경우 주소를 변경할 수 있다.

3번째 인자값으로 주소값을 전달한다.

history.push('', '', '변경할 주소값');

 

this.props.history와 history.push는 다르며 브라우저의 historyAPI를 사용하면 안된다.

리액트라우터에서 제공하는 것을 사용해야 에러가 나지 않는다.

import React from 'react';
import { HashRouter, Route, Link }  from 'react-touter-dom';
import GameMatcher from './GameMatcher';
import TestChild from './TestChild';

const Test () => {
    retern (
        <HashRouter>    
            <Link to="/game/number?query=10&data=1">게임1</Link>
            <Link to="/game/lottory">게임2</Link>
            <Link to="/game/paper">게임3</Link>

            <Link to="/game/index">게임 매처</Link>
            <Route path="/game/:name" component={GameMatcher}></Route>
        </HashRouter>
    )
};

to 뒤의 값에 쿼리스트링값을 넘길 수 있는데 

?키=값&키=값 형식으로 작성할 수 있으며  &를 통해 연장할 수 있다.

쿼리스트링값은 서버에서도 확인할 수 있다.

이 쿼리스트링은 location의 search에서 확인할 수 있다.

import React, {component} from 'react';
import {withRouter} from 'react-router-dom';

import Number from './number';
import RSP from './RSP';
import Lotto from './Lotto'

class GameMatcher extends Component{
    render(){

        // slice해서 물음표를 제거한뒤 URLSearchParams에 담아줘야하다.
        let urlSearchParams = new URLSearchParams(this.props.location.search.slice(1));
    
        if(this.props.match.params.name === 'number'){
            return <Number />    
        }else if(this.props.match.params.name === 'RSP'){
            return <RSP />
        }else if(this.props.match.params.name === 'Lotto'){
            return <Lotto />
        }else{
            return <div>일치하는 게임이 없다</div>;
        }
    }
}

export default withRouter(GameMatcher);

쿼리스트링을 사용하려면 URLSearchParams가 필요하다.

slice해서 물음표를 제거한뒤 URLSeatchParams는 빈객체가 아니며 URLSearchParams에 담아줘야하다.

보통 쿼리스트링은 게시판 작업시에 쓰인다.

 

해쉬라우터에는 뒤에 #abc=1234 식으로 붙일 수 있는데, 이건 서버는 모르고 브라우저만 안다.

실제적으로 많이 쓰이지 않는다.

서버가 모르면 쿼리스트링을 딱히 쓸때가 별로 없기때문이다.

 

함수 컴포넌트

props자리에 들어있다. 클래스컴포넌트는 this.props로 접근한다.

다만 라우터에 연결되어있지 않다면 withRouter안에 넣어줘야 사용할 수 있다,

const Games = ({ match, location, history }) => {
	// ....
}

export default withRouter(Games);

 

props를 넘길 수도 있다.

부모컴포넌트의 props를 자식컴포넌트에 넘기는게 목적이라면 render를 쓰는게 좋다.

<Route path="/game/:name" component={() => <GameMatcher props="1234" />} ></Route>
<Route path="/game/:name" render={(props) => <GameMatcher props={props}>} ></Route>

실제로 작업을 하다보면 동적 라우터가 있어도 주소고정라우터가 필요할 경우가 있다.

이때 하나만 렌더링 시키고 싶다면 switch를 사용한다.

<Switch>
    <Route path="/game/:name" render={(props) => <GameMatcher {...props} />} ></Route>
    <Route path="/game/Number" render={(props) => <GameMatcher {...props}/>} ></Route>
</Switch>

switch안에 있을경우 여러개가 렌더링이 되지 않으며 일치하는 하나만 렌더링이된다.

exact는 정확하게 일치하는 경우에만 렌더링을 시키는 경우인데 switch와 함께 쓸수도 있다.

아래와 같은 경우에 쓰는데 주소가 겹치게되는 경우에 사용된다.

만약 exact가 '/'값에 없다면 '/game/Number/' 접근하게 될 경우 switch로 인해 하나만 렌더링이 되어야하기때문에 '/'로 ㄷ등록된 컴포넌트가 렌더링이되는 현상을 겪을 수 있다. 

<Switch>
    <Route exact path="/" render={(props) => <GameMatcher {...props} />} ></Route>
    <Route path="/game/Number" render={(props) => <GameMatcher {...props}/>} ></Route>
</Switch>

강의 유튜브 주소 :

https://www.youtube.com/watch?v=V3QsSrldHqI&list=PLcqDmjxt30RtqbStQqk-eYMK8N-1SYIFn



성능최적화

구글확장플러그인 devtools로 확인하여 테스트한 후에 최적화가 필요하면 진행한다.

최적화작업은 작업의 마지막에 한다.

 

useEffect와 useRef로 재렌더링이 되는 이유를 디버깅할 수 있는 방법이 있다.

ref를 만들어서 props나 state들을 넣고 비교하면서 검사하면된다,

import React, {useCallback, useEffect, useRef}from 'react';

const Test = () => {
    const Ref = uesRef([]);

    useEffect(() =>{
    
    	// 콘솔에 찍어보자
        console.log(a === ref.current[0], a === ref.current[1]);
        ref.cuffrent = [a, b, c, d];
        
    // a, b, c, d가 변경될때 useEffect가 실행
    },[ a, b, c, d ]);

}

 

React.memo는 컴포넌트를 기억할 수 있다.

컴포넌트자체를 기억시킬수 있다.

return(
    <>
        useMemo(() => <Test />, [ data[i] ]);
    </>
);

 

근데 이 전에 memo를 먼저 진행해보고 안되면 useMemo를 쓴다.

import React, {memo} from 'react'l

const Test = memo(() => {
    // .... 
});

자식 컴포넌트부터 진행하여 위로 올라오면서 확인하는것이 더 쉽다.

useMemo는 memo를 쓰고도 재렌더링이 될때 최후의 수단으로 쓸 수 있다.(이건 추후에 다시 공부)

 

contextAPI

useReducer는 redux에서 차용했으며 state가 여러개일때 하나로 관리하고 dispatch의 action을 통해 state를 변경할 수 있따. redux와의 차이점은 동기이나 비동기이냐의 차이이다.

react와 useReducer는 비동기이며 redux는 동기이다.

 

부모컴포넌트가 자식컴포넌트에게 props를 전달할때, 다중 관계를 해결하기 위해 contextAPI를 사용한다.

return(
    <>
        <Text dispatch={dispatch}/>
    </>   
)

이렇게 dispatch를 props로 내려주는 부분을 contextAPI를 통해 바로 접근할 수 있도록 해준다.

 

createContext Provider

contextAPI에 접근하고 싶은 컴포넌트들을 provider에 감싸주면된다.

import React, { createContext } from 'react';

const TestContext = creatContext({
    // 여기에 기본 값을 넣을 수 있다.
});
import React, { createContext } from 'react';

const TableContext= creatContext({
    // 여기에 초기값을 넣어야한다.
    tableData : [],
    dispatch : () => {}
    // 초기값에 의미가 없어서 데이터형태만 넣어줬다.
});

const Test = () => {
    return (
        <TextContext.Provider>
            <Test />
        </TextContext.Provider>
    );
};

 

데이터는 value에 넣어준다.

value는 자식컴포넌트에서 바로 접근할 데이터들이며 가져올때에는 useContext를 사용한다.

return (
        <TextContext.Provider value={{ tableData : state.tableData, dispatch:dispatch }}>
            <Test />
        </TextContext.Provider>
);
// 자식
import React, { useContext } from 'react';
import { TableContext } from './createContext한 컴포넌트';

const Test = () => {    
    const value = useContext(TableContext);

    // tableContext는 createContext한 것이고 
}

// 부모
export const TableContext= creatContext({
    // 여기에 초기값을 넣어야한다.
    tableData : [],
    dispatch : () => {}
    // 초기값에 의미가 없어서 데이터형태만 넣어줬다.
});

이렇게 해두면 value.dispatch로 접근할 수 있다.

const Test = () => {    
    const value = useContext(TableContext);
    
	// 구조분해해도된다
    const { dispatch } = useContext(TableContext);

 

contextAPI가 성능최적화에서 가장 힘들다.

아래처럼 쓰면 컴포넌트가 재렌더링이 될때마다 value 객체가 새로 재렌더링이 되는데, 이럴경우 성능에 문제가 생길수 있어서 캐싱을 해줘야한다.

useMemo를 사용해준다.

참고로 dispatch는 항상 안바뀌고 유지가되므로 2번째 인자로 전달하지 않아도 된다.

return (
        <TextContext.Provider value={{ tableData : state.tableData, dispatch:dispatch }}>
            <Test />
        </TextContext.Provider>
);
import React, { useMemo } from 'react';

conts Test = () => {
    const value = useMemo (() => {
        tableData : state.tableData, dispatch:dispatch
    },[ state.tableData ]);

    return (
        <TextContext.Provider value={value}>
            <Test />
        </TextContext.Provider>
    );
};
dispatch({ action을 생성하면된다 });

 

contextAPI를 쓰려면 value가 다 바뀌게된다.(재렌더링)따라서 value를 useMemo로 감싸준다.

memo를 쓰려면 하위컴포넌트 모두에게 적용해야한다.

const value = useMemo (() => {
        tableData : state.tableData, dispatch:dispatch
},[ state.tableData ]);

 

강의 유튜브 주소 :

https://www.youtube.com/watch?v=V3QsSrldHqI&list=PLcqDmjxt30RtqbStQqk-eYMK8N-1SYIFn



state나 props가 늘어나면 관리가 힘들어진다.

그리고 props를 넘기게될때 자식에 자식에 자식의 컴포넌트가 있다면 복잡해진다.

이떄 useReducer를 써서 관리할 수 있다.

 

useReducer를 배우면 redux랑 비슷한 효과를 낼 수 있다.

contextAPI + useReducer를 쓸 경우 redux를 사용안해도 되지않나?라고 하는경우들이 있는데

대체하기는 어렵다고 한다.

거창한 redux가 필요없을 경우 (작은 프로젝트)에는 대체하는 형식으로 쓸수는 있다고 한다.

왜냐면 contextAPI와 useReducer를 쓰게되면 비동기 부분을 처리할때 결국 redux를 써야하는 경우가 온다고한다.

즉 흉내라고보면 편하다.

 

useReducer 첫번째인자는 reducer, 두번째인자는 initialState, 세번째는 지연초기화라고 하는데,

지연초기화는 거의안쓰며 가끔 복잡한 경우에 사용한다고한다.

//초기 state선언
const initialState = {    
	winner : '',    
    turn : 'o',        
    tableData : []
}

// 이건 함수, 두개의 인자를 받는다.
const reducer = (state, action) => {    

	// 여기서 state를 어떻게 바꿀지 적는다    
    // 액션이 실행되면 여기서 state를 바꾸어줘야한다.    
    switch(action.type){        
    	case 'SET_WINNER' :             
        	return {                
            	...state,                 
                winner : action.winner 
                
                // dispatch에서 실행되는 액션에 있는 값..            
          }    
    }
}
        
const Test = () => {    
	const [state, dispatch] = useReducer(reduer, initialState);    
        
    // 컴포넌트에 넣는 함수에는 무조건 useCallback    
    const onCLickTable = useCallback(()=>{        
        
       // 액션객체는 {} , dispathch가 실행되면 액션이 실행되었다고 한다        
       dispatch({ type : 'SET_WINNER', winner : 'O' });    
    }, []);    
        
    return(            
       <>                
            
       		// state에서 initialState를 선언했기 떄문에 jsx에서 접근이 가능하다.                
            {state.winner}            
       </>    
    )
};

 

onClick이 발생하면 winner를 o로 변경하는데 이때 dispatch 시킨다.

dispatch안에 들어가는 것은 action이라고 말한다.

action이라는 것은 redux에서 따온 말이라고 한다.

즉 dispatch 함수를 작성한것은 == 액션을 만들었다고 한다.

그리고 실행되면 액션이 실행되었다고 말한다.

 

action만 있다고 자동으로 state가 변경되는 것은 아니다.

action을 읽어서 state로 변경해주는 역할이 필요하다.

그리고 그것이 바로 reducer다.

 

action을 dispatch할때마다 reducer가 실행된다.

 

그리고 state는 initialState로 선언해줬는데, 이 state는 직접 변경하면안된다.

그리고 불변성을 위해 새로운 객체를 늘 넣어줘야한다.

const reducer = (state, action) => {
    switch(action.type){
        case 'SET_WINNER' :
            // 이렇게하면 안됨
            state.winner = action.winner 
    }
}

이렇게 action을 이용해 state를 바뀌는 것을 그림으로 표현해줬다.(출처는!! 제로초님 그림임, 캡쳐해왔습니다.)

그림을 설명해보자면..

일단 state(initialState)가 있고 이벤트가 발생하게되면(click과 같은것들) state를 직접수정하면 안된다.

state를 수정하기 위해서는 action을 만들고 dispatch를 통해 action을 실행한다.

action으로 state를 수정할 수 있다.

action을 어떻게 처리하는가? action의 관리는 reducer가 한다.

 

이때 action의 이름값은 변수로 저장하는 것이 좋다.

강제는 아니지만 action의 이름은 대문자가 좋다. 커뮤니티의 규칙이라고한다.

상수로 선언하고 export를 통해 다른 컴포넌트에서도 사용하는 것이 오류를 줄여준다.

const SET_WINNER = 'SET_WINNER';

const reducer = (state, action) => {
    switch(action.type){
        case SET_WINNER :
         return {
                ...state,
                winner : action.winner // dispatch에서 실행되는 액션에 있는 값..
            }
    }
}

 

action을 만들었으면 자식컴포넌트에서 사용할 수 있도록 export해준다.

// 부모
export const SET_WINNER = 'SET_WINNER';
export const CLICK_CELL = 'CLICK_CELL';

const reducer = (state, action) => {
    switch(action.type){
        case SET_WINNER :
         return {
                ...state,
                winner : action.winner // dispatch에서 실행되는 액션에 있는 값..
            };

    			// 불변성을 지키면서 작업해야한다. 이게 단점. 나중에 immer라는 라이브러리로 가독성 문제를 해결할 수 있다. 유료강좌에서 확인해보아라~~~
        case CLICK_CELL : 
          const data = [...state.data];
          data[data.row] = [...data[data.row]];
                    
          return {
              ...state
          }
    }
}

// 자식
import React, { useCallback } from 'react';
import { CLICK_CELL } from '부모컴포넌트';

const TEST = () => {
    const onClick = useCallback(() => {
        dispatch({
            type : CLICK_CELL, dataIndex : dateIndex
        });
    });

    //...
};

react에서 state를 변경하는 것과 dispatch에서 state를 변경하는 것은 비동기이며 redux는 동기이다.

비동기 state를 처리할 때에는 useEffect를 쓴다.

 

기본적으로 useState로 여러개만들게되면 변수가 많아지고 관리가 어려워진다.

리액트가 redux의 개념으로 state와 dispatch를 관리하기 위해서 useReducer를 도입했다.

initialState에 모든 state를 모아두고 reducer가 action을 통해 setState의 작업을 한다.

 

반복으로 props를 넘겨줘야하는 경우에 contextAPI를 사용한다.

contextAPI는 바로 원하는 컴포넌트로 데이터를 전송할 수 있다.

+ Recent posts