본문 바로가기
프로그래밍/react

[220429] useContext + ContextAPI 로 전역상태 관리하기

by 한코코 2022. 5. 6.

context란?

React 컴포넌트 트리 안에서 전역적(global)이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법이다.

원래 리액트에서는 부모 컴포넌트에서 자식 컴포넌트에게 props로 값을 넘겨주는 방식으로 데이터를 공요할 수 있었다.

 

useContext

const value = useContext(MyContext);

 

만들 리액트 부모관계 구조도

천천히 어떤 순서로 이 구조를 만드는지 이해하는게 목표

 

 


Store/Context.jsx - export하는 방법에 따른 import하는 형태의 차이

// Context.jsx
// ES6 모듈
export default Store //바로 사용가능
export const a = 10 //객체로 나감

// NodeJS 모듈
module.exports={}

// import받는 파일
import Store, { a } from "./Store/Context";

 

 


Store/Context.jsx - 전역상태 기본형 (createContext)

여기서 조금더 나아가면 use를 사용한 Hook을 사용한다. -> 이게 커스텀훅

// Context.jsx
import { createContext } from "react"; //컴포넌트를 만들어주는 역할

export const initialState = { // 화면에 표출할 내용
  commentList: [],
  loadding: false, // input박스의 submit 값 넣을거임
  errors: null,
};

const Store = createContext();

export default Store

 

 

Comment.jsx - 전역변수 처리 해주기 (Provider)

가장 먼저 해줘야할것은 컴포넌트끼리 이어주는 전역변수 처리.

createContext로 만든 결과물을 상위에 있는 컴포넌트에 가져와 사용할거니까 Provider를 호출했다.

그 안에 있는 상태와 상태를 변경할 dispatch를 사용하려고 value에 넣었다.

state와 dispatch를 관리하는건 useReducer가 하는 기능과 같으므로 깔끔하게 useReducer를 사용하기로 함

// Comment.jsx
import Store, { initialState } from "./Store/Context";

const Comment = () => {
  return(
    <Store.Provider value={state,dispatch}>
    </Store.Provider>
  )
};

 

 

Comment.jsx - useReducer 초기값 설정

useReducer를 사용하기 위해 인자값으로 reducer함수와 초기값 initialState를 넣어주었다.

reducer 함수까지 여기다 적으면 너무 코드가 길고 더러워지기때문에 따로 파일을 빼서 작성해주기로 했다.

// Comment.jsx
// 우선순위 = (컴포넌트끼리 이어주는)전역상태 만들기
import Store, { initialState } from "./Store/Context"; // export 하는 형태에 따른 import하는 형태의 차이점
import React, { useReducer, useMemo } from "react";
import reducer, { GET_COMMENT } from "./Store/reducer"; //dispatch({type:GET_COMMNET})
import CommentLayer from "./CommentLayout";

const Comment = () => {
  // reducer는 함수라 여기에 작성하면 코드가 너무 길고 더러워짐 -> reducer 파일로 따로 뺌
  const [state, dispatch] = useReducer(reducer, initialState);

  // const a = {state,dispatch}
  // a는 새로운 객체로 생성됨
  // Comment가 실행될때마다 함수가 리렌더링됨 = a가 계속 생성되며 메모리에 적재됨
  // 그래서 상태가 달라지면 값이 달라지게끔 만들어야함 = useMemo
  // 값이기 때문에 useMemo, 함수는 useCallback
  // [state] 안에 있는 state가 바뀔때마다 useMemo가 재실행된다.
  const defaultValue = useMemo(() => ({state,dispatch}), [state]);

  return (
    // createContext로 만든 결과물을 가지고 상위에 있는 컴포넌트에 가지고갈때 사용하는 Provider
    // <Store.Provider value={}>
    // value : state, dispatch = useReducer를 사용해야한다는 말과 같음
    // 가져온 state,dispatch 객체 사용하기
    <Store.Provider value={{ defaultValue }}>
      <CommentLayer />
    </Store.Provider>
  );
};

export default Comment;

 

 

Store/reducer.jsx - reducer 함수 사용하기

reduer함수를 사용하기 위한 state와 action의 관계를 알기 쉽게 swtich 함수로 정리해주었다.

action은 사실 문자열인 type와 payload로 만들어진 객체다.

문자열은 오타내기 쉬우므로 에러를 방지하기 위해 상수 GET_COMMNET로 선언하고,

type과 payload를 사용하기 쉽게 구조분해할당으로 꺼내주었다.

// reducer.jsx
export const GET_COMMNET = 'GET_COMMENT'

const reducer = (state, action) => {
  const { type, payload } = action;
  switch (type) {
    case GET_COMMNET:
      return {
        ...state,
      };
    default:
      return state
  }
};

export default reducer;

 

 

Comment.jsx - useMemo 사용하기

state과 dispatch를 관리할 reducer 함수의 상수값을 가져왔다.

가져온 state와 dispatch 객체를 value 안에 넣어준다.

아까와 다른 점은 이 객체는 언제나 새롭게 리렌더링될 값이라는 것.

이 기능을 간단하게 해주는 함수가 바로 useMemo다.

// Comment.jsx
import Store, { initialState } from "./Store/Context";
import React, { useReducer } from "react";
import { GET_COMMNET } from "./Store/reducer"; // = dispatch({type:GET_COMMNET})

const Comment = () => {
  const [state,dispatch] = useReducer(reducer,initialState)
  
  return(
    <Store.Provider value={ {state,dispatch} }>
    </Store.Provider>
  )
};

 

 

useMemo 함수가 실행될때마다 달라질 state와 state값을 달라지게할 함수 dispatch가 있는 콜백이 들어가있다.

두번째 인자에는 배열이 있고, 배열 안에 있는 값이 달라질때마다 useMemo 함수가 재실행된다.

useMemo는 결국 새로운 {state,dispatch} 객체를 반환하며 이는 defaultValue 값이 된다.

// Comment.jsx
import Store, { initialState } from "./Store/Context";
import React, { useReducer,useMemo } from "react";
import { GET_COMMNET } from "./Store/reducer"; // = dispatch({type:GET_COMMNET})

const Comment = () => {
  const [state,dispatch] = useReducer(reducer,initialState)
  const defaultValue = useMemo(()=>({state,dispatch}),[state])
  
  return(
    <Store.Provider value={ defaultValue }>
    </Store.Provider>
  )
};

 

 

 

 

 

 

이쯤해서 다시 컴포넌트 구조 다시 살펴보기

 

 

CommentForm.jsx - submit버튼 이벤트 만들기

  1. 항상 새 컴포넌트를 사용할때 제일 먼저 생각해야할것은, 전역변수를 가져올 방법이다.
    1. 이미 Context에서 createStore로 만들어놓은 전역변수가 있으므로 useContext로 가져올 것이다.
  2. submit버튼을 눌렀을때 상태값이 변하도록 action을 주는 dispatch를 handleSubmit에 넣어놓았다.
    1. 아직 입력값을 받지 않았으므로 우선 임시데이터를 넣어서 handleSubmit함수를 먼저 완성시키겠다.
    2. 왜냐하면 데이터는 여기저기로 흐르는데 그 데이터를 만들겠다고 컴포넌트들 들쑤시고다니면 내가 뭘했는지 나중에 헷갈린다. 그러면 이제 답이 없어진다. 그러므로, 지금 붙잡고있는 내용을 먼저 완성시킨 후에 다른 함수나 컴포넌트를 손보자.
    3. dispatch안에는 action이 들어가고, action은 type과 payload로 만들어진 객체다.
    4. dispatch를 실행시키면 reducer로 넘어간다. 그러니 reducer를 관리하는 컴포넌트로 넘어가자.
import React,{ useContext } from "react";
import Store from "./Store/Context";

const CommentForm = () => {
  const { state, dispatch } = useContext(Store);
  const handelSubmit = (e) => {
    e.preventDefault();
    dispatch()
  };

  return (
    <li>
      <form onSubmit={handelSubmit}>
        <input type="text" />
        <input type="submit" value="댓글쓰기" />
      </form>
    </li>
  );
};

export default CommentForm;

 

 

Store/reducer.jsx - 임시데이터 추가하기

임시로 넣어줄 type값을 만들어주었다.

export const CREATE_COMMENT = 'CREATE_COMMENT'

 

 

CommentForm.jsx - 임시데이터인 dispatch 만들기

만들어준 type값 CREAT_COMMENT 상수를 가져와 임시데이터 dispatch를 만들어주었다.

이거 지금 적용이 안되는데? 왜지

dispatch({
    type:CREATE_COMMENT,
    paylaod:{userid:'web7722',context:'asdf',data:'2022-02-01'}
})

댓글