728x90
반응형

이번 포스트에서는 리액트에서 무한스크롤 이벤트를 구현해보겠다.

 

IntersectionObserver이란?
타겟엘리먼트와 타겟의 부모 혹은 상위엘리먼트의 뷰포트가 교차되는 부분을 비동기적으로 관찰하는 API

 

먼저 리액트 앱을 설치한다.

$ npx create-react-app [프로젝트 명]

 

 

1. App.js 파일을 수정해서 Item을 여러개와 Item 요소들이 들어갈 Itemlist를 만들어준다.

App.js

import "./App.css";
import styled from "styled-components";
import { useState } from "react";

const ItemWrap = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  align-items: center;

  .Item {
    width: 350px;
    height: 300px;
    display: flex;
    flex-direction: column;
    background-color: #ffffff;
    margin: 1rem;
    box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
    border-radius: 6px;
  }
`;

function App() {
  const [itemList, setItemList] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
  return (
    <div className="App">
      <ItemWrap>
        {itemList.map((item, index) => (
          <div className="Item" key={index}>{index+1}</div>
        ))}
      </ItemWrap>
    </div>
  );
}

export default App;

 

이런 화면이 될 것이다.

 

 

2. 데이터를 받아오는 중에 로딩컴포넌트를 보여주기위해 다음을 설치하고 코드에 추가해준다.

 

또한 target State와 observer의 관찰 대상이 될 target Element를 최하단에 생성해준다.

 

$ npm i react-loading

 

App.js

import ReactLoading from "react-loading";

...

const LoaderWrap = styled.div`
  width: 100%;
  height: 80%;
  display: flex;
  justify-content: center;
  text-align: center;
  align-items: center;
`;

...

function App() {
  const [itemList, setItemList] = useState([1, 2, 3, 4, 5]); // ItemList
  const [target, setTarget] = useState(""); // target
  const [isLoding, setIsLoding] = useState(false); // isloding

  return (
    <div className="App">
      <ItemWrap>
        {itemList.map((item, index) => (
          <div className="Item" key={index}>
            {index + 1}
          </div>
        ))}
      </ItemWrap>
      {isLoding ? (
        <LoaderWrap>
          <ReactLoading type="spin" color="#A593E0" />
        </LoaderWrap>
      ) : (
        ""
      )}
      <div ref={setTarget}></div>
    </div>
  );
}
export default App;

 

3. 이제 Intersection Observer를 사용하기위해 useEffect를 선언하고 Intersection Oberver의 인자로 쓰일 함수를 선언하며, option을 지정해준다.

 

App.js

function App() {
  const [itemList, setItemList] = useState([1, 2, 3, 4, 5]); // ItemList
  const [target, setTarget] = useState(""); // target
  const [isLoding, setIsLoding] = useState(false); // isloding

  const onIntersect = async ([entry], observer) => {
    if (entry.isIntersecting && !isLoding) {
      observer.unobserve(entry.target);
      setIsLoding(true);
      // 데이터를 가져오는 부분
      setIsLoding(false);
      observer.observe(entry.target);
    }
  };

  useEffect(() => {
    let observer;
    if (target) {
      // callback 함수, option
      observer = new IntersectionObserver(onIntersect, {
        threshold: 0.4,
      });
      observer.observe(target); // 타겟 엘리먼트 지정
    }
    return () => observer && observer.disconnect();
  }, [target]);

  return (
    <div className="App">
      <ItemWrap>
        {itemList.map((item, index) => (
          <div className="Item" key={index}>
            {index + 1}
          </div>
        ))}
      </ItemWrap>
      {isLoding ? (
        <LoaderWrap>
          <ReactLoading type="spin" color="#A593E0" />
        </LoaderWrap>
      ) : (
        ""
      )}
      <div ref={setTarget}></div>
    </div>
  );
}

 

useEffect 함수 안에 보면 위에 선언한 callback함수와 option을 deps로 넣어준 것을 볼 수 있는데,

조금 더 자세히 알아보면

 


※ Intersection Oberver 첫 번째 deps : callback()

- callback : 타겟 엘리먼트가 교차되었을 때 실행할 함수

 

※ Intersection Oberver 두 번째 deps : root, rootMargin, threshold

 - root 

  • default : null, 브라우저의 viewport
  • 교차영역의 기준이 될 root 엘리먼트. observer의 대상으로 등록할 엘리먼트는 반드시 root의 하위 엘리먼트여야 한다. 

 - rootMargin

  • default : '0px 0px 0px 0px'
  • root 엘리먼트의 margin. 

 - threshold

  • default : 0
  • 0.0부터 1.0 사이의 숫자 혹은 이 숫자들로 이루어진 배열로, 타겟 엘리먼트에 대한 교차 영역 비율을 의미한다. 
  • 0.0의 경우 타겟 엘리먼트가 교차영역에 진입했을 시점이고, 1.0의 경우 타겟 엘리먼트 전체가 교차영역에 들어왔을 때의 observer를 실행하는 것을 의미한다.


 

다시 한 번 코드를 살펴보면

  useEffect(() => {
    let observer;
    if (target) {
      // callback 함수, option
      observer = new IntersectionObserver(onIntersect, {
        threshold: 0.4,
      });
      observer.observe(target); // 타겟 엘리먼트 지정
    }
    return () => observer && observer.disconnect();
  }, [target]);

target 엘리먼트로 지정한 target State가 첫 렌더링 때 생성될 것이고, 첫 렌더링 때와 이 target의 변경이 감지될 때 useEffect가 실행된다. callback 함수로는 위에 선언한 onIntersect 함수이고, option으로 threshold : 0.4를 지정했다.

 

  const onIntersect = async ([entry], observer) => {
    if (entry.isIntersecting && !isLoding) {
      observer.unobserve(entry.target);
      setIsLoding(true);
      // 데이터를 가져오는 부분
      setIsLoding(false);
      observer.observe(entry.target);
    }
  };

useEffect가 실행되고 callback함수인 onIntersection이 실행된다. target 엘리먼트가 교차지점에 들어오고

isLodingfalse일 때 target 지정을 철회하고 데이터를 가져올 때 동안 isLoding Statetrue로 만들어준다.

 

데이터가 정상적으로 들어오면 isLoding State를 다시 false로 바꿔주고, targetElement를 재지정한다.

데이터를 임의로 가져왔다고 예상하고 itemList에 추가하는 로직을 구현해보자.

 

  const onIntersect = async ([entry], observer) => {
    if (entry.isIntersecting && !isLoding) {
      observer.unobserve(entry.target);
      setIsLoding(true);
      // 데이터를 가져오는 부분
      await new Promise((resolve) => setTimeout(resolve, 2000));
      let Items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
      setItemList((itemLists) => itemLists.concat(Items));
      setIsLoding(false);
      observer.observe(entry.target);
    }
  };

 

DB를 조회할 땐 그 만큼의 시간이 걸릴테지만 지금은 로딩컴포넌트를 보여주기위해 Timeout을 2초 설정했다.

  1. target Element가 첫 렌더링 될 때와 설정한 교차지점에 진입·들어왔을 때 observer 객체가 새로 생성된다.
  2. observer 객체가 callback함수option을 가진 채로 생성된다.
  3. observer 객체가 새로 생성될 때 callback 함수가 실행되어 데이터를 가져오거나 하는 로직이 실행된다.
  4. Itemlist가 수정되어 더 많아지게되고, target Element가 재지정되어 다시 최 하단으로 설정된다.

 

전체코드

App.js

import "./App.css";
import styled from "styled-components";
import { useState } from "react";
import { useEffect } from "react";
import ReactLoading from "react-loading";
import axios from "axios";

const LoaderWrap = styled.div`
  width: 100%;
  height: 80%;
  display: flex;
  justify-content: center;
  text-align: center;
  align-items: center;
`;

const ItemWrap = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  align-items: center;

  .Item {
    width: 350px;
    height: 300px;
    display: flex;
    flex-direction: column;
    background-color: #ffffff;
    margin: 1rem;
    box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
    border-radius: 6px;
  }
`;

function App() {
  const [itemList, setItemList] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); // ItemList
  const [target, setTarget] = useState(""); // target
  const [isLoding, setIsLoding] = useState(false); // isloding

  const onIntersect = async ([entry], observer) => {
    if (entry.isIntersecting && !isLoding) {
      observer.unobserve(entry.target);
      setIsLoding(true);
      // 데이터를 가져오는 부분
      await new Promise((resolve) => setTimeout(resolve, 2000));
      let Items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
      setItemList((itemLists) => itemLists.concat(Items));
      setIsLoding(false);
      observer.observe(entry.target);
    }
  };

  useEffect(() => {
    let observer;
    if (target) {
      // callback 함수, option
      observer = new IntersectionObserver(onIntersect, {
        threshold: 0.4,
      });
      observer.observe(target); // 타겟 엘리먼트 지정
    }
    return () => observer && observer.disconnect();
  }, [target]);

  return (
    <div className="App">
      <ItemWrap>
        {itemList.map((item, index) => (
          <div className="Item" key={index}>
            {index + 1}
          </div>
        ))}
      </ItemWrap>
      {isLoding ? (
        <LoaderWrap>
          <ReactLoading type="spin" color="#A593E0" />
        </LoaderWrap>
      ) : (
        ""
      )}
      <div ref={setTarget}></div>
    </div>
  );
}

export default App;

 

실행 화면

 

 


Next.js typescript 에서는 타겟 엘리먼트를 다음과 같이 선언해주자.

<div ref={(e: any) => { setTarget(e) }}></div>

 

728x90
반응형
728x90
반응형

ReactJS에 CSS를 추가하는 방법은 크게 세가지가 있다. 먼저 css, sass, scss의 차이점 부터 알아보자.

 

HTML

<ul class='list'>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</div>

 

이러한 코드가 주어졌을 때 

CSS

.list {
  width: 100px;
  float: left;
  }
li {
  color: red;
  background: url("./image.jpg");
  }
li:last-child {
  margin-right: -10px;
  }

SCSS

.list {
  width: 100px;
  float: left;
  li {
    color: red;
    background: url("./image.jpg");
    &:last-child {
      margin-right: -10px;
    }
  }
}

SASS ( 중괄호, 세미콜론 사라짐 )

.list
  width: 100px
  float: left
  li
    color: red
    background: url("./image.jpg")
    &:last-child
      margin-right: -10px

 

CSS는 작업이 크고 고도화 될수록 코드가 길어지고 연산에 어려움이 생긴다.

 

SASS가 CSS의 태생적 한계를 보완하기 위해 아래와 같은 추가기능과 유용한 도구를 제공하면서 등장하였고

  • 변수의 사용
  • 조건문과 반복문
  • Import
  • Nesting
  • Mixin
  • Extend/Inheritance

더 넓은 범용성과 CSS의 호환성 등의 이유로 SCSS가 등장하였다.


 

이번 포스트에서는 리액트에서의 CSS 다루는 법을 다루고자 한다.

 

1. 인라인 스타일 적용법

말 그대로 태그가 선언된 그 자리에서 바로 CSS를 적용하는 방법이다. JSON(Key,Value) 형식으로 되어있는데, 

이 방법은 수정이 필요할 때 페이지나 컴포넌트를 일일이 다 조회해야되기 때문에 그닥 추천하는 방법은 아니다. 

const ReactComponent = props => {
	return (
    	<div
        	style={{
            	color: "blue",
                frontWeigt: "bold"
                }}
        >
        abcd
        </div>
    }
};

 

 

2. SCSS 파일 스타일 적용법 (기존 HTML CSS와 비슷)

 

또한 className을 사용하기위해 다음 모듈을 사용해준다.

$ npm i classnames

 

scss 파일을 사용하면 다음과 같이 변수를 지정할 수도 있다.

colors.css

 

또한 위에 만든 파일을 import 할 수도 있다.

customDiv.scss

 

그다음 사용할 곳에서 import 해주고 태그에 적용시켜주면 끝이다. scss파일에서 정의한 이름과 className을 동일하게 작성하면 된다.

App.js

 

 

 

3. 스타일 컴포넌트 적용법

먼저 styled-components 모듈을 설치한다.

$ npm i styled-components

 

타입스크립트는 아래 타입까지 추가한다.

$ npm i @types/styled-components

 

 

그 다음 컴포넌트 상단에 변수로 선언을 하고 return할 때 사용한다.

컴포넌트에 들어오는 Props의 값에 따라 유동적으로 변화를 줄 수도 있다.

 

 

컴포넌트 상단에 변수를 선언해주고 return할 때 쓰는 방법이다.

 

728x90
반응형
728x90
반응형

반복된 코드

export async function getUsers(dispatch) {
  dispatch({ type: 'GET_USERS' });
  try {
    const response = await axios.get(
      'https://jsonplaceholder.typicode.com/users'
    );
    dispatch({ type: 'GET_USERS_SUCCESS', data: response.data });
  } catch (e) {
    dispatch({ type: 'GET_USERS_ERROR', error: e });
  }
}

export async function getUser(dispatch, id) {
  dispatch({ type: 'GET_USER' });
  try {
    const response = await axios.get(
      `https://jsonplaceholder.typicode.com/users/${id}`
    );
    dispatch({ type: 'GET_USER_SUCCESS', data: response.data });
  } catch (e) {
    dispatch({ type: 'GET_USER_ERROR', error: e });
  }
}

 

이 부분을 깔끔하게 리팩토링 해보자. src 디렉토리 하위에 api.js 파일을 만들고 다음과 같이 작성한다.

 

api.js

import axios from 'axios';

export async function getUsers() {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

export async function getUser(id) {
  const response = await axios.get(
    `https://jsonplaceholder.typicode.com/users/${id}`
  );
  return response.data;
}

 

그 다음에 src 디렉터리에 asyncActionUtils.js라는 파일을 만들고 코드를 작성한다.

asyncActionUtils.js

// 이 함수는 파라미터로 액션의 타입 (예: GET_USER) 과 Promise 를 만들어주는 함수를 받아옵니다.
export default function createAsyncDispatcher(type, promiseFn) {
  // 성공, 실패에 대한 액션 타입 문자열을 준비합니다.
  const SUCCESS = `${type}_SUCCESS`;
  const ERROR = `${type}_ERROR`;

  // 새로운 함수를 만듭니다.
  // ...rest 를 사용하여 나머지 파라미터를 rest 배열에 담습니다.
  async function actionHandler(dispatch, ...rest) {
    dispatch({ type }); // 요청 시작됨
    try {
      const data = await promiseFn(...rest); // rest 배열을 spread 로 넣어줍니다.
      dispatch({
        type: SUCCESS,
        data
      }); // 성공함
    } catch (e) {
      dispatch({
        type: ERROR,
        error: e
      }); // 실패함
    }
  }

  return actionHandler; // 만든 함수를 반환합니다.
}

 

이렇게 만들어주면 Context 파일이 깔끔해진다.

UserContext.js

import React, { createContext, useReducer, useContext } from 'react';
import createAsyncDispatcher from './createAsyncDispatcher';
import * as api from './api'; // api 파일에서 내보낸 모든 함수들을 불러옴

(...)

export const getUsers = createAsyncDispatcher('GET_USERS', api.getUsers);
export const getUser = createAsyncDispatcher('GET_USER', api.getUser);

 

 

그다음 리듀서쪽 코드도 리팩토링해보자. UsersContextlodingState, success, error를 잘라내서 asyncActionUtils.js 안에 붙여넣고 initialAsyncState 객체와 createAsyncHandler 함수를 만들어서 내보낸다.

 

이제 방금만든 initialAsyncStatecreateAsyncHandler를 사용해서 코드를 고쳐보자.

 

UsersContext.js

import React, { createContext, useReducer, useContext } from 'react';
import {
  createAsyncDispatcher,
  createAsyncHandler,
  initialAsyncState
} from './asyncActionUtils';
import * as api from './api'; // api 파일에서 내보낸 모든 함수들을 불러옴

// UsersContext 에서 사용 할 기본 상태
const initialState = {
  users: initialAsyncState,
  user: initialAsyncState
};

const usersHandler = createAsyncHandler('GET_USERS', 'users');
const userHandler = createAsyncHandler('GET_USER', 'user');

// 위에서 만든 객체 / 유틸 함수들을 사용하여 리듀서 작성
function usersReducer(state, action) {
  switch (action.type) {
    case 'GET_USERS':
    case 'GET_USERS_SUCCESS':
    case 'GET_USERS_ERROR':
      return usersHandler(state, action);
    case 'GET_USER':
    case 'GET_USER_SUCCESS':
    case 'GET_USER_ERROR':
      return userHandler(state, action);
    default:
      throw new Error(`Unhanded action type: ${action.type}`);
  }
}

(...)

 

이렇게 리팩토링을 하면 반복되는 코드를 함수화해서 재사용 할 수 있다.

728x90
반응형
728x90
반응형

src 디렉터리에 UserContext.js 라는 파일을 만들어보자.

 

UsersContext.js

import React, { createContext, useReducer, useContext } from 'react';

// UsersContext 에서 사용 할 기본 상태
const initialState = {
  users: {
    loading: false,
    data: null,
    error: null
  },
  user: {
    loading: false,
    data: null,
    error: null
  }
};

// 로딩중일 때 바뀔 상태 객체
const loadingState = {
  loading: true,
  data: null,
  error: null
};

// 성공했을 때의 상태 만들어주는 함수
const success = data => ({
  loading: false,
  data,
  error: null
});

// 실패했을 때의 상태 만들어주는 함수
const error = error => ({
  loading: false,
  data: null,
  error: error
});

// 위에서 만든 객체 / 유틸 함수들을 사용하여 리듀서 작성
function usersReducer(state, action) {
  switch (action.type) {
    case 'GET_USERS':
      return {
        ...state,
        users: loadingState
      };
    case 'GET_USERS_SUCCESS':
      return {
        ...state,
        users: success(action.data)
      };
    case 'GET_USERS_ERROR':
      return {
        ...state,
        users: error(action.error)
      };
    case 'GET_USER':
      return {
        ...state,
        user: loadingState
      };
    case 'GET_USER_SUCCESS':
      return {
        ...state,
        user: success(action.data)
      };
    case 'GET_USER_ERROR':
      return {
        ...state,
        user: error(action.error)
      };
    default:
      throw new Error(`Unhanded action type: ${action.type}`);
  }
}

// State 용 Context 와 Dispatch 용 Context 따로 만들어주기
const UsersStateContext = createContext(null);
const UsersDispatchContext = createContext(null);

// 위에서 선언한 두가지 Context 들의 Provider 로 감싸주는 컴포넌트
export function UsersProvider({ children }) {
  const [state, dispatch] = useReducer(usersReducer, initialState);
  return (
    <UsersStateContext.Provider value={state}>
      <UsersDispatchContext.Provider value={dispatch}>
        {children}
      </UsersDispatchContext.Provider>
    </UsersStateContext.Provider>
  );
}

// State 를 쉽게 조회 할 수 있게 해주는 커스텀 Hook
export function useUsersState() {
  const state = useContext(UsersStateContext);
  if (!state) {
    throw new Error('Cannot find UsersProvider');
  }
  return state;
}

// Dispatch 를 쉽게 사용 할 수 있게 해주는 커스텀 Hook
export function useUsersDispatch() {
  const dispatch = useContext(UsersDispatchContext);
  if (!dispatch) {
    throw new Error('Cannot find UsersProvider');
  }
  return dispatch;
}

 

만약 id를 가지고 특정 사용자의 정보를 가져오는 API를 호출하고 싶으면 이런 형식으로 해줘야 한다.

dispatch({ type: 'GET_USER' });
try {
  const response = await getUser();
  dispatch({ type: 'GET_USER_SUCCESS', data: response.data });
} catch (e) {
  dispatch({ type: 'GET_USER_ERROR', error: e });
}

 

 

API 처리 함수 만들기

이런 작업들을 처리하는 함수를 만들어보겠다. UserContext.js 파일을 열어서 하단에 함수들을 추가한다.

 

import React, { createContext, useReducer, useContext } from 'react';
import axios from 'axios';

// (...)

export async function getUsers(dispatch) {
  dispatch({ type: 'GET_USERS' });
  try {
    const response = await axios.get(
      'https://jsonplaceholder.typicode.com/users'
    );
    dispatch({ type: 'GET_USERS_SUCCESS', data: response.data });
  } catch (e) {
    dispatch({ type: 'GET_USERS_ERROR', error: e });
  }
}

export async function getUser(dispatch, id) {
  dispatch({ type: 'GET_USER' });
  try {
    const response = await axios.get(
      `https://jsonplaceholder.typicode.com/users/${id}`
    );
    dispatch({ type: 'GET_USER_SUCCESS', data: response.data });
  } catch (e) {
    dispatch({ type: 'GET_USER_ERROR', error: e });
  }
}

 

 

Context 사용하기

이제는 만든 Context를 사용해보자.

 

App.js

import React from 'react';
import Users from './Users';
import { UsersProvider } from './UsersContext';

function App() {
  return (
    <UsersProvider>
      <Users />
    </UsersProvider>
  );
}

export default App;

 

Users.js

import React, { useState } from 'react';
import { useUsersState, useUsersDispatch, getUsers } from './UsersContext';
import User from './User';

function Users() {
  const [userId, setUserId] = useState(null);
  const state = useUsersState();
  const dispatch = useUsersDispatch();

  const { data: users, loading, error } = state.users;
  const fetchData = () => {
    getUsers(dispatch);
  };

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return <button onClick={fetchData}>불러오기</button>;

  return (
    <>
      <ul>
        {users.map(user => (
          <li
            key={user.id}
            onClick={() => setUserId(user.id)}
            style={{ cursor: 'pointer' }}
          >
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={fetchData}>다시 불러오기</button>
      {userId && <User id={userId} />}
    </>
  );
}

export default Users;

useUserState로 state를 가져오고 useUserDispatch로 dispatch를 가져온다.

요청을 시작할 때 getUsers 안에 dispatch로 호출해준다.

실행해보면 정상작동 하는것을 볼 수 있다. 이번엔 User를 바꿔보자.

 

User.js

import React, { useEffect } from 'react';
import { useUsersState, useUsersDispatch, getUser } from './UsersContext';

function User({ id }) {
  const state = useUsersState();
  const dispatch = useUsersDispatch();
  useEffect(() => {
    getUser(dispatch, id);
  }, [dispatch, id]);

  const { data: user, loading, error } = state.user;

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!user) return null;
  return (
    <div>
      <h2>{user.username}</h2>
      <p>
        <b>Email:</b> {user.email}
      </p>
    </div>
  );
}

export default User;

 

useEffect()를 사용해서 id값이 바뀔 때마다 getUser() 함수를 호출해준다. 두번째 파라미터에 props로 받아온 id값을

넣어준다.

 

 

정리

  1. Provider를 만들고 state와 dispatch를 선언하고, userReducer와 initialState를 지정해준다.
  2. state와 dispatch를 Provider로 감싼 내부에서 사용할 수 있도록 Context를 구현한다.
  3. 그 밖에 dispatch를 이용한 함수들을 구현한다.
  4. state의 공유가 필요한 곳을 provider로 감싼다.
  5. 감싸여진 컴포넌트에서 state,dispatch, 함수들을 import 해준다.
  6. 감싸여진 컴포넌트에 할당된 값이 바뀔 때마다 변환을 해주려면 useEffect를 써준다.
728x90
반응형
728x90
반응형

react-async는 우리가 지난 섹션에서 만들었던 useAsync와 비슷한 함수가 들어있는 라이브러리다.

이름은 똑같으나 사용법이 조금 다르다.

 

만약 매번 프로젝트를 만들 때 마다 직접 요청 상태 관리를 위한 커스텀 Hook을 만들기 귀찮으면

이 라이브러리를 사용하면 유용할 것이다.

 

먼저 react-async를 설치한다.

$ npm i react-async

 

import { useAsync } from "react-async"

const loadCustomer = async ({ customerId }, { signal }) => {
  const res = await fetch(`/api/customers/${customerId}`, { signal })
  if (!res.ok) throw new Error(res)
  return res.json()
}

const MyComponent = () => {
  const { data, error, isLoading } = useAsync({ promiseFn: loadCustomer, customerId: 1 })
  if (isLoading) return "Loading..."
  if (error) return `Something went wrong: ${error.message}`
  if (data)
    return (
      <div>
        <strong>Loaded some data:</strong>
        <pre>{JSON.stringify(data, null, 2)}</pre>
      </div>
    )
  return null
}

react-async의 useAsync 를 사용 할 때 파라미터로 넣는 옵션 객체에는 호출 할 함수 promiseFn 을 넣고, 파라미터도 필드 이름과 함께 costomerId를 넣어주어야 한다.

 

User 컴포넌트 전환

User 컴포넌트를 react-async의 useAsync 로 전환하자

import React from 'react';
import axios from 'axios';
import { useAsync } from 'react-async';

async function getUser({ id }) {
  const response = await axios.get(
    `https://jsonplaceholder.typicode.com/users/${id}`
  );
  return response.data;
}

function User({ id }) {
  const { data: user, error, isLoading } = useAsync({
    promiseFn: getUser,
    id,
    watch: id
  });

  if (isLoading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!user) return null;

  return (
    <div>
      <h2>{user.username}</h2>
      <p>
        <b>Email:</b> {user.email}
      </p>
    </div>
  );
}

export default User;

 

react-async를 사용할 때에는 프로미스를 반환하는 함수의 파라미터를 객체형태로 해주어야 한다.

async function getUser({ id }) {}

 

그리고 watch 값에 특정 값을 넣어주면 이 값이 바뀔 때마다 promiseFn에 넣은 함수를 다시 호출한다.

 

 

Users 컴포넌트 전환

Users.js

import React, { useState } from 'react';
import axios from 'axios';
import { useAsync } from 'react-async';
import User from './User';

async function getUsers() {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

function Users() {
  const [userId, setUserId] = useState(null);
  const { data: users, error, isLoading, reload } = useAsync({
    promiseFn: getUsers
  });

  if (isLoading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return <button onClick={reload}>불러오기</button>;
  return (
    <>
      <ul>
        {users.map(user => (
          <li
            key={user.id}
            onClick={() => setUserId(user.id)}
            style={{ cursor: 'pointer' }}
          >
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={reload}>다시 불러오기</button>
      {userId && <User id={userId} />}
    </>
  );
}

export default Users;

 

 

이전에 사용했던 skip처럼 나중에 데이터를 불러오려면 promiseFn 대신 deferFn, reload 대신 run 함수를 사용한다.

 

Users.js

import React, { useState } from 'react';
import axios from 'axios';
import { useAsync } from 'react-async';
import User from './User';

async function getUsers() {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

function Users() {
  const [userId, setUserId] = useState(null);
  const { data: users, error, isLoading, run } = useAsync({
    deferFn: getUsers
  });

  if (isLoading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return <button onClick={run}>불러오기</button>;

  return (
    <>
      <ul>
        {users.map(user => (
          <li
            key={user.id}
            onClick={() => setUserId(user.id)}
            style={{ cursor: 'pointer' }}
          >
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={run}>다시 불러오기</button>
      {userId && <User id={userId} />}
    </>
  );
}

export default Users;

 

 

정리 

  • 로그인 할 때는 최상위 컴포넌트에 쿠키를 state에 담고 상태를 관리해준다.(로그아웃, 프로필 수정)
  • 로그아웃 했을 경우(state가 null값이 될 때) 최상위에서 첫 화면 컴포넌트를 return 한다.
  • API가 필요한 경우 처음 페이지가 렌더링 될 때 useAsync로 state(리스트, 사진 등)를 만들어준다.
  • API 필요 없을 경우 처음 페이지 렌더링 될 때 useEffect를 사용해준다.
  • 추가, 수정, 삭제가 필요한 데이터의 경우 reducer를 만들어서 관리한다.(action으로 실행)
728x90
반응형
728x90
반응형

데이터를 요청해야 할 때마다 리듀서를 작성하는 것은 매우 번거롭다.

커스텀 Hook을 만들어서 요청 상태 관리 로직을 쉽게 재사용하는 방법을 알아보자.

src폴더 하위에 useAsync.js 파일을 만든다.

 

useAsync.js

import { useReducer, useEffect } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'LOADING':
      return {
        loading: true,
        data: null,
        error: null
      };
    case 'SUCCESS':
      return {
        loading: false,
        data: action.data,
        error: null
      };
    case 'ERROR':
      return {
        loading: false,
        data: null,
        error: action.error
      };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

function useAsync(callback, deps = []) {
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: false
  });

  const fetchData = async () => {
    dispatch({ type: 'LOADING' });
    try {
      const data = await callback();
      dispatch({ type: 'SUCCESS', data });
    } catch (e) {
      dispatch({ type: 'ERROR', error: e });
    }
  };

  useEffect(() => {
    fetchData();
    // eslint 설정을 다음 줄에서만 비활성화
    // eslint-disable-next-line
  }, deps);

  return [state, fetchData];
}

export default useAsync;

 

useAsync 함수는 두가지 파라미터를 받아온다. 첫 번째 파라미터는 API 요청을 시작하는 함수이고, 두 번째 파라미터는 deps 인데 이 deps 값은 해당 함수 안에서 사용하는 useEffect 의 deps 로 설정된다.

 

나중에 우리가 사용 할 비동기 함수에서 파라미터가 필요하고, 그 파라미터가 바뀔 때 새로운 데이터를 불러오고 싶을 경우에 활욜 할 수 있다.

 

이제 이 Hook을 써보자.

 

Users.js

import React from 'react';
import axios from 'axios';
import useAsync from './useAsync';

// useAsync 에서는 Promise 의 결과를 바로 data 에 담기 때문에,
// 요청을 한 이후 response 에서 data 추출하여 반환하는 함수를 따로 만들었습니다.
async function getUsers() {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

function Users() {
  const [state, refetch] = useAsync(getUsers, []);

  const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={refetch}>다시 불러오기</button>
    </>
  );
}

export default Users;

  

버튼 클릭 시 불러오기

만약 특정 버튼을 눌렀을 경우에만 API를 요청하고 싶다면 이렇게 하면 된다.

useAsync.js

import { useReducer, useEffect } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'LOADING':
      return {
        loading: true,
        data: null,
        error: null
      };
    case 'SUCCESS':
      return {
        loading: false,
        data: action.data,
        error: null
      };
    case 'ERROR':
      return {
        loading: false,
        data: null,
        error: action.error
      };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

function useAsync(callback, deps = [], skip = false) {
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: false
  });

  const fetchData = async () => {
    dispatch({ type: 'LOADING' });
    try {
      const data = await callback();
      dispatch({ type: 'SUCCESS', data });
    } catch (e) {
      dispatch({ type: 'ERROR', error: e });
    }
  };

  useEffect(() => {
    if (skip) return;
    fetchData();
    // eslint 설정을 다음 줄에서만 비활성화
    // eslint-disable-next-line
  }, deps);

  return [state, fetchData];
}

export default useAsync;

skip 파라미터의 기본 값을 false로 지정하고, 만약 이 값이 true라면 useEffect 에서 아무런 작업도 하지 않도록 설정해주었다.

 

Users.js

import React from 'react';
import axios from 'axios';
import useAsync from './useAsync';

// useAsync 에서는 Promise 의 결과를 바로 data 에 담기 때문에,
// 요청을 한 이후 response 에서 data 추출하여 반환하는 함수를 따로 만들었습니다.
async function getUsers() {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

function Users() {
  const [state, refetch] = useAsync(getUsers, [], true);

  const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return <button onClick={refetch}>불러오기</button>;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={refetch}>다시 불러오기</button>
    </>
  );
}

export default Users;

useAsync 의 세 번째 파라미터에 true를 넣어줬고, !users인 상황에 불러오기 버튼을 렌더링해주었다.

 

 

API에 파라미터가 필요한 경우

이번에는 API를 요청 할 때 파라미터가 필요한 경우에 어떻게 해야 하는지 알아보겠다.

 

User 라는 컴포넌트를 만들고, id값을 props로 받아와서

 

https://jsonplaceholder.typicode.com/users/1

이런 식으로 맨 뒤에  id를 넣어서 API를 요청할 것이다.

src 폴더에 User.js 를 생성 후 다음 코드를 작성해보자.

 

User.js

import React from 'react';
import axios from 'axios';
import useAsync from './useAsync';

async function getUser(id) {
  const response = await axios.get(
    `https://jsonplaceholder.typicode.com/users/${id}`
  );
  return response.data;
}

function User({ id }) {
  const [state] = useAsync(() => getUser(id), [id]);
  const { loading, data: user, error } = state;

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!user) return null;

  return (
    <div>
      <h2>{user.username}</h2>
      <p>
        <b>Email:</b> {user.email}
      </p>
    </div>
  );
}

export default User;

useAsync 를 사용할 때 파라미터를 포함시켜서 함수를 호출하는 새로운 함수를 만들어서 등록한다.

그리고 id가 바뀔 때마다 재호출 되도록 deps에 id를 추가한다.

 

그 다음 Users.js에서 useState를 사용하여 userId의 상태를 관리한다. 초깃값은 null이며 리스트에 항목을

클릭할 때마다 사용자의 id를 userId 값으로 설정해준다.

 

Users.js

import React, { useState } from 'react';
import axios from 'axios';
import useAsync from './useAsync';
import User from './User';

// useAsync 에서는 Promise 의 결과를 바로 data 에 담기 때문에,
// 요청을 한 이후 response 에서 data 추출하여 반환하는 함수를 따로 만들었습니다.
async function getUsers() {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

function Users() {
  const [userId, setUserId] = useState(null);
  const [state, refetch] = useAsync(getUsers, [], true);

  const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return <button onClick={refetch}>불러오기</button>;
  return (
    <>
      <ul>
        {users.map(user => (
          <li
            key={user.id}
            onClick={() => setUserId(user.id)}
            style={{ cursor: 'pointer' }}
          >
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={refetch}>다시 불러오기</button>
      {userId && <User id={userId} />}
    </>
  );
}

export default Users;

 

실행해보면

 

 

id를 클릭할 때마다 useState로 선언된 userId가 바뀌게 되고, User 컴포넌트에 userId가 할당되면서 렌더리이 되는 것을

확인할 수 있다.

728x90
반응형
728x90
반응형

이번에는 useState 대신에 useReducer로 구현해보도록 하겠다.

 

Users.js

import React, { useEffect, useReducer } from 'react';
import axios from 'axios';

function reducer(state, action) {
  switch (action.type) {
    case 'LOADING':
      return {
        loading: true,
        data: null,
        error: null
      };
    case 'SUCCESS':
      return {
        loading: false,
        data: action.data,
        error: null
      };
    case 'ERROR':
      return {
        loading: false,
        data: null,
        error: action.error
      };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

function Users() {
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: null
  });

  const fetchUsers = async () => {
    dispatch({ type: 'LOADING' });
    try {
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/users'
      );
      dispatch({ type: 'SUCCESS', data: response.data });
    } catch (e) {
      dispatch({ type: 'ERROR', error: e });
    }
  };

  useEffect(() => {
    fetchUsers();
  }, []);

  const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={fetchUsers}>다시 불러오기</button>
    </>
  );
}

export default Users;

 

useReducer 로 구현했을 때의 장점은 useState를 여러번 사용하지 않아도 되며 로직을 분리했으니 다른곳에서도 쉽게 재사용을 할 수 있다는 점이다.

728x90
반응형
728x90
반응형

API 연동의 기본

 

API 연동을 하기 위해 우선 프로젝트를 새로 만들어주자.

$ npx create-react-app react-api

 

그리고 API를 호출하기 위해 axios 라는 라이브러리를 설치한다.

$ npm i axios

 

API의 사용법은 다음과 같다.

import axios from 'axios';

axios.get('/users/1');

 

axios.post() 로 데이터를 등록 할 때에는 두 번째 파라미터에 등록하고자 하는 정보를 넣을 수 있다.

axios.post('/users', {
  username: 'blabla',
  name: 'blabla'
});

 

useState 와 useEffect 로 데이터 로딩하기

 

컴포넌트가 랜더링되는 시점에 요청을 시작하는 작업을 해보자.

요청에 대한 상태를 관리 할 때에는 다음과 같이 총 3가지 상태를 관리해주어야 한다.

  1. 요청의 결과
  2. 로딩 상태
  3. 에러

 

src 컴포넌트에 Users.js를 생성하고 다음 코드를 작성해보자.

 

Users.js

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function Users() {
  const [users, setUsers] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        // 요청이 시작 할 때에는 error 와 users 를 초기화하고
        setError(null);
        setUsers(null);
        // loading 상태를 true 로 바꿉니다.
        setLoading(true);
        const response = await axios.get(
          'https://jsonplaceholder.typicode.com/users'
        );
        setUsers(response.data); // 데이터는 response.data 안에 들어있습니다.
      } catch (e) {
        setError(e);
      }
      setLoading(false);
    };

    fetchUsers();
  }, []);

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          {user.username} ({user.name})
        </li>
      ))}
    </ul>
  );
}

export default Users;

 

사용자들이 담길 users와 loading상태 여부를 체크할 loading이랑, error 를 출력해주는 error를 선언한다.

  const [users, setUsers] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

 

그다음 주석대로 error와 users를 초기화하고 loading을 true로 만들어준 다음, 데이터가 정상적으로 수신되면 loading을 false로 바꿔준다. catch부분에 error를 담아준다.

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        // 요청이 시작 할 때에는 error 와 users 를 초기화하고
        setError(null);
        setUsers(null);
        // loading 상태를 true 로 바꿉니다.
        setLoading(true);
        const response = await axios.get(
          'https://jsonplaceholder.typicode.com/users'
        );
        setUsers(response.data); // 데이터는 response.data 안에 들어있습니다.
      } catch (e) {
        setError(e);
      }
      setLoading(false);
    };

    fetchUsers();
  }, []);

 

 

컴포넌트가 잘 작동하는지 확인하기 위해 App 컴포넌트에 User 컴포넌트를 추가하자.

 

 App.js

import React from 'react';
import Users from './Users';

function App() {
  return <Users />;
}

export default App;

 

이제 주소로 들어가보면

 

 

 

로딩중인지, 에러가 발생했는지, user가 존재하지 않은지에 대해 조건문을 설정해준다.

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;

 

 

에러 발생까지 확인해준다. 주소를 이상하게 바꿔준다.

const response = await axios.get(
  'https://jsonplaceholder.typicode.com/users/showmeerror'
);

 

확인해보면

 

 

이번엔 버튼을 눌러서  API를 재요청하는 코드를 구현해보자.

 

Users.js

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function Users() {
  const [users, setUsers] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetchUsers = async () => {
    try {
      // 요청이 시작 할 때에는 error 와 users 를 초기화하고
      setError(null);
      setUsers(null);
      // loading 상태를 true 로 바꿉니다.
      setLoading(true);
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/users'
      );
      setUsers(response.data); // 데이터는 response.data 안에 들어있습니다.
    } catch (e) {
      setError(e);
    }
    setLoading(false);
  };

  useEffect(() => {
    fetchUsers();
  }, []);

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={fetchUsers}>다시 불러오기</button>
    </>
  );
}

export default Users;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

참조

https://react.vlpt.us/integrate-api/01-basic.html

 

1. API 연동의 기본 · GitBook

1. API 연동의 기본 API 연동을 하기 위해서, 우선 프로젝트를 새로 만들어주도록 하겠습니다. $ npx create-react-app api-integrate 그리고, API 를 호출하기 위해서 axios 라는 라이브러리를 설치하세요. $ cd a

react.vlpt.us

 

728x90
반응형
728x90
반응형

리액트에서 배열이나 객체를 업데이트 해야 할 때에는 직접 수정 하면 안되고 불변성을 지켜주면서 업데이트를 해주어야 한다.

 

 

다음과 같은 객체가 있다고 생각해보자.

const state = {
  posts: [
    {
      id: 1,
      title: '제목입니다.',
      body: '내용입니다.',
      comments: [
        {
          id: 1,
          text: '와 정말 잘 읽었습니다.'
        }
      ]
    },
    {
      id: 2,
      title: '제목입니다.',
      body: '내용입니다.',
      comments: [
        {
          id: 2,
          text: '또 다른 댓글 어쩌고 저쩌고'
        }
      ]
    }
  ],
  selectedId: 1
};

 

여기서 posts 배열 안의 id 가 1 인 post 객체를 찾아서, comments 에 새로운 댓글 객체를 추가해줘야 한다면

보통 우리는 이렇게 할 것이다.

 

const nextState = {
  ...state,
  posts: state.posts.map(post =>
    post.id === 1
      ? {
          ...post,
          comments: post.comments.concat({
            id: 3,
            text: '새로운 댓글'
          })
        }
      : post
  )
};

 

 

 

방법이 잘못된건 아니지만 코드의 구조가 좀 복잡해서 한 눈에 안들어온다.

이럴 때 Immer 모듈을 사용할 수 있는데,

 

1. 먼저 설치한다.

$ sudo npm i immer

 

2. 코드의 상단부분에 import 해준다.

import produce from 'immer';

이 produce라는 함수를 쓸 때에는 첫번째 파라미터에는 수정하고 싶은 상태, 두번째 파라미터에는 어떻게 업데이트하고 싶을지 정의하는 함수를 넣어준다.

두번째 함수에서 불변성을 상관하지 않고 그냥 업데이트를 해주면 알아서 다 해준다.

 

const state = {
  number: 1,
  dontChangeMe: 2
};

const nextState = produce(state, draft => {
  draft.number += 1;
});

console.log(nextState);
// { number: 2, dontChangeMe: 2 }

 

 

이번엔 리듀서에서 Immer를 사용해보자.

 

import React, { useReducer, useMemo } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
import produce from 'immer';

function countActiveUsers(users) {
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user => user.active).length;
}

const initialState = {
  users: [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
      active: true
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
      active: false
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
      active: false
    }
  ]
};

function reducer(state, action) {
  switch (action.type) {
    case 'CREATE_USER':
      return produce(state, draft => {
        draft.users.push(action.user);
      });
    case 'TOGGLE_USER':
      return produce(state, draft => {
        const user = draft.users.find(user => user.id === action.id);
        user.active = !user.active;
      });
    case 'REMOVE_USER':
      return produce(state, draft => {
        const index = draft.users.findIndex(user => user.id === action.id);
        draft.users.splice(index, 1);
      });
    default:
      return state;
  }
}

// UserDispatch 라는 이름으로 내보내줍니다.
export const UserDispatch = React.createContext(null);

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  const { users } = state;

  const count = useMemo(() => countActiveUsers(users), [users]);
  return (
    <UserDispatch.Provider value={dispatch}>
      <CreateUser />
      <UserList users={users} />
      <div>활성사용자 수 : {count}</div>
    </UserDispatch.Provider>
  );
}

export default App;

 

업데이트를 해주는 부분인 TOGGLE type은 다소 깔끔해지지가 않았다.

 

우리가 useState를 썼을 땐 업데이트를 할 때 보통 아래와 같이 코딩했었다.

const [todo, setTodo] = useState({
  text: 'Hello',
  done: false
});

const onClick = useCallback(() => {
  setTodo(todo => ({
    ...todo,
    done: !todo.done
  }));
}, []);

 

위에서 선언한 produce를 쓸 때 원래는 두 개의 파라미터를 이용해 선언했지만, 파라미터를 한 가지만 담아서 보낼 경우에는 반환된 값이 새로운 상태가 아닌 상태를 업데이트를 해주는 함수가 된다.

 

const [todo, setTodo] = useState({
  text: 'Hello',
  done: false
});

const onClick = useCallback(() => {
  setTodo(
    produce(draft => {
      draft.done = !draft.done;
    })
  );
}, []);

 

 

단, Immer 는 JavaScript 엔진의 Proxy 라는 기능을 사용하는데, 구형 브라우저 및 react-native 같은 환경에서는 지원되지 않으므로 (Proxy 처럼 작동하지만 Proxy는 아닌) ES5 fallback 을 사용하게 됩니다. ES5 fallback 을 사용하게 되는경우는 191ms 정도로, 꽤나 느려지게 됩니다. 물론, 여전히 데이터가 별로 없다면 크게 걱정 할 필요는 없습니다.
Immer 라이브러리는 확실히 편하기 때문에, 데이터의 구조가 복잡해져서 불변성을 유지하면서 업데이트하려면 코드가 복잡해지는 상황이 온다면, 이를 사용하는 것을 권장드립니다.
다만, 무조건 사용을 하진 마시고, 가능하면 데이터의 구조가 복잡해지게 되는 것을 방지하세요. 그리고 어쩔 수 없을 때 Immer 를 사용하는것이 좋습니다. Immer 를 사용한다고 해도, 필요한곳에만 쓰고, 간단히 처리 될 수 있는 곳에서는 그냥 일반 JavaScript 로 구현하시길 바랍니다.

 

 

 

참조

http://react.vlpt.us/basic/23-immer.html

728x90
반응형
728x90
반응형

전 글에서 설명할 때 

 

1. 상위 컴포넌트에서 내보내고,

export const UserDispatch = React.createContext(null);

 

 

 

2. 감싸고,

  return (
    <UserDispatch.Provider value={dispatch}>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
      <div>활성사용자 수 : {count}</div>
    </UserDispatch.Provider>
  );

 

 

 

 

3. 하위 컴포넌트에서 import 하고,

import { UserDispatch } from './App';

 

 

4. 하위 컴포넌트에서 useContext로 가져왔다.

const User = React.memo(function User({ user }) {
  const dispatch = useContext(UserDispatch);

 

 

 

 

이 방법이 잘못된건 아니지만, 다른 방법도 있어서 설명하겠다.

 

const TodoStateContext = createContext();
const TodoDispatchContext = createContext();

export function TodoProvider({ children }) {
  const [state, dispatch] = useReducer(todoReducer, initialTodos);
  return (
    <TodoStateContext.Provider value={state}>
      <TodoDispatchContext.Provider value={dispatch}>
        {children}
      </TodoDispatchContext.Provider>
    </TodoStateContext.Provider>
  );
}

export function useTodoState() {
  return useContext(TodoStateContext);
}

export function useTodoDispatch() {
  return useContext(TodoDispatchContext);
}

먼저 상위 컴포넌트에서 위 코드가 같이 선언해준다.

 

 

 

1. 먼저 state 와 dispatch가 담길 Context를 선언해준다.

const TodoStateContext = createContext();
const TodoDispatchContext = createContext();

 

 

2. 그 다음 다른 컴포넌트에서 import 할 함수들을 정의한다.

export function useTodoState() {
  return useContext(TodoStateContext);
}

export function useTodoDispatch() {
  return useContext(TodoDispatchContext);
}

 

 

3. 그 다음 Context를 사용 할 하위 컴포넌트들을 감싼다

export function TodoProvider({ children }) {
  const [state, dispatch] = useReducer(todoReducer, initialTodos);
  return (
    <TodoStateContext.Provider value={state}>
      <TodoDispatchContext.Provider value={dispatch}>
        {children}
      </TodoDispatchContext.Provider>
    </TodoStateContext.Provider>
  );
}

 

 

4. 그 다음 하위 컴포넌트에서 import하여 사용한다.

import React from 'react';
import { useTodoState, useTodoDispatch } from '../TodoContext';

function Sample() {
  const state = useTodoState();
  const dispatch = useTodoDispatch();
  return <div>Sample</div>;
}

 

 

차이점은

- useContext를 하위컴포넌트에서 사용하냐, 

- useContext를 상위컴포넌트에서 사용하여 Context가 return되는 함수를 정의하고 하위 컴포넌트에서 이 함수를 하위

   컴포넌트에서 사용하냐 이다.

 

사용하기 나름이라 편한걸로 쓰면 될 것 같다.

 

 

728x90
반응형

'Front-End > React.js' 카테고리의 다른 글

React.js | API 연동 | 기본  (0) 2021.10.07
React.js | 입문 | Immer  (0) 2021.09.17
React.js | 입문 | Context API 전역 값 관리(1)  (0) 2021.09.17
React.js | 입문 | React.memo  (0) 2021.09.17
React.js | 입문 | useCallback  (0) 2021.09.17

+ Recent posts