반응형

react 17은 큰 변화는 없고 점진적인 upgrade를 위한 단계라고 한다.

 

 

이벤트 위임의 변경점

 

기존에는 document에 이벤트 위임을 붙여 이벤트를 처리했다면

17부터는 root에 이벤트를 붙여서 처리

 

https://ko.reactjs.org/blog/2020/08/10/react-v17-rc.html#changes-to-event-delegation

ex) 

<button onClick={handleClick}>

 

 

위 코드같은경우엔 내부적으로 

 

const rootNode = document.getElementById('root');
ReactDOM.render(<App />, rootNode);

 

root에 이벤트 위임을 넣어 처리

기존에는 (예시로) 리엑트 내부 e.stopPropagation() 코드가 혼용되서 쓰여지고 있는 JQuery 이벤트를 prevent하는 일이 있었는데

이런일을 방지하게 변경

또한 React17에서 event propagation이 보다 regular dom에 가깝게 변경

 

 

브라우저 최적화 (잡다한 변경점)

 

1. onScroll이 네이티브에선 버블링되지 않는데 React onScroll은 버블링되서 이벤트 버블링 제거 

 

2.

onFocus = focusin

onBlur = focusout 이벤트 사용하도록 변경?

(focusin focusout은 버블링 발생) 

 

3. Capture phase events (e.g. onClickCapture) 가 이제 진짜 캡처 페이즈 리스너 사용 (이전엔 어케했누?)

 

이벤트 풀링 최적화 제거 (No Event Pooling)

 

“event pooling” optimization이 별 의미없어서 삭제

비동기적으로 이벤트 객체에 접근 불가능했던게 삭제됨

 

function handleChange(e) {
  setData(data => ({
    ...data,
    // This crashes in React 16 and earlier:
    text: e.target.value <- before v17 : null
  }));
}

 

난 2021 입문자라(react 17 이후 사용) event pooling이 무슨 역할이었는지 정확히는 모르겠다

SyntheticEvent 객체는 이벤트 핸들러가 호출된 후 초기화되기에, 비동기적으로 이벤트 객체에 접근할 수 없었던 것이 수정되었다고 한다.

 

아마 setData 실행시기와 e의 생명주기가 일치하지 않았던것(초기화되버려서)으로 추론된다.

 

Effect Cleanup Timing

useEffect(() => {
  // This is the effect itself.
  return () => {    // This is its cleanup.  };});

 

useEffect가 보다 효율적으로 변경 

 

기본 컨셉 : 

 

Most effects don’t need to delay screen updates, so React runs them asynchronously soon after the update has been reflected on the screen.

 

(For the rare cases where you need an effect to block paint, e.g. to measure and position a tooltip, prefer useLayoutEffect.)

 

그러나 보통은 unmounting 될때 cleanup function은 보통 동기적으로 실행되게 사용되어 왔다.

(similar to componentWillUnmount being synchronous in classes)

이것은 탭 변경과 같은 큰 페이지 변경시 속도 저하를 초래했다. 

 

그래서 React 17에서는 항상 cleanup function은 비동기적으로 실행되게 변경되었다.

 

(In React 17, the effect cleanup function always runs asynchronously — for example, if the component is unmounting, the cleanup runs after the screen has been updated.)

 

동기적 상황을 원하면 useLayoutEffect를 쓰시라

 

(you might want to rely on the synchronous execution, you can switch to useLayoutEffect instead)

 

추가적으로 cleanup function은 언제나 new Effect가 실행하기 전에 실행된다. 

useEffect(() => {
  const instance = someRef.current;
  instance.someSetupMethod();
  return () => {
    instance.someCleanupMethod();
  };
});

 

Consistent Errors for Returning Undefined

let Button = forwardRef(() => {
  // We forgot to write return, so this component returns undefined.
  // React 17 surfaces this as an error instead of ignoring it.
  <button />;
});

let Button = memo(() => {
  // We forgot to write return, so this component returns undefined.
  // React 17 surfaces this as an error instead of ignoring it.
  <button />;
});

return undefined하면 에러

(제대로 return 안하면 에러) 

 

만약 아무것도 return 하지 않는 것을 명시적으로 나타내고 싶다면 return null 사용

 

ref

https://han7096.medium.com/react-v17-release-candidate-톺아보기-6a4b091965c4

 

React v17 Release Candidate 톺아보기

변경사항과 유의할점을 React 공식 문서와, 개인적인 지식을 덧붙여 정리한 글

han7096.medium.com

https://reactjs.org/blog/2020/08/10/react-v17-rc.html

 

React v17.0 Release Candidate: No New Features – React Blog

Today, we are publishing the first Release Candidate for React 17. It has been two and a half years since the previous major release of React, which is a long time even by our standards! In this blog post, we will describe the role of this major release, w

reactjs.org

 

반응형

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

react-flow에 undo, redo 구현  (0) 2022.09.03
Suspense를 위한 wrapper 만들기  (0) 2022.07.28
React18(개인 공부 메모용)  (0) 2022.06.09
react TMI  (0) 2022.04.13
drag and drop으로 창 크기 조절  (0) 2022.03.18
반응형

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
반응형

*틀린 부분 있으면 피드백 주시면 감사하겠습니다~

 

async/await를 쓸줄은 아는데 정확히 어떻게 동작하는지 애매하게 알고 있어서 작성해봅니다.

 

동작 원리

 

const a = () => {
  console.log("a 시작");
  b();
  console.log("a 끝");
};

const b = async () => {
  console.log("b 시작");
  await c();
  console.log("b 끝");
};

const c = async () => {
  console.log("c 시작");
  await d();
  console.log("c 끝");
};

const d = () => {
  console.log("d")
};

a();

출처 : https://velog.io/@jjunyjjuny/JavaScript-asyncawait%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8F%99%EC%9E%91%ED%95%A0%EA%B9%8C

 

이 코드를 이해하려면 일단 다음 부분에 대한 이해가 있어야한다.

 

- 자바스크립트의 비동기 처리방식

- 이벤트 루프와 테스크 큐, 마이크로 테스크 큐

- promise(ES6)와 async/await(ES8)

- async 함수는 항상 (명시적, 암묵적 방식으로) promise를 반환한다

 

자바스크립트는 비동기 함수를 처리하기 위해서 이벤트루프라는 방식을 사용하는데 이벤트 루프는 지금 콜스텍이 비었는지(실행하고 있는 task가 없는지)와 테스크 큐(들)에 지금 실행 가능한 task가 있는지를 반복적으로 확인한다. 그래서 비동기 함수는 큐에서 대기하다가 실행이 가능해지면 꺼내져와서 실행하는데, promise같은 경우에는 실행 우선 순위가 매우 높아서(마이크로 테스크큐) 실행 가능해지면 먼저 실행되게 된다.

 

async/await 함수의 동작 순서

 

1. await 키워드가 붙은 대상을 실행한다.

 

2. 1번 대상이 함수가 아니거나, 함수의 실행이 끝나면 '1번 대상을 실행한' async 함수를 일시정지하고 콜스텍에서 마이크로 테스크 큐에 옮긴다. 이때 await를 실행한 위치를 기억한다.

 

3. 지금 실행하고 있던 함수가 콜스텍에서 빠져나왔으니 다른 콜스텍을 실행한다. 

 

4. 콜스텍이 비었으면 2번에서 빠져 나왔던 async 함수를 다시 콜스택으로 옮겨 실행한다. 이때 await가 실행된 위치 다음부터 실행된다. 

 

5. 함수가 끝날때까지 반복...!

 

 

그래서 아까 문제를 다시 풀어보자면

 

1. a를 실행하고 콜스텍에 쌓인다 [a]

 

2. a 시작 출력 

 

3. b를 실행하고 콜스텍에 쌓인다 [a, b]

 

4. b 시작 출력

 

5. await keyword를 만났고 함수이므로 C를 실행하고 콜스텍에 쌓인다.  [a, b, c]

 

6. C 시작 출력

 

7.  await keyword를 만났고 함수이므로 D를 실행하고 콜스텍에 쌓인다.  [a, b, c, d]

 

8. D 출력

및 종료하고 콜스텍에서 빠져나온다. [a, b, c]

 

9. 7번에서 D를 시작했고 실행이 끝났으니 C는 마이크로 테스크큐에 들어가게 된다.  [a, b],  micro = [c]

 

10. 5번에서 C를 시작했고 9번에서 C가 일시정지 되었으므로 B도 마이크로 테스크 큐에 들어가게 된다.  [a],  micro = [c, b]

 

11. 콜스텍에서 B가 빠져나갔으므로 a가 이후 흐름을 실행한다.

a 끝 출력

a가 끝나서 콜스텍에서 빠져나온다. [], micro = [c, b]

 

12. 콜스텍에 비었으므로 이벤트루프가 마이크로 테스트큐에 있는 함수를 실행한다.

 

13. C 끝 출력 및 C가 콜스텍에서 빠져나온다. [], micro = [b]

 

14. B 끝 출력 및 b가 콜스텍에서 빠져나온다. [], micro = []

 

답 : 

a 시작
b 시작
c 시작
d
a 끝
c 끝
b 끝

 

결론 : 

async/await를 쓰면 가독성이 좋은 like-동기 방식으로 실행하는거처럼 보이지만 사실은 엄청 복잡한 비동기 방식으로 실행된다는것을 알게 되었다.  

 

 내부적 원리 요약

 

그래서 마이크로 테스크큐에 넣고 다시 콜스텍에서 실행될때 돌아올 위치를 어떻게 결정하는데? 의문이 떠올랐는데 Generator와 Promise의 원리를 섞어서 처리했다고 한다.

 

참고한 글) https://medium.com/sjk5766/async-await-%EC%9B%90%EB%A6%AC-cc643f18526d

async function fun() {
  await getDrink();
  await haveMeal();
  await drinkSoju();
}

예를 들어 위와 같은 코드가 있다면

 

function* fun() {
  yield getDrink(); // 1
  yield haveMeal(); // 2
  yield drinkSoju();// 3
}

위와 같은 함수로 변경되고 

yield를 만나 실행되고

내부에서 Promise 실행을 한 뒤 

Promise가 끝난다면 내부에서 resolve를 실행하고

Generator의 next()(step 함수 자체구현)를 호출해서 체인을 걸어주는식으로 동작한다고 한다. 

 

그런데 3년 전 글이라 실제로 바벨 홈페이지에 들어가서 돌려보니 (개념상으로는 비슷한데) 약간 달라진점이 있었다.

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    resolve(value);
  } else {
    Promise.resolve(value).then(_next, _throw);
  }
}

function _asyncToGenerator(fn) {
  return function () {
    var self = this,
      args = arguments;
    return new Promise(function (resolve, reject) {
      var gen = fn.apply(self, args);
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);
      }
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
      }
      _next(undefined);
    });
  };
}

바벨을 돌려봤더니 위 코드를 보면 

 

step() 부분이 변경되어서

 

asyncGeneratorStep 안에서 만약 Generator가 끝난다면 (예를들어 코드 맨끝줄 상황) 그대로 resolve하는것은 동일한데 

아니라면 Promise의 then (Promise.resolve(value).then(_next, _throw);)을 사용해 첫번째 await 함수까지 실행하고 다음 체인은 마이크로 테스크큐에 넣어주는것으로 구현한것으로 보인다.

 

직접 해보고싶은 분들은 아래 주소로..?

 

https://babeljs.io/repl#?browsers=&build=&builtIns=false&corejs=3.6&spec=false&loose=false&code_lz=FAMwrgdgxgLglgewgAhgQwM4GsCMAKASgG9gBIKJDBAGwFMA6ahAczwHIZbsc2CBuZMmABfYKEixEKdNgBMhZCUEAnWjDDKUEWgHdkABWUIAtnAy08eVRgLIAvAD5ggl67fvkCx8nMwAKnDGtAhgMFZceBQQVHSMLOwyWLK8BAA0yDgADNkE_CJiwJgAntDI4NDwSMgARgpKLlExDEysbNU8ea5oOmhwMKiYuIR8zg2UNM3xbcmdyKTdvf2J8rPk47Et7NUAzLwjosCNE3GtGOjKMHvAtQSH65OttBAAJgD8V8BAA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env&prettier=false&targets=&version=7.17.8&externalPlugins=&assumptions=%7B%7D 

 

Babel · The compiler for next generation JavaScript

The compiler for next generation JavaScript

babeljs.io

 

 

P.S) 바벨로 트렌스파일 말고 진짜 Generator로 변환하는가 찾아봤는데 V8 주석의 경우 변환한다고 적혀있긴 하다..

 

// ES#abstract-ops-async-function-await
// AsyncFunctionAwait ( value )
// Shared logic for the core of await. The parser desugars
//   await value
// into
//   yield AsyncFunctionAwait{Caught,Uncaught}(.generator_object, value)
// The 'value' parameter is the value; the .generator_object stands in
// for the asyncContext.

주석 발췌 from  https://github.com/v8/v8/blob/17a99fec258bcc07ea9fc5e4fabcce259751db03/src/builtins/builtins-async-function-gen.cc#L247-L254

 

reference

 

https://velog.io/@jjunyjjuny/JavaScript-asyncawait%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8F%99%EC%9E%91%ED%95%A0%EA%B9%8C

 

[ JavaScript ] async/await는 어떻게 동작할까

await가 만드는 동기처럼 보이는 비동기 코드

velog.io

 

https://medium.com/sjk5766/async-await-%EC%9B%90%EB%A6%AC-cc643f18526d

 

async-await 원리

async-await 을 처음 봤을 때, 기존 call-back 구조와 비교해 소스를 가독성 좋고 간단하게 짤 수 있구나에 감탄했고 어떻게 이렇게 될까 라는 생각이 들었습니다. async-await은 내부적으로 Generator와 Promi

medium.com

 

 

반응형

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

commonjs와 ESM의 차이  (0) 2023.07.25
pnpm 도입기 (feat: 모노 레포)  (0) 2023.07.15
JS로 Single Page Application 구현  (0) 2022.03.12
Generator  (0) 2022.02.02
호이스팅과 var, let에 대하여  (0) 2022.01.29
반응형

 

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

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
반응형

SPA(Single Page Application)이란? 

 

과거에는 웹 페이지를 클릭하면 하나의 문서를 전달하고, <a href="페이지 주소"> 같은 하이퍼 링크를 누르면

새로운 페이지를 서버에서 전송받아 보여주고는 했습니다.

즉, 새로운 페이지를 요청할때마다 정적 리소스가 다운로드 되고 (새로운) 전체 페이지가 전부 다 렌더링 됩니다.

 

SPA와 기존 방식(MPA)

SPA는 기존 방식과 달리 웹 페이지에 필요한 리소스(HTML,CSS,JS)를 한번에 다 다운로드 받은 뒤

새로운 페이지 요청이 있을 경우 페이지 갱신에 필요한 데이터만 서버에서 전달받아 페이지를 갱신하는 방식(AJAX)으로 구현이 가능합니다.

 

즉, 빈 도화지(HTML)에 그림(JS로 동적 렌더링)을 그리는 식으로 작동합니다.


JS로 간단하게 구현해봅시다.

 

폴더 구조

└─src
|   ├─pages
|   |   ├─ RouterButton.js
|   |   ├─ A.js
|   |   ├─ B.js
|   |   └─ C.js
|   ├─BaseComponent.js 
|   └─App.js
└─ index.html

page는 A,B,C 페이지가 있고

요청에 따라 index.html에 동적으로 A,B,C 페이지를 각각 그려줄 것입니다.

하얀 도화지에 그림(렌더링)을 그린다고 보시면 이해가 편합니다.

 

HTML

 

<html>
  <head>
    <title>SPA</title>
  </head>
  <body>
    <main class="app">
    </main>
    <script type="module" src="./src/app.js"></script>
  </body>
</html>

우선 기본 base html입니다. 

우리는 기본 html에 JS로 동적으로 화면을 그려줄 것 입니다.

 

(*type="module"을 쓰면 global scope가 아니고 독립적인 module scope가 생기고, ES6 module(import, export)를 사용 가능합니다.) 

 

간단히 렌더링 되기 이전까지의 과정을 요약해보자면

 

1. www.홈페이지 요청

2. 서버에서 리소스(HTML, JS, CSS)등을 클라이언트에 보내줌

3. HTML,CSS, JS 다운로드 완료

4. JS 동작 

5. HTML에 렌더링

 

BaseComponent.js

 

A,B,C 페이지 javascript폴더에 공통적으로 쓸 부모 클래스를 정의해줍니다.

 

아래에서 쓰이는 예시는 템플릿 메소드 패턴이라고 디자인 패턴을 사용한 것인데

부모 클래스에서 기본적인 로직을 짜고 아래 부분(자식 클래스)에서 구체화하는식으로 동작하게 구현하는 

방식입니다.

 

(예시는 객체지향 패러다임을 썼지만 객체지향적인 컨셉으로 짜도 되고 함수 컨셉으로 짜도 됩니다.)

export default class BaseComponent {

    constructor({$parent, setState, state}){
        this.$parent = $parent;
        this.state = state;
        this.setState = setState;
    }
    
    template(){
        return '';
    }

    #render(){
        const template = this.template();
        this.$parent.insertAdjacentHTML('beforeend', template);
    }

    setEvent(){

    }

    renderSequence(state){
        this.state = state;
        this.#render();
        this.setEvent();
    }
}

constructor에서 주입받는건 3가지인데 

이 중 $parent는 HTML의  <main class="app"></main> 값을 가지고 있는 변수(querySelector)입니다.

setState, state 두가지는 이 후에 차차 알아봅시다.

 

저런식으로 구현한다면 만약 class="app"을 가진 root element가 아니라

다른 element값을 주입해서 그 아래에 렌더링되는식으로 부모클래스가 자식 클래스를 제어할 수 있는 유연성을 가질 수 있습니다.

 

이것을 유식한 말로 DI(Dependency Injection)이라 합니다.

 

상속받은 자식 클래스들 (A.js, B.js, C.js)

 

우리가 자식 클래스에서 구현할것은 

template() 함수 와 setEvent() 함수입니다.

 

template() 함수는 안에 넣을 뼈대(string)를,

setEvent()는 렌더링 이후 페이지에 붙여줄 이벤트만 작성해주면 됩니다. (간단 예시니 event는 붙이지 않았습니다)

 

그럼 부모 클래스가 로직을 알아서 제어해서 렌더링 시켜줄 것입니다.

 

>> 자식 클래스들 코드 

더보기
import BaseComponent from '../BaseComponent.js';

export default class A extends BaseComponent{

    template(){
        return `IM A!
        ${RouterButton()}
        `;
    }

    setEvent(){

    }
}

export default class B extends BaseComponent{


    template(){
        return `IM B!
        ${RouterButton()}
        `;
    }


    setEvent(){
    }
}
export default class B extends BaseComponent{



    template(){
        return `IM B!
        ${RouterButton()}
        `;
    }


    setEvent(){
    }
}

export default function RouterButton(){
    return `
        <button type="button" class="A">
            A
        </button>
        <button type="button" class="B">
            B
        </button>
        <button type="button" class="C">
            C
        </button>
    `
};

 

이제 SpaRouter를 작성해 봅시다.

 

import A from './pages/A.js';
import B from './pages/B.js';
import C from './pages/C.js';

class app{

    constructor(){
        this.state = { 'locate' : window.location.pathname};
        this.root = document.querySelector('.app');
        
        const ObjectForDI = {$parent:this.root, setState : this.setState.bind(this), state : this.state};

        this.A = new A(ObjectForDI);
        this.B = new B(ObjectForDI);
        this.C = new C(ObjectForDI);

        this.render();
        this.setDummyEvent();
    }

    setState (newState){
        const { locate } = this.state;
        this.state = {...this.state, ...newState};
        this.render();
    }
    
    render(){
        this.root.innerHTML = '';

        let { locate } = this.state;

        console.log(locate);

        if(locate === '/A'){
            this.A.renderSequence(this.state);
        } 
        else if(locate === '/B'){
            this.B.renderSequence(this.state);
        }
        else{
            this.C.renderSequence(this.state);
        }

        this.historyRouterPush(locate);
    }

    historyRouterPush(locate) {
        window.history.pushState({}, locate, window.location.origin + locate);
    }
}

new app();

 

코드를 나눠서 살펴 봅시다.

 

라우팅 처리 방식 

 

    render(){
        this.root.innerHTML = '';

        let { locate } = this.state;

        console.log(locate);

        if(locate === '/A'){
            this.A.renderSequence(this.state);
        } 
        else if(locate === '/B'){
            this.B.renderSequence(this.state);
        }
        else{
            this.C.renderSequence(this.state);
        }
    }

지금 페이지의 경로(window.location.pathname) 에 따라 A, B, C 페이지가 라우팅됩니다.

경로가 /A면 A 페이지, /B면 B 페이지, 그외엔 C 페이지가 렌더링 되고 있습니다.

 

 

경로 변경 방식

 

    setState (newState){
        // newState = {locate : 'newPath' }
        this.state = {...this.state, ...newState};
        this.render();
    }

 

 특정 이벤트가 일어나면 setState를 호출하고, 다시 렌더링되게(위에서 보았던 render 함수 호출)됩니다.

 

그리고 state와 setState는 아까 BaseComponent.js의 생성자에서 본 state와 setState의 정체입니다.

부모 클래스(app.js)에서 자식 클래스(BaseCompoent 상속받은 A.js, B.js 등)에게 함수와 공통 객체를 주입해주고,

페이지를 바꿀 (정보 갱신) 타이밍이 오면 setState 함수를 자식 클래스에서 호출해서 해당 페이지를 전부 rerender 하게 됩니다.

 

리엑트에서 상태 변경때 쓰는 방식과 비슷하지 않나요?

내부 구현은 매우 다를거라 예상되지만... 비슷하게 작동합니다.

(이 코드에서 엄밀히 따지면 setState보다는 setRouterPath 등 다른 이름이 어울릴꺼 같긴 해요) 

 

뒤로가기, 앞으로 가기 구현

 

    historyRouterPush(locate) {
        window.history.pushState({}, locate, window.location.origin + locate);
    }

브라우저 API인 히스토리 API를 사용해서 매우 쉽게 구현 가능합니다.

 

이상 간단하게 SPA 구현 방식을 알아봤습니다.

 

 

ref

 

https://devopedia.org/single-page-application

 

Single Page Application

A web application broadly consists of two things: data (content) and control (structure, styling, behaviour). In traditional applications, these are spread across multiple pages of HTML, CSS and JS files. Each page is served in HTML with links to suitable

devopedia.org

 

참고)

 

클릭 이벤트 처리방법

 

    dummyClickEvent = ({target}) => {
        if(target.classList.contains('A')){
            this.setState({...this.state, locate : '/A'});
        }
        if(target.classList.contains('B')){
            this.setState({...this.state, locate: '/B'});
        }
        if(target.classList.contains('C')){
            this.setState({...this.state, locate: '/C'});
        }
    }


    setDummyEvent(){
        this.root.addEventListener('click', this.dummyClickEvent);
    }

 

반응형
반응형

jest로 테스트를 공부해보고 있는데 몇개 안넣었는데 3~4초 이상이 걸리길래 

한번 찾아봤다

 

https://if1live.github.io/posts/escape-from-jest-jest-is-slow/

 

Jest 탈출기 - Jest는 느리다 · /usr/lib/libsora.so

개요 요새 작업하는 프로젝트에서 jest를 사용해서 유닛테스트를 돌린다. 프로젝트가 진행될수록 테스트가 점점 느려지더니 이제 유닛 테스트 한번 돌리는데 1분이 걸린다. 라이젠 붙은 좋은 컴

if1live.github.io

 

요약) 

jest는 각 VM (Executing JavaScript) - test case에서 import를 각각 하기 때문에 느리다..

 

홀리..

반응형
반응형

See the Pen Untitled by lodado (@lodado) on CodePen.

 

css으로 직접 만들어본 loading spinner

 

출처 : https://codepen.io/mandelid/pen/kNBYLJ

 

Super Simple CSS Spinner

Just recreating http://codepen.io/scottkellum/pen/tzjCK to learn how he did it....

codepen.io

반응형

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

font-family 상속 시키기  (0) 2022.11.04
css 챌린지  (0) 2022.10.05
[CSS] box-sizing  (0) 2022.07.11
[CSS] Gap  (0) 2022.06.13
CSS 애니메이션 성능 개선  (0) 2022.01.27
반응형

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

 

예시 화면(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
반응형

 

CORS는 많이 들어봤는데

CORB란것을 '사이트 격리'에 대해 알아보다가

새로 알게 되었다.

 

참고) 사이트 격리

 

- It ensures that pages from different websites are always put into different processes.

- It also blocks the process from receiving certain types of sensitive data from other site

 

즉, Chrome's Site Isolation effectively makes it harder for untrusted websites to access or steal information from your accounts on other websites.

 

Cross-Origin Read Blocking(CORB)

 

Cross-Origin Read Blocking (CORB) is an algorithm that can identify and block dubious 
cross-origin resource loads in web browsers before they reach the web page.

 

For example, it will block a cross-origin text/html response requested 
from a <script> or <img> tag, replacing it with an empty response instead.

 

출처 : https://developers.google.com/web/updates/2018/07/site-isolation

 

즉, 웹에서 cross origin의 data resource(xml, html, json 등)를 읽어와 사용하지 못하도록 브라우저에서 막는 동작이다.

 

원리 

 

CORB가 어떻게 동작하는지 알아보자.

보통 web client는 서버에 두가지의 요청을 보낸다.

 

  1. data resources such as HTML, XML, or JSON documents
  2. media resources such as images, JavaScript, CSS, or fonts

보통 1번은 SOP & CORS 설정에 따라 브라우저에 의해 blocking 되거나 보이게 된다.

(참고로 2번 media resources들은 cors에 상관없이 요청 가능하다. ex-JSONP)

 

조건 

그리고 CORB는 위 조건을 만족한다면

renderer processa cross-origin data resource를 받아와 사용하는 것을 금지한다.

(a new security feature that prevents the contents of balance.json from ever entering the memory of the renderer process memory based on its MIME type.)

 

참고)

X-Content-Type-Options: nosniff는 Microsoft에서 제안하는 확장 헤더이며 웹서버가 보내는 MIME 형식 이외의 형식으로 해석을 확장하는 것을 제한하는 크로스사이트스크립트 방어법이다.

 

즉, nosniff 옵션이 켜있다면 브라우저는 이게 HTML, XML, JSON중 하나인것으로 Content-type 종류로 인하여 헷갈리지 않고 바로 확정 지을 수 있다.

(가끔 어떤 서버는 이미지에 대해 text/html로 잘못 분류하기도 한다. 이걸 방지 가능)

 

이점 

 

CORB를 사용하면 다음과 같은 이점이 있다.

 

- Mark responses with the correct Content-Type header.

- out of sniffing by using the X-Content-Type-Options: nosniff header.

- 보안! (In browsers with Site Isolation, it can keep such data out of untrusted renderer processes entirely, helping even against side channel attacks like Spectre)

예시

그럼 예시 데모를 한번 보자.

 

https://anforowicz.github.io/xsdb-demo/index.html

 

CORB demo

Demo of CORB This page demonstrates how Cross-Origin Read Blocking (CORB) works. Please see one of the following resources for more information about CORB: Repro steps to trigger CORB: Make sure that CORB is active In Chrome M68 and later CORB is active by

anforowicz.github.io

 

<button> Add 1 <img src="https://www.chromium.org/"> </button>
<button> Add 2 <img src="https://www.w3.org/.../dummy.pdf"> </button>

 

들어가서 버튼을 한번 눌러보면

CORB 차단이 뜬다.

 

위 버튼들은 data resource(text/html, application/pdf) 요청을 cross-origin 서버에 보내는 버튼들이고, CORB에 의해 막히게 된다.

그리고 CORS와 동일하게 정상적인 요청이 왔어도 (status 200) 브라우저에서 막도록 구현된 것을 확인할수 있다.

 

 

tmi)

현재 tistory 스킨에서 영어를 읽으려니 눈이 아프다 -_-;

 

 

함께 읽으면 좋은 글)

 

CORS

https://beomy.github.io/tech/browser/cors/

 

[Browser] CORS란?

이번 포스트에서는 교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)에 대해 이야기해보도록 하겠습니다. 아래 사진과 같은 에러를 보신 적이 있으셨을 수도 있습니다.

beomy.github.io

 

reference 

 

https://chromestatus.com/feature/5629709824032768

 

https://stackoverflow.com/questions/18337630/what-is-x-content-type-options-nosniff

 

https://webhack.dynu.net/?idx=20161120.001&print=friendly 

 

https://bingbingba.tistory.com/8

 

https://developers.google.com/web/updates/2018/07/site-isolation

 

Site Isolation for web developers  |  Web  |  Google Developers

Chrome 67 on desktop has a new feature called Site Isolation* enabled by default. This article explains what Site Isolation is all about, why it’s necessary, and why web developers should be aware of it.

developers.google.com

 

반응형

+ Recent posts