반응형

notion link

https://silver-blue-23c.notion.site/React18-0d46d803f3be437dbe7f90348dbecd09

 

React18

React 18의 알파 버전이 출시

* 바로 막 쓰기엔 무리일듯

자동 배치(Automatic Batching)가 도입되어 배치가 개선

 

배치 : React가 더 나은 성능을 위해 여러 개의 state 업데이트를 하나의 리렌더링 (re-render)로 묶는 것을 의미)

React 18의 createRoot를 통해, 모든 업데이트들은 어디서 왔는가와 무관하게 자동으로 배칭되게 된다.

 

이 뜻은, 추가적으로 timeout, promise, native 이벤트 핸들러와 모든 여타 이벤트는 React에서 제공하는 이벤트와 동일하게 state 업데이트를 배칭할 수 있다. 이를 통해 우리는 렌더링을 최소화하고, 나아가 애플리케이션에서 더 나은 성능을 기대한다.

상태 업데이트에 배치가 적용되지 않았으면 하는 경우엔 새롭게 추가된ReactDOM.flushSync함수를 사용

 

useTransition

React 17 까지는 상태 업데이트를 긴급 혹은 전환으로 명시하는 방법 X

 

 모든 상태 업데이트는 긴급 업데이트 → setTimeout이나 throttle, debounce 등의 테크닉을 써서 긴급 업데이트 방해를 우회하는 것이 최선이였다

 

useTransition을 쓰면

일부 상태 업데이트를 긴급하지 않은 것 (not urgent)로 표시

이것으로 표시되지 않은 상태 업데이트는 긴급한 것으로 간주

긴급한 상태 업데이트 (input text 등)가 긴급하지 않은 상태 업데이트 (검색 결과 목록 렌더링)을 중단할 수 있다.

 

쓰는 이유 : 느린 렌더링 & 느린 네트워크

업데이트를 긴급한 것과 긴급하지 않은 것으로 나누어 개발자에게 렌더링 성능을 튜닝하는데 많은 자유를 주었다고 볼 수 있다.

 

useId

 

useId는 클라이언트와 서버간의 hydration의 mismatch를 피하면서 유니크 아이디를 생성할 수 있는 새로운 훅이다. 이는 주로 고유한 id가 필요한 접근성 API와 사용되는 컴포넌트에 유용할 것으로 기대

 

useDeferredValue

 

useDeferredValue를 사용하면, 트리에서 급하지 않은 부분의 재렌더링을 지연

지연된 렌더링을 인터럽트하고 사용자 입력을 차단할때 사용

이는 debounce와 비슷하지만, 몇가지 더 장점이 있다.

지연된 렌더링은 인터럽트가 가능하며, 사용자 입력을 차단하지 않는다.

 

***** 아래는 Library author들에게 권장하는 hook들*****

(아직 어디에 쓰는진 잘..? library 제작자한테 추천한다고 한다 특히 useSyncExternalStore)

 

useSyncExternalStore

 

스토어에 대한 업데이트를 강제로 동기화 하여 외부 스토어가 concurrent read를 지원할 수 있도록 하는 새로운 훅

**useEffect**가 필요하지 않고, 이는 리액트 외부 상태와 통합되는 모든 라이브러리에 권장된다.

 

 

용어 설명 :

  • External Store: 외부 스토어라는 것은 우리가 subscribe하는 무언가를 의미한다. 예를 들어 리덕스 스토어, 글로벌 변수, dom 상태 등이 될 수 있다.
  • Internal Storeprops context useState useReducer 등 리액트가 관리하는 상태를 의미한다.
  • Tearing: 시각적인 비일치를 의미한다. 예를 들어, 하나의 상태에 대해 UI가 여러 상태로 보여지고 있는, (= 각 컴포넌트 별로 업데이트 속도가 달라서 발생하는) UI가 찢어진 상태를 의미한다.

**useExternalStore**는 리액트 18에서 스토어 내 데이터를 올바르게 가져올 수 있도록 도와준다.

 

useInsertionEffect

 

css-in-js 라이브러리가 렌더링 도중에 스타일을 삽입할 때 성능 문제를 해결할 수 있는 새로운 훅

 

The signature is identical to useEffect, but it fires synchronously before all DOM mutations.

 

리액트가 DOM을 변환한경우, 레이아웃에서 무언가를 읽기전 (**clientWidth**와 같이) 또는 페인트를 위해 브라우저에 값을 전달하기 전에 DOM에 대한 다른 변경과 동일한 타이밍에 작업을 하면 된다.

 

→ We must ensure to manipulate the CSS rules at the same time as other changes to the DOM. It can be when React mutates the DOM, before anything is read from the layout and before the content is visible in the browser.

useLayoutEffect 과 비슷,

hook is used to read the layout from the DOM and synchronously re-render.

 

순서 UseInsertionEffect → useLayoutEffect → useEffect

 

this hook does not have access to refs and cannot schedule updates.

 

선택적(Selective) Hydartion 등의 동시성(Concurrent) 기능이 추가

 

이 파트는 알아두면 좋을듯

 

 

새로운 서버 사이드 렌더링 아키텍처가 도입(<Suspense>와 React.lazy를 지원)

및 부분 SSR 지원

React 18에는 렌더링을 위한 3가지 API가 존재

  • renderToString: 유지 (제한된 Suspense 지원)
  • renderToNodeStream: Deprecated (Full Suspense를 지원하나, 스트리밍되지 않음)
  • pipeToNodeWritable: 신규 API, 사용 추천(스트리밍으로 Full Suspense) 지원

 

ref

 

https://medium.com/naver-place-dev/react-18을-준비하세요-8603c36ddb25

 

React 18을 준비하세요.

요약

medium.com

https://yceffort.kr/2022/04/react-18-changelog#starttransition-usetransition

 

Home

yceffort

yceffort.kr

 

반응형

'Front-end > React' 카테고리의 다른 글

Suspense를 위한 wrapper 만들기  (0) 2022.07.28
react 17(개인 공부용)  (1) 2022.06.09
react TMI  (0) 2022.04.13
drag and drop으로 창 크기 조절  (0) 2022.03.18
redux, redux-saga로 로딩, 에러 페이지 구현  (0) 2022.02.12
반응형

JSX(JavaScript Syntax Extension and occasionally referred as JavaScript XML)

 

const element = <h1>{title}</h1>;

- JSX은 HTML가 아니라 js이므로 camelCase 사용

 

- React는 별도의 파일에 마크업 & 로직을 넣어 인위적으로 분리 대신

둘다 포함하는 '컴포넌트'라는 단위로 관심사를 분리

 

- JSX는 주입 공격 방지(렌더링 하기 이전에 문자열로 변환되어 XSS 방지)

 

- Babel은 JSX를 React.createElement() 호출로 컴파일

 

DangerouslySetInnerHTML

function createMarkup() {
  return {__html: 'First &middot; Second'};
}

function MyComponent() {
  return <div dangerouslySetInnerHTML={createMarkup()} />;
}

브라우저 DOM에서 innerHTML을 사용하기 위한 React의 대체 방법

* 사용은 하지만 innerHTML은 위험하다는 내용 상기

 

Rendered Element

 

- React Element들은 생성된 이후에 immutable

-> it represents the UI at a certain point in time.

 

State

 

- setState() state update는 비동기 처리 (State update May Be Asynchronous - React may batch multiple setState() calls into a single update for performance.)

 

- state Updates are merged

  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []    
    };
  }

예를 들어 위와 같은 코드가 있을때

  componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments      });
    });
  }

 

위와 같은 코드를 실행하면 상태 변경 merge됨 (The merging is shallow, so this.setState({comments}) leaves this.state.posts intact, but completely replaces this.state.comments.)

 

- 데이터는 단방향(아래로 흐른다)

 

Event

 

- 이벤트는 합성 이벤트

function Form() {
  function handleSubmit(e) {
    e.preventDefault();    console.log('You clicked submit.');
  }

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">Submit</button>
    </form>
  );
}

여기서 e는 리엑트 고유의 합성 이벤트(synthetic event)라 cross-browser 호환성을 걱정하지 않아도 된다

 

제어 컴포넌트 (Controlled Component)

<input>, <select> 등이 있을때 컴포넌트의 상태, props로 주어진 값을 활용하는 컴포넌트

ex) input 태그의 value 값을 useState로 제어)

 

비제어 컴포넌트

 

<input> 등이 자체적으로 상태를 갖는 경우 - (useRef등으로 관리)

해당 데이터와 UI의 동기가 이뤄지진 않으니 주의 (해당 상태가 변해도 컴포넌트의 상태가 변한게 아니므로 리렌더링이 일어나지 않는다)

 

ex) <input type="file"/> 의 경우 File API를 통해 js로 조작 가능한데 값이 읽기 전용이고 사용자만이 값 설정이 가능해서 비제어 컴포넌트

 

Key

 

가상돔 갱신할때 휴리스틱한 알고리즘으로 연산 속도를 돕는다

 

가정 : 

 

  • 서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.
  • 개발자가 key prop을 통해, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.

 

비교대상 - sibling

배열의 index를 key로 사용시 뒤죽박죽 되므로 변하지 않고 예상 가능한 '유일값' 사용

 

Hook

 

클래스 컴포넌트에서 함수 컴포넌트로 바꾼 동기 요약

1. 간결하고(simplify) 빠르게 

2. 빠르게(performance)

3. 재사용성(Reusing logic & avoid Giant components & Confusing classes)

 

그외 이유 

1. 기존 클래스 컴포넌트에서는 컴포넌트간 로직의 재사용이 힘들었는데 

hook을 쓰면 상태 관련 로직을 재사용하기 쉽게 만들어준다.

-> 생명주기 메서드 기반으로 쪼개는것보다 hook을 통해 서로 비슷한 일을 하는 함수 묶음으로 컴포넌트를 나눈다.

 

2. 클래스 기반은 사람과 기계를 혼동시킨다

자바스크립트의 class와 this는 사람을 혼동시킨다.

-> hook은 class 없이 사용하는 방법을 제시

 

hook 규칙 

 

react hooks API은 hooks임을 네이밍에서부터 드러내기 위해 use-* prefix를 사용

 

1. 오직 React 내에서만 hook 호출

2. 최상위에서만 호출 

 

훅은 전역 배열로 관리되고 생성되는 순서에 따라 컴포넌트를 key로 해 인덱스로 관리

-> 항상 최상위에서 호출되야한다 (호출 순서 흐름이 항상 예상 가능해야한다)

function Ex(){
    const [a, setA] = useState(0);
    const [b, setB] = useState(0);
 
}

정확한 비유는 아닐 수 있지만.. 위 같은 예시는 내부적으로

[a, setA, b, setB ...];

이런식으로 저장될텐데 if 분기문같은 이유로 인해 hook 호출 순서가 바뀌면 원치 않은 값을 사용할 가능성이 생김

 

useEffect의 Effect란?

 

sideEffect 할때 그 Effect!

데이터 가져오기, dom 수동 수정, dom 수정 등 이 모든게 side Effect

리엑트 상태(FSM)와 연관 없는 부분을 side Effect라 부르는듯?

 

useRef

 

일종의 자바스크립트 객체값(plain object)를 반환(참조값)하기 때문에

리엑트의 상태 & 렌더링 생명주기와 연관없이 고유의 값을 가진다

(useRef 바뀌어도 리렌더링 되지 않는다)

 

reference

 

https://reactjs.org/

 

React – A JavaScript library for building user interfaces

A JavaScript library for building user interfaces

reactjs.org

 

 

반응형

'Front-end > React' 카테고리의 다른 글

react 17(개인 공부용)  (1) 2022.06.09
React18(개인 공부 메모용)  (0) 2022.06.09
drag and drop으로 창 크기 조절  (0) 2022.03.18
redux, redux-saga로 로딩, 에러 페이지 구현  (0) 2022.02.12
redux 기본 구조  (0) 2022.02.02
반응형

 

갑자기 부캠 프로젝트에서 시간 문제로 미완성 했었던

drag and drop 크기 변경 기능을 한번 구현해보고 싶어서 잠깐 해보았다.

 

레이아웃 틀만 잡아주고 팀원에게 넘기고 다른곳으로 넘어간곳이라 새로운 기능 추가가 어떤 사이드 이펙트를 낳을지 몰라 무섭긴 하지만... 프로젝트도 종료되었고 drag and drop 연습겸 해보았다!

 

기본 원리

const PAGEVW = 100;
const OPENSIZE = 82;
const SIDEBARSIZE = 18;
const CLOSEDSIZE = 61;

export const sizestate = atom<number>({
  key: 'sizeState',
  default: OPENSIZE,
});

구현할때 왼쪽부터 차례대로 Channels, Workspace(chat), Thread(reply)라고 나눴었고.

 

전체 width를 100(%)이라고 했을때

댓글창이 안열려 있다면 workspace의 width가 82(%)이고 channel이 18(%),

열려있다면 workspace의 width가 61(%)이고 나머지가 reply(21%) 창과 channel이 18(%)이 되도록 구현해놓은 상태였다.

 

그리고 위에서 구한 값을 recoil에 넣어 전역으로 관리했고, styled-component에 props로 넣어 각 화면의 width를 비율대로 맞추게 했다.

 

// workspace 길이 
export const mainWorkspaceSizeState = selector<number>({
  key: 'mainWorkspaceSizeState',
  get: ({ get }) => {
    const { isOpened } = get(replyToggleState);
    const OPENEDSIZE = get(sizestate);

    if (isOpened) return CLOSEDSIZE;
    return OPENEDSIZE;
  },
});

//reply 길이
export const replyWorkspaceState = selector<number>({
  key: 'replyWorkspaceSizeState',
  get: ({ get }) => {
    const { isOpened } = get(replyToggleState);

    if (!isOpened) return 0;

    const size = PAGEVW - get(mainWorkspaceSizeState) - SIDEBARSIZE;
    return size;
  },
});

(위 코드는 workspace 의 width를 기준으로 reply 창이 열렸을때, 안열렸을때 recoil의 selector를 이용해 각각 길이를 계산해주는 코드이다.)

 

그렇다면 여기서 넣어줄 기능은 drag and drop으로 동적으로 width를 변경할 수 있는 코드만 넣으면 되는데...

 

다음 방식을 사용했다.

 

저 빨간 선(div)를 구현한 코드는 다음과 같다.

 

import React from 'react';
import { useSetRecoilState } from 'recoil';
import { widthSizeState } from '@state/workspace';

import Container from './styles';

const DraggableDiv = (): JSX.Element => {
  const setWidthSize = useSetRecoilState(widthSizeState);

  const dragEventHandler = (e: React.DragEvent) => {
    const { target } = e;
    const { parentNode } = target;

    const parentBox = parentNode.getBoundingClientRect();
    setWidthSize(Math.max(47, (e.clientX / parentBox.width) * 100 + 1));
  };

  return <Container draggable="true" onDragEnd={dragEventHandler} />;
};

export default DraggableDiv;

원리는 상당히 간단하다.

 

drag 하고 마우스를 놨을때 drag가 끝난 점의 좌표를 계산해 넣어준다.

클릭한 점의 X 좌표를 빨간선 div의 부모 width로 나누고 100을 곱하면 백분위가 되는데

왜냐하면 전체 박스 길이(width)에서 맨 왼쪽을 기준으로 오른쪽만큼 간게 e.clientX가 되므로

단순히 나눠주고 100을 곱하면 백분위가 된다.

 

참고) draggable 이 달려있는게 빨간선이다.

 

그렇게 구한 값을 recoil에 적용해주면 workspace의 width이고, selector를 통해 reply의 크기가 자동으로 반영되므로 

화면의 크기를 동적으로 바꿀 수 있게된다.

 

 

channel도 동일하게 drag and drop을 붙일 수 있는데 노가다의 문제이므로 

drag and drop을 써본거에 만족하고 끝내려 한다. 

 

(구현하려면 DraggableDiv에서 setRecoilValue만 바꿔주면 되는데 위에서 주입하거나 view와 logic을 분리해서 구현하면 될꺼같다?)

반응형

'Front-end > React' 카테고리의 다른 글

react 17(개인 공부용)  (1) 2022.06.09
React18(개인 공부 메모용)  (0) 2022.06.09
react TMI  (0) 2022.04.13
redux, redux-saga로 로딩, 에러 페이지 구현  (0) 2022.02.12
redux 기본 구조  (0) 2022.02.02
반응형

* 잘못된점이 있으면 피드백 주시면 감사하겠습니다!

 

예시 화면(by react-query로 제작)

 

react-query를 보면 data, loading, error 상태가 있고 로딩 화면, 에러 화면을 각각 보여줄 수 있다

리둑스와 리둑스 사가로 비슷한걸 연습겸 한번 만들어 보았다.

 

*혹시 redux-saga 가 익숙하지 않다면

https://mskims.github.io/redux-saga-in-korean/introduction/BeginnerTutorial.html

추천

 

우선 초기 constant 를 정의한다.

/* constrant.ts */

export const START_LOADING = 'loading/START_LOADING';
export const FAIL_LOADING = 'loading/FAIL_LOADING';
export const FINISH_LOADING = 'loading/FINISH_LOADING';

export const GET_ITEM = 'GET_ITEM';
export const GET_ITEMS = 'GET_ITEMS';

export const RESPONSE_STATUS = {
  REQUEST: {
    data: undefined,
    error: false,
    empty: false,
    loading: true,
  },
  FAILURE: {
    data: undefined,
    error: true,
    empty: false,
    loading: false,
  },
  SUCCESS: {
    error: false,
    loading: false,
  },
} as const;

 

RESPONSE_STATE는 각 비동기 조건(data, loading, error)때 가지고 있을 상태이다.

SUCCESS의 경우엔 fetch된 data를 가지고 있어야 하기 때문에 나중에 상태를 만들때 주입할 예정이다.

 

~LOADING constrant들은 각 조건때의 action이다.

GET_ITEM, GET_ITEMS는 각각 아이템 하나, 아이템 리스트를 받아오는 비동기 요청 action이다.

 

이제 action를 제작해보자.

// action.ts

export const LOADING_STATUS = {
  GET_ITEM,
  GET_ITEMS,
} as const;

export type LoadingStatusType = typeof LOADING_STATUS[keyof typeof LOADING_STATUS];

function getVaildRequestType(requestType: string): LoadingStatusType {
  return LOADING_STATUS[requestType];
}

export const startLoading = (requestType: string) => ({
  type: START_LOADING,
  payload: getVaildRequestType(requestType),
});

export const failLoading = (requestType: string) => ({
  type: FAIL_LOADING,
  payload: getVaildRequestType(requestType),
});

export const finishLoading = (requestType: string, response: []) => {
  return {
    type: FINISH_LOADING,
    payload: getVaildRequestType(requestType),
    response,
  };
};

 

getVaildRequestType을 쓴 이유는 action과 reducer를 추상화하고 비동기 요청때마다 기본 템플릿으로 재사용하기 위해서 저런 번거로운 방식을 사용한것이다. (requestType로 type를 보내준다.)

 

(사실 getVaildRequestType을 안쓰고 그냥 payload:requestType해도 되긴 하는데 확장성을 위해서(혹시 상태의 이름을 변경하고 싶을때 사용하라고) 넣었다)

 

 

reducer를 만약 재사용을 하지 않는다면 비동기 요청 하나하나마다 각각 reducer를 제작해야하는 번거로움이 존재한다. 

 

이것은 reducer를 보면서 자세히 설명한다.

LOADING_STATE의 역할도 reducer를 보면서 설명한다.

각 action에 넣어주는 requestType는 redux-saga에서 넣어준다

 

// reducer.ts

function setInitState() {
  const init = {};

  const { REQUEST } = RESPONSE_STATUS;
  // eslint-disable-next-line no-return-assign
  Object.keys(LOADING_STATUS).map((key) => (init[LOADING_STATUS[key]] = REQUEST));
  return init;
}

interface LoadingActionType {
  type: string;
  payload: string;
  response?: [];
}

export function asyncLoadingReducer(state = setInitState(), action: LoadingActionType) {
  const { type, payload, response } = action;
  const { REQUEST, FAILURE, SUCCESS } = RESPONSE_STATUS;

  switch (type) {
    case START_LOADING:
      return {
        ...state,
        [payload]: REQUEST,
      };

    case FAIL_LOADING:
      return {
        ...state,
        [payload]: FAILURE,
      };
    case FINISH_LOADING:
      return {
        ...state,
        [payload]: { ...SUCCESS, data: response, empty: isEmpty(response) },
        //isEmpty는 그냥 fetch된 데이터가 비었나 확인해주는 함수
      };
    default:
      return state;
  }
}

 

[payload] : ~~상태 를 사용해서 아까 보낸 getVaildRequestType의 파라미터마다 상태를 가질 수 있다. 

 

예를 들어 LOADING_STATE에 지금 GET_ITEM 과 GET_ITEMS라는 type를 지정해두고 이후 redux-saga에서 실행할때 해당 type를  requestType(payload)로 받아온다면 type마다 { data, loading, error } 결과값을 가지는 상태를 가질 수 있다!

(이게 무슨말인지 혼동된다면 아래의 redux-saga까지 보고오면 이해가 빠를것 같다)

 

GET_ITEM, GET_ITEMS 각각 type 하나마다 { START_LOADING, FAIL_LOADING, FINISH_LOADING } case 상태(RESPONSE_STATE)를 가진다. 

ex) GET_ITEMS만 ajax 요청을 받아오고 완료되었을때 상태 출력(case FINISH_LOADING), GET_ITEMS는 아직 요청을 보내지 않았다(case START_LOADING-(default)).

SUCCESS(case FINISH_LOADING)땐 아까 만든 상태에 data를 주입해서 반환해주자.

 

이제 redux-saga를 보자.

function* abstractGenerator({ ACTION, axiosRequest }) {
  yield put(startLoading(ACTION));

  try {
    const response = yield axiosRequest;
    yield put(finishLoading(ACTION, response.data));
  } catch (error) {
    yield put(failLoading(ACTION));
  }
}

function *getItemListGenerator() {
  yield abstractGenerator({ ACTION: GET_ITEMS, axiosRequest: call(getItemList) });
}

function *getItemByIdGenerator(status: { id: number; type: string }) {
  const { id } = status;
  //yield put ~~ 
  yield abstractGenerator({ ACTION: GET_ITEM, axiosRequest: call(getItemById, id) });
}

function* getItemLIstSaga() {
  yield takeLatest(GET_ITEMS, getItemListGenerator);
}

function* getItemByIdSaga() {
  yield takeLatest(GET_ITEM, getItemByIdGenerator);
}

export function* rootSaga() {
  yield all([getItemLIstSaga(), getItemByIdSaga()]);
}

 

비동기의 기본 로직을 잘 보면

function* abStractGenerator({ ACTION, axiosRequest }) {
  yield put(startLoading(ACTION));

  try {
    const response = yield axiosRequest;
    yield put(finishLoading(ACTION, response.data));
  } catch (error) {
    yield put(failLoading(ACTION));
  }
}

 

1. 데이터 fetch를 시작한다 - START_LOADING(action)

2. action(GET_ITEM, GET_ITEMS)에 따라 ajax 요청을 보내고,  - START_LOADING 

3. 데이터를 받아오는데 성공했다면 성공 action을 반환한다. - FINISH_LOADING 

4. 실패했다면 실패 action을 반환한다. - FAIL_LOADING

 

비동기 요청시 대다수의 redux-saga는 비슷한 로직으로 실행될것이다.

그럼 이 비동기 로직도 달라지는 값인 action과 ajax 함수 값만 파라미터로 주입해준다면 재사용을 할 수 있다.

 

그리고 여기서 action인 startLoading, finishLoading, failLoading에 넣는 ACTION이 아까 보았던 requestType의 정체다.

그렇게 넣으면 payload로 action이 들어가게 되고... asyncLoadingReducer에서 [payload]마다 로딩, 에러, 성공시 알맞은 상태를 반환한다.

 

이런식으로 1개의 reducer, 1개의 saga를 재사용해 n개 이상의 다른 비동기 요청을 처리할 수 있다.

 

그리고 ajax 함수들은 이런식이다.

export const getItemList = () => axios.get(API.GET.ITEMLIST); // API.GET ~ 는 주소

export const getItemById = (itemId: number) => {
  return axios.get(API.GET.ITEM, {
    params: {
      itemId,
    },
  });
};

 

예시 component) 

export function ItemBox() {
  const { asyncLoadingReducer } = useSelector((state: RootStoreType) => state);
  const disPatch = useDispatch();

  const itemRequest = asyncLoadingReducer[LOADING_STATUS[GET_ITEM]];
  const { data, empty, loading, error } = itemRequest;

  useEffect(() => {
    disPatch({ type: GET_ITEM, id: 1 });
  }, []);

  if (loading) return <div>loading</div>;
  if (empty) return <div>emptyData</div>;
  if (error) return <div>error</div>;

  return <div>{JSON.stringify(data)}</div>;
}

 

추가) 

import { createStore, combineReducers, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { asyncLoadingReducer } from './reducer';
import { rootSaga } from './saga';

const configureStore = () => {
  const sagaMiddleware = createSagaMiddleware();
  const rootReducer = combineReducers({
    asyncLoadingReducer,
  });

  const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
  sagaMiddleware.run(rootSaga);
  return store;
};

const rootStore = configureStore();

export type RootStoreType = ReturnType<typeof rootStore.getState>;
export default rootStore;

 

해본 후기)

 

그냥 react-query 쓰자

 

같이 읽으면 좋은 글)

 

https://techblog.woowahan.com/6339/

 

Store에서 비동기 통신 분리하기 (feat. React Query) | 우아한형제들 기술블로그

오늘은 주문에서 사용하는 FE 프로덕트의 구조 개편을 준비하며 FE에서 사용하는 Store에 대해 개인적인 고민 및 팀원들과 검토하고 논의했던 내용을 소개합니다. 이 과정에서 생긴 여러 가지 의

techblog.woowahan.com

 

반응형

'Front-end > React' 카테고리의 다른 글

react 17(개인 공부용)  (1) 2022.06.09
React18(개인 공부 메모용)  (0) 2022.06.09
react TMI  (0) 2022.04.13
drag and drop으로 창 크기 조절  (0) 2022.03.18
redux 기본 구조  (0) 2022.02.02
반응형

원래 redux를 처음에 공부하다가 

부스트캠프에서 프로젝트할때 recoil를 도입해서 사용했었는데

recoil이 나온지 얼마 안되다보니

redux의 기본적인 틀에 대한 코드만 메모해두려고 한다.

 

redux의 핵심 개념 - flex 패턴

 

사용자와 상호작용을 하다보며 view가 가끔씩 state을 업데이트해나갈때가 있는데,

그 영향이 다른 view나 state를 연쇄적으로 업데이트해서 서로 상호작용을 일으켜 의도치 않은 

버그나 결과를 내놓는 경우가 있었다

 

즉, 공을 튀기면 어느곳으로 튕겨나갈지 모르는 연쇄적인 반응이 일어났던것이다.

그래서 연쇄적 업데이트를 방지하고 단방향 데이터 흐름인(undirectional data flow) 인 flex 패턴이 탄생했다.

redux는 flex 패턴을 활용한 유명한 라이브러리이다.

 

Flux는 단방향으로 데이터가 흐르게 하는 구조이다.

redux는 크게 4가지 파트로 이루어져 있다.

 

상태 변경 정보인 Action

Action을 Store 에 반영하는 Dispatcher

Action을 처리해서 상태를 변경시키는 reducer

상태를 저장하는 Store

 

 

view layer에서 상태를 변경하고 싶다면 정해진 Action을 dispatch에 담아 호출한다. 

그럼 Reducer에서 그 액션을 처리하고 상태를 변경하여 store에 반영한다.

 

이제 연쇄적인 양방향 데이터 흐름을 단방향 데이터 흐름으로 바꾸었고, side effect가 없는

순수함수적인 개념으로 동작한다.

 

 

 

 

그림이니 눌러도 작동 안합니다 😂

그럼 버튼을 누르면 숫자가 늘어나거나 줄어드는 간단한 화면을

코드로 구현해보자.

Action

const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

 

Action 명은 그냥 unique한 식별자를 가진 이름으로 구현하면 된다.

옛날에 수학 시간에 배운 함수를 떠올려보자.

 

함수 F 에 x 를 넣으면 y가 나온다는 수학 공식을 떠올리면

항상 같은 x를 넣으면 같은 y가 나온다.

F(x)=y

 

정해진 Action과 파라미터를 dispatcher에 넣으면 (상태가 같다면) 항상 같은 결과가 나오는것을 기대할 수 있어야 한다.

즉, 함수형 패러다임 개념을 일부 가져왔다.

강제된 법칙은 아닐테지만 의도치않은 버그를 보고싶지 않다면 따르는것이 좋다.

 

function increment(value: number) {
  return { type: INCREMENT, payload: { value } };
}

function decrement(value: number) {
  return { type: DECREMENT, payload: { value } };
}

해당 방식으로 action과 payload를 미리 정의할수도 있다.

payload는 action 이외에 view에서 받고 싶은 파라미터이다.

 

간단한 수학공식으로 예를 들면

F(x, a) = x + a 라고 볼 수 있다.

 

reducer

const initState = {
  value: 0,
};

const reducer = (state = initState, action) => {
  const { type, payload } = action;
  const { value } = state;

  switch (type) {
    case INCREMENT:
      return { ...state, value: value + payload.value };
    case DECREMENT:
      return { ...state, value: value - payload.value };
    default:
      return state;
  }
};

 

reducer는 state와 action을 받아서

알맞은 action에 따라 연산을 한 후 값을 반환한다.

 

아까 설명한 F(x)=y 의 함수(F) 역할을 하는 셈이다.

state의 초기값은 매개변수로 넣은 객체값이고, 이후 리둑스 라이브러리에서 자체적으로 관리와 갱신을 해주게 된다.

그리고 switch문 내부 가독성을 위해

case문 하나 하나 함수를 구현하는것을 추천한다.

store

const store = createStore(
  combineReducers({
    reducer,
  }),
);

store는 리엑트의 '전역' 상태 state라 보면 이해가 편하다.

store는 한개만 있어야 한다. (a single source of truth)

 

만약 reducer를 너무 크게 만들지 않고 기능별로 쪼개고 싶다면 combineReducer를 활용해서 합친다.

 

dispatcher

 

import { Provider, useDispatch, useSelector } from 'react-redux';

function Test() {
  const { value } = useSelector((state) => state.reducer);
  const dispatch = useDispatch();

  return (
    <div>
      <button
        onClick={() => dispatch({ type: INCREMENT, payload: { value: 10 } })}
      >
        up 1
      </button>

      <button onClick={() => dispatch(increment(10))}>up 10</button>
      <button
        onClick={() => dispatch({ type: DECREMENT, payload: { value: 10 } })}
      >
        down
      </button>

      {value}
    </div>
  );
}

ReactDOM.render(
  <Provider store={store}>
    <Test />
  </Provider>,
  document.getElementById('root'),
);

 

dispatcher는 미리 action을 받아서 reducer를 통해 상태를 갱신한다.

함수형 컴포넌트에서 react-redux 의 useSelector, useDispatch hook을 사용하면 쉽게 구현 가능하다.

 

      <button onClick={() => dispatch(increment(10))}>up 10</button>
      <button onClick={() => dispatch({ type: DECREMENT, payload: { value: 10 } })}>
        down
      </button>

위 코드 중간에 해당 방식처럼 dispatch에 넣는 방식으로는 함수를 쓸 수도 있고, 아니면 객체 리터럴을 정해진 형식대로 그대로 넣어도 된다.

 

(무슨 방식이든 내가 기존에 구현했던 reducer와 매칭이 잘되야 한다.)

 

나는 미리 구현된 함수를 넣는 방식을 추천한다.

남이 봤을때 dispatch만 봐서 무슨 역할을 하는지 이해가 어려울 수 있는데,

잘 명시된 함수 명으로 힌트를 주는 방식으로 구현하는게 유지보수및 협업 측면에서 좋다고 생각된다.

(위 코드는 예시 코드이니 함수명이 매우 간결하다 😂)

 

그외

 

개인적으로 

action, state, reducer 등을 한 파일에 몰아넣지 말고 

각각 모듈화해서 분리(action은 action끼리, reducer는 reducer 끼리)해서 파일구조를 짜는걸 추천한다.

 

그리고 redux-toolkit 이라는 redux를 보다 쉽게 사용 가능하게 도와주는 라이브러리도 있다.

다른 라이브러리는 redux 기반으로 설명 되어 있는 글이 많아서 처음 배우고 있을때 보면 엄청 헷갈린다.

redux가 어느정도 익숙해지면 나중 생산성 향상을 위해 도입해보는거도 나쁘지 않는거 같다.

 

같이 읽어보면 좋을 글)

 

전역 상태를 왜 쓰는지 -> props drilling 해결

https://medium.com/front-end-weekly/props-drilling-in-react-js-723be80a08e5

 

Props Drilling In React.Js

What Is Props Drilling? And How To Sidestep It?

medium.com

 

기존 react는 (model이 없는) VVM 혹은 VC 패턴이라고 보는게 정확하다는 의견도 있다.

https://stackoverflow.com/questions/51506440/mvvm-architectural-pattern-for-a-reactjs-application

 

MVVM architectural pattern for a ReactJS application

I'm a semi-senior react and JavaScript developer, I've made several Universal react application. Today our CTO told me: Do you use a software architectural pattern for your application? I've no a...

stackoverflow.com

 

reference

 

https://redux.js.org/tutorials/fundamentals/part-1-overview

 

Redux Fundamentals, Part 1: Redux Overview | Redux

The official Fundamentals tutorial for Redux: learn the fundamentals of using Redux

redux.js.org

 

 

https://sihus.tistory.com/37

 

Flux Design Pattern 이해하기 (+ 단방향, 양방향 데이터 바인딩)

MVC 디자인 패턴이 아닌 Redux의 주개념이자, 리액트를 사용하면서 컴포넌트끼리 데이터를 교류할 때, 글로벌 이벤트 시스템을 설정하는 방법인 Flux 디자인패턴에 대해 알아보고 왜 facebook에서 reac

sihus.tistory.com

 

https://yoonho-devlog.tistory.com/171

 

Redux 개념 이해하기

Flux 패턴 redux는 Flux 패턴의 구현체입니다. 따라서 Flux 패턴을 먼저 이해하면 자연스럽게 Redux를 이해할 수 있습니다. facebook은 우리가 흔히 알고 있는 MVC 패턴을 사용하는 대신, Flux라는 새로운 방

yoonho-devlog.tistory.com

 

https://hanamon.kr/redux%EB%9E%80-%EB%A6%AC%EB%8D%95%EC%8A%A4-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC/

 

Redux(리덕스)란? (상태 관리 라이브러리) - 하나몬

Redux(리덕스)란? 무엇인지 부터 간단한 실습까지 (상태 관리 라이브러리 리덕스 알아보기) ⚡️ Redux(리덕스)란? Redux(리덕스)란 JavaScript(자바스트립트) 상태관리 라이브러리이다. Redux(리덕스)의

hanamon.kr

 

 

반응형

'Front-end > React' 카테고리의 다른 글

react 17(개인 공부용)  (1) 2022.06.09
React18(개인 공부 메모용)  (0) 2022.06.09
react TMI  (0) 2022.04.13
drag and drop으로 창 크기 조절  (0) 2022.03.18
redux, redux-saga로 로딩, 에러 페이지 구현  (0) 2022.02.12

+ Recent posts