Reducer 

 

CreateSlice : reducer를 만드는 것을 도와주는 역할.
CreateSlice는  name,initialState,reducers  3개를 반환
name: CreateSlice의 이름. 유일한 키값을 자동으로 생성해준다.
initialState : 초기값 설정
reducers  : 사용될 기능들을 담는 곳. 내부 객체에서 함수로 변환하여 만들게 됨.

 

const counterSlice = createSlice({
   name:”redux-toolkit”,
   initialState,
   reducers: {
     addNumber(state,action){
        state.number  = state.number + action.payload
     },
      minusNumber(state,action){
        state.number = state.number - action.payload
      } 
   }   
})

console.log(counterSlice)


export const { addNumber, minusNumber } = counterSlice.actions;
export default counterSlice.reducer;

 

기존의 redux의 리듀서에서는 전개연산자(…state)를 사용하여 기존의 리스트들을 넣으면서 바꿀 대상을 변경하여 return 해주는 방식으로 사용했었다.
하지만 redux-toolkit에서는 반복해왔던 return과 …state를 생략할 수 있으며 수정된 데이터만 설정해주면 redux-toolkit이 자동으로 값을 넣어주게 된다.
또한 컴포넌트에서 dispatch할때 리듀서에서 export한 action creator의 값을 들고와서 사용한다. 

 

 

console.log(counterSlice)

 

Store

 

redux가 버전 업데이트를 하면서 기존의 createStore를 권장하지 않음. CombineReducer을 사용하지 않게 됨

 

 

이제는 새로운 configureStore()을 권장. 

     

import { configureStore } from "@reduxjs/toolkit";
import counter from "../modules/counterSlice";

const store = configureStore({
   reducer: { counter: counter },
});

 

 

Dispatch

 

redux-toolkit은 리듀서에 있는 함수를 어떻게 불러 올 수 있을까?

 

기존 redux

dispatch({type:”ADD_TODO”, payload:{data}})

Redux-toolkit 

dispatch(addNumber.getSingleProduct(number))

Action creator의 내부 메서드에 인자 값으로 데이터(변수)를 넣게되면 자동으로 payload의 값으로 들어가게 된다.

( number == action.payload )

 

 

 

'React 내용 정리' 카테고리의 다른 글

가볍게 살펴보는 Axios  (0) 2022.12.11
useState batching & action creator & reducer  (0) 2022.12.04
React REDUX  (0) 2022.11.30
Context  (0) 2022.11.04
Memoization  (0) 2022.11.03

Axios?

 

Axios는 웹 통신 기능을 제공하는  HTTP 비동기 통신 라이브러리이다.

 

 

Axios의 특징

  • 운영 환경에 따라 브라우저의 XMLHttpRequest 객체 또는 Node.js의 http api 사용
  • Promise(ES6) API 사용
  • 요청과 응답 데이터의 변형
  • HTTP 요청 취소
  • HTTP 요청과 응답을 JSON 형태로 자동 변경

 

요청방식

  • GET - read
  • POST - create
  • PATCH,PUT - update
  • DELETE - delete

 

GET

 

// node.js에서 GET 요청으로 원격 이미지 가져오기
axios({
  method: 'get',
  url: 'http://bit.ly/2mTM3nY',
  responseType: 'stream'
})
  .then(function (response) {
    response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
  });
  
  
  //편의를 위해 지원하는 메소드의 명령어
const fetchTodos = async () => {
    const { data } = await axios.get("http://bit.ly/2mTM3nY"); // axios.get(url[, config])
  };

 

값을 가져올 때 사용하는 메서드.

url과 config를 받음.

config에는 설정할 수 있는 요소들이 굉장히 많다

 

Axios를 사용할 때에는 어떤 상황에서 path variable을 사용할지 query를 사용할지는 API 명세서를 보고 판단해야 한다.

 

path variable   =  /posts/1

query =  /posts?title=json-server&author=typicode

 

 

POST

 

// POST 요청 전송
axios({
  method: 'post',
  url: '/user/12345',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone'
  }
});
//편의를 위해 지원하는 메소드의 명령어
 const onSubmitHandler = (data) => {
    axios.post("/user/12345", data); //axios.post(url[, data[, config]])
  };

 

새로운 리소스를 생성할 때 사용하는 메서드.

post 메서드의 두 번째 인자는 본문으로 보낼 데이터를 설정한 객체 리터럴을 전달한다.

 

여러 개의 요청을 동시 수행할 경우, axios.all() 메서드를 사용

function getUserAccount() {
  return axios.get('/user/12345');
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
  .then(axios.spread(function (acct, perms) {
    // Both requests are now complete
  }));

 

 

PATCH,PUT

 

어떤 데이터를 수정하고자 서버에 요청을 보낼때 사용하는 메서드. 

 

PUT은 리소스의 모든 데이터를 수정한다.

PATCH는 요청하는 부분만 업데이트를 진행한다.

 

API명세서를 작성할때 PUT이나 PATCH 같은 용어들은 수정을하거나 삭제를 할때, 용어들을 다르게 자유자재로 써도 상관없지만 HTTP에서 정해진 규칙을 만들어 개발자들 상호간에 약속을 해놓고 사용하는 편이다.

 

 

 

 

 

참고사이트

 

[AXIOS] 📚 axios 설치 & 특징 & 문법 💯 정리

Axios 라이브러리 Axios는 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리 아다. 쉽게 말해서 백엔드랑 프론트엔드랑 통신을 쉽게하기 위해 Ajax와 더불어 사용한다. 이미

inpa.tistory.com

 

사용법 | Axios 러닝 가이드

사용법 GET 요청 axios를 사용해 GET 요청하는 방법은 다음과 같습니다. const axios = require('axios'); axios.get('/user?ID=12345') .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }) .t

yamoo9.github.io

 

'React 내용 정리' 카테고리의 다른 글

가볍게 살펴보는 Redux-Toolkit  (0) 2022.12.11
useState batching & action creator & reducer  (0) 2022.12.04
React REDUX  (0) 2022.11.30
Context  (0) 2022.11.04
Memoization  (0) 2022.11.03

 

배칭(batching)이란?

리액트에서 setState를 사용하여 상태를 업데이트할 경우, setState는 비동기적으로 작동하기 때문에 업데이트 된 상태에서 바로 반영되지 않는다.

즉, 리렌더링이 된 후에서야 state가 반영된다.

 

batching은 리액트 버전이 18로 업데이트 되면서부터 상태 업데이트(setState)를 하나도 통합하여 배치처리를 한 후 리렌더링이 진행되도록 만들어졌다.

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

React에서 setState를 사용하여 상태를 업데이트할 경우, 업데이트된 상태는 비동기적 특성 때문에 즉시 반영되지 않는다고 한다. setState는 요청에 따라 바로 리랜더링이 되는것이 아니라 변경 사항을 모아서 가장 최신의 결과를 렌더링해내게 된다.

 

이러한 비동기적인 방법을 해결하기 위해서는 함수형 업데이트를 사용하면 된다.

함수형 업데이트는 값을 자체를 전달하는 것이 아닌 업데이트된 최신의 state와 함께 함수를 전달한다(콜백함수).

이 경우 리액트는 setState()의 변경 내용을 합치는 것이 아니라, 하나의 큐(queue)에 setState()를 올리고 이것들이 호출된 순서에 따라 state를 변경한다.

 

 

 

큐의 개념과 연산

이번에는 큐의 개념과 큐에서 이용되는 연산에 대해서 정리해 보았습니다.

velog.io

 

action creator를 사용해야하는 이유

 

const PLUS_ONE = "PLUS_ONE";
const MINUS_ONE = "MINUS_ONE";

export const plusOne = () => {
  return {
    type: PLUS_ONE,
  };
};

export const minusOne = () => {
  return {
    type: MINUS_ONE,
  };
};

 

 

1. 오타같은 휴먼에러를 방지할 수 있다.

 
액션객체를 상수로 만들어서 사용하면 개발툴에서 관련 내용을 자동완성보조기능을 지원받을 수 있다.
 
 
 
2. 유지보수기능의 효율성이 굉장히 증가한다.
 
액션객체를 한 곳이 아닌 여러 곳에서 사용하는 경우 action creator만 변경하면 모든 수정사항을 변경할 수 있다.
 
 
3. 코드 가독성이 높아진다.
 
action creator가 잘 정리되어 있을 경우, 다른 개발자가 참고하기 용이하여 action들의 리스트업을 해주는 일종의 문서역할을 하게 된다.

 

 

<div>
    <button onClick={() => dispatch({ type: "PLUS_ONE" })}>+1</button>
    <button onClick={() => dispatch({ type: "MINUS_ONE" })}>-1</button>
    {num}
</div>
 
  • action 객체란 반드시 type이라는 key를 가져야 하는 객체이자 reducer로 보낼 명령이다. 
  • dispatch란 action 객체를 reducer로 보내는 전달자 함수이다 
  • dispatch는 스토어의 내장함수이며 type은 대문자로 작성하여야 한다. 
 
const initialState = {
  number: 0, //리덕스에 있는 state값이 변경되면 useSelector을 하고 있는 컴포넌트에 리렌더링이 일어남
};

const counter = (state = initialState, action) => {
  switch (action.type) {
    case PLUS_ONE: // case에서도 문자열이 아닌, 위에서 선언한 상수를 넣어줍니다.
      return {
        number: state.number + 1,
      };
    case MINUS_ONE: // case에서도 문자열이 아닌, 위에서 선언한 상수를 넣어줍니다.
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
};

export default counter;
 
  • reducer는 dispatch를 통해 전달받은 action 객체를 검사하고 조건이 일치했을 때 새로운 함수를 만들어내는 함수이다.

 

 

 

 

'React 내용 정리' 카테고리의 다른 글

가볍게 살펴보는 Redux-Toolkit  (0) 2022.12.11
가볍게 살펴보는 Axios  (0) 2022.12.11
React REDUX  (0) 2022.11.30
Context  (0) 2022.11.04
Memoization  (0) 2022.11.03

목표 : 리액트의 개념에 대해서 알아보자.

 

 

 

리덕스란?

 

리덕스는 javascript의 대표적인 상태관리 라이브러리이다.

 

Props Drilling에 의한 이슈로 유지보수가 힘들어 지는 상황을 해결하기 위해 나온 라이브러리이다.

 

Props Drilling 

Props Drilling란, props를 오로지 하위 컴포넌트로 전달하는 용도로만 쓰이는 컴포넌트를 거치면서 리액트 컴포넌트 트리의 한 부분에서 다른 부분으로 데이터를 전달하는 과정을 말한다.

Props Drilling은 컴포넌트 간에 데이터를 가장 쉽고 빠르게 전달 할 수 있다.

컴포넌트를 잘게 분할해서 prop drilling을 통해 전달하면, 코드를 실행하지 않고 정적으로 따라가는 것만으로도 어떤 데이터가 어디서 사용되는지 쉽게 파악할 수 있으며, 수정도 용이하다.

 

어떤 것이 문제가 될까?      

작은 프로젝트의 단위에서는 props drilling은 큰 효율을 볼 수 있다. 하지만 큰 프로젝트의 단위에서는 경우가 다르다.

하위 컴포넌트의 갯수가 10개라면 어떻게 될까? 컴포넌트의 갯수가 많아질수록 전달한 코드를 읽을 때 해당 props를 추적하기가 힘들어진다.

또한 필요보다 많은 props를 전달하다가, 컴포넌트를 분리하는 과정에서 필요하지 않은 props가 계속 남거나 전달되는 문제가 발생할 수도 있다.

하위컴포넌트의 수가 많아질수록 그 코드를 이해하기 어렵게 만들고 유지보수가 힘들어지는 것.

 

 

프로젝트 규모가 커질수록 관리해야 될 state는 많아질 수 밖에 없다. 컴포넌트가 많아질수록 관련된 기능 또한 복잡해지고 다양해진다.

이 모든 것들을 위해 상태관리를 하기 용이한 라이브러리를 사용하게 되었는데, 그중 하나가 리덕스라는 라이브러리이다.

 

flux Archtecture

 

리덕스는 flux 구조를 따르고 있다. 

flux 아키텍쳐에 대해 알아보기 전에 이 아키텍쳐가 왜 생겼는지에 대해 알아보자.

 

mvc 아키텍쳐의 한계

 

mvc 패턴은 Model, Controller, View 로 구성되어 있다.

흐름을 간단하게 설명해보자면,

유저가 구글을 검색 > controller는 model에 데이터를 조회나 업데이트를 요구 > model은 그에 대한 데이터제공 > 데이터를 view로  반영 > 그리고 user가 받게 되는 구조이다.

 

유저가 입력한 데이터에 의해 model이 업데이트 될 수도 있고 model의 데이터로 인해 view가 수정되는 일도 있을 것이다.

여기서 문제는 view와 model 사이의 의존성이 높아 이러한 구조 자체가 규모가 큰 프로젝트에서는 너무 복잡해지고 유지보수가 어려워진다는 것. 

 

Model과 View 상호 간 양방향 데이터 흐름의 문제

 

그리하여 선택 된 것이 단방향 데이터 흐름이다.

단방향 데이터 흐름을 가지는 구조는 데이터는 단방향으로만 흐르고, 새로운 데이터를 넣으면 처음 부터 다시 시작되는 방식으로 설계 되어있다. 이것을 우리는 FLUX 구조라 부른다. 

 

Flux 구조

 

 

Flux 구조의 가장 큰 특징은 단방향 데이터 흐름이다.

데이터의 흐름은 언제나 디스패처(Dispatcher)에서 스토어(Store)로, 스토어에서 ​뷰(View)로, 뷰에서 액션(Action)으로 다시 액션에서 디스패처로 흐른다.

Flux구조는 변화를 감지하고 화면을 업데이트하는 코드를 매번 작성해야 하는 단점이 있다.

하지만 데이터 변화에 따른 성능 저하 없이 DOM 객체 갱신이 가능하다는 장점 역시 존재한다.

 

 

Dispatcher

디스패처(Dispatcher)는 Flux 애플리케이션의 모든 데이터 흐름을 관리하는 허브 역할을 한다.

액션이 발생하면 디스패처로 메시지가 전달되고 디스패처는 디스패처에 등록된 콜백 함수를 통해 이 메시지를 스토어에 전달한다.

 

Action 

디스패처의 특정 메소드를 실행하면 스토어에 변화를 일으킬 수 있는데, 이 메소드를 호출할 때는 데이터 묶음을 인수로 전달한다. 이 때 이 데이터 묶음을 액션(Action)이라 한다.

액션생성자의 상세 역할로는 타입, 페이로드를 포함한 액션을 생성하게 되는데 그중 타입은 시스템에 정의된 액션들 중 하나이고, 액션의 예로는 MESSAGE_CREATE or MESSAGE_READ 같은 것.

 

const addTodo = text => {
  return {
    type: 'MESSAGE_CREATE', //type은 action을 묘사하는 이름을 가진 String이어야 한다.
    payload: {title: 'React Redux Tutorial!', uploader: 'minseung' }
  }
}

 

Store

스토어는 상태를 저장한다. 모든 상태 변경은 스토어에 의해 결정되며 상태 변경을 위한 요청을 스토어에 직접 할 수는 없다. 상태 변경을 위해서는 꼭 액션 생성자를 통해 디스패쳐 단계를 거친 후 액션을 보내야만 상태값 변경이 가능

 

import { createStore } from "redux";
import { combineReducers } from "redux";
import todoReducer from "../modules/todos";

const rootReducer = combineReducers({
  todoReducer,
});
const store = createStore(rootReducer);
export default store;

 

View 

뷰는 상태를 가져와서 보여주고 사용자로 부터 입력 받을 화면을 보여준다.

 

 

 

 

리덕스의 3가지 원칙

 

1.  Single source of truth

하나의 어플리케이션에는 하나의 스토어(store)만 존재한다라는 의미이다.

하나의 상태 트리만을 가지고 있기 때문에 디버깅에도 용이하다.

 

2. State is read-only

상태는 읽기전용이다. 리덕스에서  액션이라는 객체를 통해서만 상태를 변경할 수 있다. 

모든 변경 사항이 중앙 집중화되고 하나씩 발생하기 때문에 상태를 변경시킬 목적을 명확하게 알 수 있으며, 상태 변경에 대한 추적이 용이해지게 된다.

 

3. Changes are made with pure functions

변화를 일으키는 리듀서 함수는 순수한 함수여야 한다. 

리듀서는 그저 이전 상태와 액션 객체를 파라미터로 받아 다음 상태를 반환하는 순수함수이다. 

이전 상태를 변경하는 것이 아니라, 새로운 상태 객체를 생성해서 반환해야한다.

 

 

Ducks Pattern

Ducks Pattern은 리덕스에서 사용되는 actionTypes , actions, reducer들을 한 곳에서 처리하기 위해 만들어진 패턴이다.

 

Ducks Pattern은 다음 4가지를 따른다.

 

1.  MUST export default a function called reducer()

reducer는 reducer() 라는 함수로  export default 해야 한다.

 

const todoList = (state = initialState, action) => {
  switch (action.type) {
    case CREATE_TODO:
      return {
        todos: [...state.todos, action.payload],
      };
    default:
      return state;
  }
};
export default todoList;

 

2. MUST export its action creators as functions

action creator 함수는 export 해야한다

 

// action creator
export const addList = (payload) => {
  return {
    type: CREATE_TODO,
    payload,
  };
};

 

 3. MUST have action types in the form npm-module-or-app/reducer/ACTION_TYPE

action type의 네이밍 규칙을 지정한다.

 

//action value
const CREATE_TODO = "todos/CREATE_TODO";

// action creator
export const addList = (payload) => {
  return {
    type: CREATE_TODO,
    payload,
  };
};

 

 

4. MAY export its action types as UPPER_SNAKE_CASE, if an external reducer needs to listen for them, or if it is a published reusable library

action type을 UPPER_SNAKE_CASE로 작성해야한다.

3번의 코드와 동일

//action value
const CREATE_TODO = "todos/CREATE_TODO";

 

 

 

리덕스  관련  궁금했던 용어들

 

Provier :  단순한 하나의 컴포넌트이다. Provider안에 컴포넌트를 넣으면 하위 컴포넌트들이 Provider를 통해 redux store에 접근이 가능하다.

//index.js

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

 

useDispatch : 리액트 훅. 액션을 실행시킨다.  dispatch를 이용 -> 액션함수 실행 -> state저장 & 변경 

useSelector : Provider로 감싼 store에서 state 데이터를 가져올 수 있게 해준다.

import { addList } from "../redux/modules/todos";
import { nanoid } from "nanoid";

export default function AddForm() {
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");
  const todos = useSelector((state) => state.todos.todos);
  const dispatch = useDispatch();

  const submitHandler = () => {
    dispatch(
      addList({
        id: nanoid(),
        order: todos.length + 1,
        title,
        content,
        isDone: false,
      })
    );
  };

  return (
    <StAddForm>
      <form className="form_container">
        <div className="content_wrapper">
          <span className="title_field">제목 :</span>
          <input
            type="text"
            className="content_field"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
          />
        </div>
        <div className="content_wrapper">
          <span className="title_field">내용 :</span>
          <input
            type="text"
            className="content_field"
            onChange={(e) => setContent(e.target.value)}
          />
        </div>
        <button type="button" className="add_btn" onClick={submitHandler}>
          추가하기
        </button>
      </form>
    </StAddForm>
  );
}


createReducer :  기존의 state를 action을 통해 새로운 state로 변형시키고, 그 값을 store에 전달한다

combineReducers : 리듀서 함수가 모두 들어있는 객체를 받고 키 이름과 동일한 상태 객체들을 생성한다. combineReducers 에 넘겨지는 객체의 키 이름은 결과적으로 나올 상태 객체의 키 이름을 정의한다.

내부에 저장되는 데이터의 이름을 따서 키 이름을 지정하도록 하고, “reducer” 와 같은 이름은 피하도록 한다.

import { createStore } from "redux";
import { combineReducers } from "redux";
import todos from "../modules/todos";

const rootReducer = combineReducers({
  todos,
});
const store = createStore(rootReducer);
export default store;​

'React 내용 정리' 카테고리의 다른 글

가볍게 살펴보는 Axios  (0) 2022.12.11
useState batching & action creator & reducer  (0) 2022.12.04
Context  (0) 2022.11.04
Memoization  (0) 2022.11.03
고차컴포넌트(HOC, Higher Order Component)  (0) 2022.11.02

Context

 

React에서 props는 공통의 분모에서 내려받아 자식 컴포넌트에서 사용한다.


하지만 Context를 사용하면 props를 넘기지 않고도 자식컴포넌트에서 수정하거나 사용할 수 있게 만들 수 있다고 한다.

 

한번 알아 보도록 하자.

 

※ React의 생명주기 패턴과 기본 개념 없이는 이해하기 힘들 수 있습니다.



언제 context를 써야 할까


context는 React 트리 안에서 전역적이라고 볼 수 있는 데이터를 공유할 수 있도록 만든 방법이다.

그러한 데이터들을 예로 들어 로그인한 유저의 정보나 테마, 언어 등을 공유할 때 사용하는 것이 좋다

 

API

 

React.createContext

 

const MyContext = React.createContext(defaultValue);


데이터를 넣는 공간. 즉, Context 객체를 만든다.
defaultValue 매개변수는 트리 안에서 적절한 Provider를 찾지 못했을 때만 쓰이는 값이다.(Default 값으로 부여해주는 것)

 

 

import React from "react";

export const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee",
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222",
  },
};

export const ThemeContext = React.createContext(themes.dark);

 

Content.Provider

 

<MyContext.Provider value={/* 어떤 값 */}>


Context의 변화를 자식들에게 알려주는 역할
value prop을 받아서 이 값을 하위에 있는 컴포넌트에게 전달한다.

 

Content.contentType

 

import { ThemeContext } from "./ThemeContext";

 class ThemedButton extends Component {
  render() {
    let props = this.props;
    let theme = this.context;
    return (
      <button
        {...props}
        style={{ background: theme.background, color: theme.foreground }}
        onClick={props.changeTheme}
      >
        Button
      </button>
    );
  }
}

ThemedButton.contextType = ThemeContext;

export default ThemedButton;


React.createContext()로 생성한 Context 객체를 원하는 클래스의 contextType 프로퍼티로 지정할 수 있다.
이 프로퍼티를 활용해 클래스 안에서 this.context를 이용해 해당 Context의 가장 가까운 Provider를 찾아 그 값을 읽을 수 있게 된다.

 

※ 주의 - API를 사용하면 하나의 context만 구독할 수 있다



Context.Consumer

 

<MyContext.Consumer>
  {value => /* context 값을 이용한 렌더링 */}
</MyContext.Consumer>


context 변화를 구독하는 React 컴포넌트
컴포넌트를 리턴할 수 있는 함수를 자식으로 두면서 값을 그대로 사용해서 렌더할 수 있게해준다.
Context.Consumer의 자식은 함수여야 한다.

 

import React, { Component } from "react";
import { ThemeContext, themes } from "./ThemeContext";
import ThemedButton from "./ThemedButton";

export default class Example extends Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: themes.light,
    };
    this.toggleTheme = () => {
      this.setState((prev) => ({
        theme: prev.theme === themes.dark ? themes.light : themes.dark,
      }));
    };
  }
  render() {
    return (
      <div>
        <ThemeContext.Provider value={this.state.theme}>
          <ThemedButton changeTheme={this.toggleTheme} />
          <ThemeContext.Consumer>
            {(theme) => (
              <div
                style={{ background: theme.background, height: "300px" }}
              ></div>
            )}
          </ThemeContext.Consumer>
        </ThemeContext.Provider>
        <ThemedButton />
      </div>
    );
  }
}

 

위의 코드상 <Provieder> 내부에있는 <ThemeButton>을 클릭하면 배경색이 흰색에서 검은색으로 바뀌게 되는데
Consumer은 그 변화를 감지하여 내부의 div의 background를 바꿔주게 됨

 

 

결과 ↓

 

 

'React 내용 정리' 카테고리의 다른 글

useState batching & action creator & reducer  (0) 2022.12.04
React REDUX  (0) 2022.11.30
Memoization  (0) 2022.11.03
고차컴포넌트(HOC, Higher Order Component)  (0) 2022.11.02
List and Key  (0) 2022.10.28

목표 : Memoization의 개념에 대해서 알아보자!

 

 

 

memoization ?

 

메모이제이션은 컴퓨터 프로그램이 동일한 계산반복해야 할때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다.

 

예제)

 

[App.js]

import "./App.css";
import Memo from "./components/3.8 Memoization/Memo";

function App() {
  return (
    <div className="App">
      <Memo />
    </div>
  );
}

export default App;

 

[Memo.jsx]

더보기
import React, { useEffect, useState } from "react";
import Comments from "./Comments";

let commentList = [
  { title: "comment1", content: "message1", likes: 1 },
  { title: "comment2", content: "message2", likes: 1 },
  { title: "comment3", content: "message3", likes: 1 },
];

export default function Memo() {

  const [comments, setComment] = useState(commentList);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setComment((prevComments) => [
        ...prevComments,
        {
          title: `comment${prevComments.length + 1}`,
          content: `message${prevComments.length + 1}`,
          likes: 1,
        },
      ]);
    }, 1000);
    return () => {
      clearInterval(interval);
    };
  });
  return <Comments commentList={comments} />;
}

 

 

[Comments.jsx]

더보기
import React, { useCallback } from "react";
import CommentItem from "./CommentItem";
export default function Comments({ commentList }) {
  const handleChange = useCallback(() => console.log("눌림"), []);

  return (
    <div
      style={{
        display: "flex",
        gap: "10px 0",
        padding: "10px 0",
        flexDirection: "column",
        width: "fit-content",
        margin: "0 auto",
      }}
    >
      {commentList.map((comment) => (
        <CommentItem
          key={comment.title}
          title={comment.title}
          content={comment.content}
          likes={comment.likes}
          onClick={handleChange}
        />
      ))}
    </div>
  );
}

 

[CommentItem.jsx]

더보기
import React, { memo, useState, useMemo, Profiler } from "react";

import "./CommentItem.css";

function CommentItem({ title, content, likes, onClick }) {
  const [count, setCount] = useState(0);

  function onRenderCallback(
    id, // 방금 커밋된 Profiler 트리의 "id"
    phase, // "mount" (트리가 방금 마운트가 된 경우) 혹은 "update"(트리가 리렌더링된 경우)
    actualDuration, // 커밋된 업데이트를 렌더링하는데 걸린 시간
    baseDuration, // 메모이제이션 없이 하위 트리 전체를 렌더링하는데 걸리는 예상시간
    startTime, // React가 언제 해당 업데이트를 렌더링하기 시작했는지
    commitTime, // React가 해당 업데이트를 언제 커밋했는지
    interactions // 이 업데이트에 해당하는 상호작용들의 집합
  ) {
    // 렌더링 타이밍을 집합하거나 로그...
    console.log(`actualDuration(${title}:${actualDuration})`);
  }
  const handleClick = () => {
    onClick();
    setCount((prev) => prev + 1); // 리렌더링!
    alert(`${title}나 눌림!`);
  };
  const rate = useMemo(() => {
    console.log("click rate");
    return likes > 10 ? "good" : "bad";
  }, [likes]);
  return (
    <Profiler id="CommentItem" onRender={onRenderCallback}>
      <div className="CommentItem" onClick={handleClick}>
        <span>{title}</span>
        <span>{content}</span>
        <span>{likes}</span>
        <span>{rate}</span>
        <span>{count}</span>
      </div>
    </Profiler>
  );
}

export default memo(CommentItem);

 

 

예제를 살펴보면 알겠지만 Memo 컴포넌트에서는 매 초마다 commetList에 있는 객체들을 하나씩 추가해서 뿌려주고 있다.

자식 컴포넌트인 CommentItem은 부모 컴포넌트로부터 props를 전달받아 title, content likes.. 등의 값을 호출한다.

부모 컴포넌트인 Memo에서 객체가 하나씩 들어갈때마다 자식컴포넌트도 한번씩 리렌더링이 일어날 수밖에 없는 구조가 되어버린 것.

하지만 컴포넌트의 props 가 바뀌지 않은 상태에서 같은 내용을 반복해서 업데이트하는 경우에는 리렌더링하는 과정에서 많은 자원의 낭비를 할 수 밖에 없다.

 

리액트의 컴포넌트가 렌더링 되는 상황은 많겠지만 보통 state나 props의 상태가 변했을 때 혹은 부모의 컴포넌트가 렌더링 되었을 때 일어난다.

많은 분들이 코딩을 통해 프로젝트를 만들게 되면서 느끼는 거겠지만 규모가 커질수록 다른요소들에 의해 리렌더링이 일어나는 모습을 흔히 볼 수 있다.

이것은 메모리 낭비적인 측면이나 사용자의 서비스사용에 큰 불편함을 초래하는 것이기 때문에 리렌더링이 일어나지않도록 최적화를 해주는 습관이 필요하다.

 

 

리액트는 메모이제이션을 위한 세개의 api를 제공한다.

1. memo

2. useCallback

3. useMemo

 

 

 

 

memo

 

React는 먼저 컴퍼넌트를 렌더링 한 뒤, 이전 렌더된 결과와 비교하여 DOM 업데이트를 결정한다.
만약 렌더 결과가 이전과 다르다면, React는 DOM을 업데이트한다.

 

 React.memo는 고차 컴포넌트(Higher Order Component이다.

 

컴포넌트가 동일한 props로 동일한 결과를 렌더링해낸다면, React.memo를 호출하고 결과를 메모이징(Memoizing)하도록 래핑하여 경우에 따라 성능 향상을 누릴 수 있다.

 

즉, React는 컴포넌트를 렌더링하지 않고 마지막으로 렌더링된 결과를 재사용을 한다.

 

function CommentItem({ title, content, likes, onClick }) {

  return (
      <div className="CommentItem">
        <span>{title}</span>
        <span>{content}</span>
        <span>{likes}</span>
      </div>
  );
}

export default memo(CommentItem);

 

위 예제에서는 컴포넌트 자체를 memo함수의 인자로 넣어 호출한다.


그렇다면 memo를 사용함과 사용하지 않는 상태를 비교할 수 있는 방법은 어떤 것이 있을까?
성능을 측정하기 위해 Profiler를 사용해보자.

 

Profiler

<Profiler id="CommentItem" onRender={onRenderCallback}>
      <div className="CommentItem" onClick={handleClick}>
        <span>{title}</span>
        <span>{content}</span>
        <span>{likes}</span>
        <span>{rate}</span>
        <span>{count}</span>
      </div>
 </Profiler>


Profiler는 React 트리 내에 어디에나 추가될 수 있으며 트리의 특정 부분의 렌더링 비용을 계산해준다.
이는 두 가지 props를 요구하는데,
id (문자열) 와 onRender 콜백 (함수)이며 React 트리 내 컴포넌트에 업데이트가 “커밋”되면 호출한다.
자식들 중에 이미 그려졌던 애들은 반복해서 사용하기 때문에 비효율을 줄일 수 있다.

이 함수는 무엇이 렌더링 되었는지 그리고 얼마나 걸렸는지 설명하는 입력값을 받게 됨

  function onRenderCallback(
    id, // 방금 커밋된 Profiler 트리의 "id"
    phase, // "mount" (트리가 방금 마운트가 된 경우) 혹은 "update"(트리가 리렌더링된 경우)
    actualDuration, // 커밋된 업데이트를 렌더링하는데 걸린 시간
    baseDuration, // 메모이제이션 없이 하위 트리 전체를 렌더링하는데 걸리는 예상시간
    startTime, // React가 언제 해당 업데이트를 렌더링하기 시작했는지
    commitTime, // React가 해당 업데이트를 언제 커밋했는지
    interactions // 이 업데이트에 해당하는 상호작용들의 집합
  ) {
    // 렌더링 타이밍을 집합하거나 로그...
    console.log(`actualDuration(${title}:${actualDuration})`);
  }

 

※ 주의사항
Profiler는 가벼운 컴포넌트이지만 조금의 CPU와 메모리 비용을 추가하게 되기 때문에 필요할 때만 사용해야 한다.

memo함수를 사용하지 않은 화면
memo함수 사용한 화면

 

useCallback


useCallback함수는 메모이제이션된 콜백을 반환한다. 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용한다.


현재 Comments 에서는 handleChange라는 함수를 새로 만들어서 자식 컴포넌트인 CommentItem에 전해주고 있다.
그렇다면 props로 전달해주는 새로운 함수를 리렌더링해줘야 하기때문에 memo는 사용할 수 가 없게 된다.

 

export default function Comments({ commentList }) {
  
  const handleChange = () => console.log("눌림");

  return (
    <div
      style={{
        display: "flex",
        gap: "10px 0",
        padding: "10px 0",
        flexDirection: "column",
        width: "fit-content",
        margin: "0 auto",
      }}
    >
      {commentList.map((comment) => (
        <CommentItem
          key={comment.title}
          title={comment.title}
          content={comment.content}
          likes={comment.likes}
          onClick={handleChange}
        />
      ))}
    </div>
  );
}


하지만 useCallback 함수를 사용하게 되면 함수를 새로 만들지 않고 재사용하기 때문에 리렌더링을 하지 않는다.

const handleChange = useCallback(() => console.log("눌림"), []);



'React 내용 정리' 카테고리의 다른 글

React REDUX  (0) 2022.11.30
Context  (0) 2022.11.04
고차컴포넌트(HOC, Higher Order Component)  (0) 2022.11.02
List and Key  (0) 2022.10.28
조건부 렌더링  (0) 2022.10.28

고차컴포넌트란 ?

 

컴포넌트 로직을 재사용하기 위한 React의 고급 기술이다.

고차 컴포넌트(HOC)는 React API의 일부가 아니며, React의 구성적 특성에서 나오는 패턴.

 

구체적으로, 고차 컴포넌트는 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수

const EnhancedComponent = higherOrderComponent(WrappedComponent);

 

컴포넌트는 props를 UI로 변환하는 반면에, 고차 컴포넌트는 컴포넌트를 새로운 컴포넌트로 변환한다.

 

 

 

예제

 

 

[App.js]

import "./App.css";
import Button from "./components/3.7 HOC/Button";
import Input from "./components/3.7 HOC/Input";

function App() {
  return (
    <div className="App">
      <Input />
      <Button />
    </div>
  );
}

export default App;

 

[Input.js]

import React from "react";
import withLoading from "./withLoading";

function Input() {
  return <input defaultValue="Input" type="text" />;
}

export default withLoading(Input);

 

[Button.js]

import React from "react";
import withLoading from "./withLoading";

function Button() {
  return <button>Button</button>;
}

export default withLoading(Button);

 

button컴포넌트와 input컴포넌트는 일반적으로 button과 input을 리턴하는 구조이다.

여기서는 아직 input이나 button이 호출되지는 않는다.

마지막 라인에 withLoading함수를 호출하면서자기 자신의 컴포넌트를 인자로 넣어준 것을 볼 수 있다.

 

 

 

[withLoading.js]

import React, { useEffect, useState } from "react";

export default function withLoading(Component) {
  const WithLoadingComponent = (props) => {
    const [loading, setLoading] = useState(true);

    useEffect(() => {
      const Timer = setTimeout(() => {
        setLoading(false);
      }, 2000);
      return () => clearTimeout(Timer);
    });

    return loading ? <p>...Loading</p> : <Component {...props} />;
  };
  return WithLoadingComponent;
}

withLoading 함수는 component(Input,Button)를 인자로 받으면서 인자로 받은 컴포넌트를 return해준다. 

 

 return loading ? <p>...Loading</p> : <Component {...props} />;

Input컴포넌트와 Button컴포넌트는 여기서 호출이되어 실행되는 것으로 이해하면 될 듯하다.

 

리액트를 사용하면서 재사용되는 훅이 많아 커스텀 훅을 사용하듯이 고차컴포넌트 또한 재사용되는 컴포넌트 로직이 많아 사용한다고 이해하고 있다.

 

 

결과

 

'React 내용 정리' 카테고리의 다른 글

Context  (0) 2022.11.04
Memoization  (0) 2022.11.03
List and Key  (0) 2022.10.28
조건부 렌더링  (0) 2022.10.28
SUPER(PROPS)에 대해서  (0) 2022.10.23

기본 리스트 컴포넌트

 

import React from "react";

export default function List() {
  
  const todos = [
    { id: 1, text: "Drink Water" },
    { id: 2, text: "Go to Bed" },
    { id: 3, text: "Listen Lecture" },
    { id: 4, text: "Take a Shower" },
  ];
  
  const Item = (props) => {
    return <li>{props.text}</li>;
  };

  const TodoList = todos.map((todo) => <Item {...todo} key={todo.id} />);
  return TodoList;
}

 

여러개의 컴포넌트 렌더링하기

 

  • JavaScript map() 함수를 사용하여 todos 배열을 반복 실행한다.
  • Item 컴포넌트에 props로 todos의 객체들을 하나씩 넣어준다.
console.log(todo)


Item 컴포넌트의 props → id:1, text: 'Drink Water' // props = id:2, text: 'Go to Bed' ..... 

접근 방식 : props.id , props.text 

실제 사용 방식 : <li> {props.text} </li>

map함수로 배열안에 있던 객체들이 하나씩 props로 전달 된다.

<li>Drink Water</li>

<li>Go to Bed</li>

....

  • 일반적으로 컴포넌트 안에서 리스트를 렌더링한다.
const Item = (props) => {
    return <li>{props.text}</li>;
  };

 

  • Item컴포넌트는 전달받은 props를 중괄호 {}을 이용하여 사용.
  • map함수 사용하여 props를 Item 컴포넌트로 반환하고 TodoList에 저장한 후 return 한다.

 

 

 

Key

리액트는 페이지 전부를 렌더링하는 것이 아니라 기존과 비교하여 수정되는 부분만 렌더링해주는 방식이다.

이 과정에서 고유한 Key값이 없다면 모든 데이터를 비교해야 하지만, Key가 있으면 Key값만 비교하여 Key가 추가 됐는지, 삭제 됐는지만 비교하면 돼서 불필요한 렌더링을 없애준다.

즉 ,Key는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕는다. 

 

 

key로 컴포넌트 추출하기

 

위 코드의 경우 Item컴포넌트를 추출하는 과정에서 key값은 Item컴포넌트에 넣어줬다. 그 이유는 엘리먼트가 map 함수로 인해 여러개 생성되는데, 뭉쳐있는 형제 엘리먼트 사이에서 서로 고유성을 가져야하기 때문에 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정하는 것.

 

또한 key는 어튜리뷰트(속성)이기 때문에 props로 전달 되는 것은 아니니 참고하면 좋을 듯하다.

 

 

 

 

'React 내용 정리' 카테고리의 다른 글

Memoization  (0) 2022.11.03
고차컴포넌트(HOC, Higher Order Component)  (0) 2022.11.02
조건부 렌더링  (0) 2022.10.28
SUPER(PROPS)에 대해서  (0) 2022.10.23
Component(class,function) & LifeCycle  (0) 2022.10.22

+ Recent posts