본문 바로가기

React Native

React #10 - context API, recoil/zustand 찍먹

다른 컴포넌트인 Right3에서 Left3에 있는 count를 조작하고자 할때,

count는 이들의 부모인 App단에서 상태 관리가 이루어져야 한다!!!

 

 

HOW?

방법 1. prop 드릴링을 통해서 밑으로, 밑으로, 밑으로 자식에게 전달해서 Left3에게 전달하는 것이다. 

트리 구조의 뎁스가 긴 경우, 복잡해질 수 있다. 

 

 

 

방법 2. contextAPI를 사용!

contextAPI
1. context 작성
2. 자식 컴포넌트에서는 useContext를 사용해서 작성한 context를 불러온다
3. 부모 컴포넌트에서는 provider를 import해서 전달할 자식컴포넌트들을 감싸준다. 

 

1. context.jsx 를 작성한다. 

[CounterContext.jsx]

import {createContext, useState} from "react";
import PropTypes from 'prop-types';

// 1. 컨텍스트 컴포넌트 생성
const CounterContext = createContext(); //객체를 생성

CounterProvider.propTypes = {
  children : PropTypes.element
}

//3. procider 함수 작성, export
export function CounterProvider({children}){
  const [count, setCount] = useState(10); //초깃값은 10이다!
 
  //4. 상태와 상태를 관리하는 함수 정의
  const countUp = function(step){
    setCount(count + step);
  };

  const countDown = function(step){
    setCount(count - step);
  }
 
  const reset = function (){
    setCount(0);
  }

  const values = {
    state : { count },
    actions : { countUp, countDown, reset}
  }; //2개 이상 보내야해서 객체로 생성

  // 5. 컨텍스트 컴포넌트의 Provider로 자식 컴포넌트를 감싸서 리턴
  // value 속성에 전달할 컨텍스를 지정
  return(
    <CounterContext.Provider value = {values}>
      {children}
    </CounterContext.Provider>
  )
}

export default CounterContext;

1. const CounterContext = createContext() 를 통해 context 객체를 생성한다. 

이 때 createContext 를 import 해준다. 

 

2. Provider 함수 작성

>>여기서 count state를 관리해준다. 

>>countUp, countDown, reset함수를 작성한다. 

 

const values =  {

   state: {count},

   actions: {countUp, countDown, reset},

}

ㄴ values객체를 생성해준다. state는 {count} actions 에는 count함수들을 정의한다. 

ㄴ values 객체는 컨텍스트에 전달될 값이다. 

 

  return(
    <CounterContext.Provider value = {values}>
      {children}
    </CounterContext.Provider>
  )

return에는 CounterContext.Provider 태그로 감싸주고, children 객체를 사이에서 리턴

  • children prop은 Provider 컴포넌트 내부에 렌더링될 컴포넌트입니다.

state와 actions 가 들어있는 value 객체는 여기서 전달 

 

[App.jsx] : 부모 컴포넌트

import { useEffect, useState } from 'react';
import Left1 from '@components/Left1';
import Right1 from '@components/Right1';
import { CounterProvider } from '@context/CounterContext';

function App() {

  useEffect(()=>{
    console.log('# App 렌더링.');
  });
  return (
    <div id="container">
      <h1>APP - Sample</h1>
      <CounterProvider>
      <div id="grid">
          <Left1 />
          <Right1 />
      </div>
      </CounterProvider>
    </div>
  );
}

export default App;

부모 컴포넌트에서 CounterProvider로 감싸준다.

감싸주는 트리는 한개

 

 

[Left3.jsx] : 렌더링 해줘야 하는 컴포넌트 //자식 컴포넌트

import { useContext, useEffect } from 'react';
import PropTypes from 'prop-types';
import CounterContext from '@context/CounterContext';

function Left3() {
  useEffect(()=>{
    console.log('#### Left3 렌더링.');
  });

  const {state: {count}} = useContext(CounterContext);
 
  return (
    <div>
      <h1>Left3 : {count}</h1>
    </div>
  );
}

export default Left3;

1. useContext를 이용하여 CounterContext 를 읽어 state객체의 count에 객체를 넣어준다. 

 

 

[Right3.jsx] : 클릭 이벤트가 일어나는 컴포넌트  //자식 컴포넌트

import { useContext, useEffect } from 'react';
import PropTypes from 'prop-types';
import CounterContext from '@context/CounterContext';

function Right3() {
  useEffect(()=>{
    console.log('#### Right3 렌더링.');
  });
 
  const {actions : {countUp}} = useContext(CounterContext);
  const {actions : {countDown}} = useContext(CounterContext);
  const {actions : {reset}} = useContext(CounterContext);
  return (
    <div>
      <h1>Right3</h1>
      <button onClick={() => countDown(2)}>-</button>
      <button onClick={() => reset()}>0</button>
      <button onClick={() => countUp(1)}>+</button>
    </div>
  );
}

export default Right3;

자식컴포넌트에서 동일하게 useContext로 읽어오는 작업을 해준다. 

 

 

 

 


 

글로벌 상태 관리 라이브러리 

 

recoil
-atoms.js 생성 필
1. 부모노드인 App.jsx
  • Atom: Recoil에서 상태를 저장하는 기본 단위입니다.
  • Selector: Atom의 값을 기반으로 새로운 값을 계산하는 함수입니다.
  • Provider: Atom과 Selector를 제공하는 컴포넌트입니다.
  • Consumer: Atom 또는 Selector의 값을 사용하는 컴포넌트입니다.

atom은 상태를 저장하는 기본단위

selector은 atom 값을 기반으로 새로운 값을 계산하는 함수이다. 

 

[atoms.js]

원자인 atoms 객체를 작성해줘야 한다! 

import { atom } from "recoil";

export const countState = atom({
  key: "count",
  default: 5,
});

import {atom} from "recoil";

atom은 key, default 값으로 이루어져 있다. 

 

[selectors.jsx]

import { countState } from "@recoil/atoms.js";
import { selector } from "recoil";

export const countStateKor = selector({
  key: "counterKor",
  get: ({ get }) => {
    const count = get(countState);
    return numberToKorean(count);
  },
});

function numberToKorean(number) {
  // number를 한국식 발음으로 변환하는 함수
  const koreanNumbers = [
    "영",
    "일",
    "이",
    "삼",
    "사",
    "오",
    "육",
    "칠",
    "팔",
    "구",
  ];
  const koreanUnits = ["", "십", "백", "천", "만", "억", "조", "경"]; // up to 1경 (10^16)

  // Function to convert each digit to Korean
  function digitToKorean(digit, position) {
    if (digit === 1 && position > 0) {
      // Exclude "일" (il) prefix for 1 in units place
      return "";
    }
    return koreanNumbers[digit];
  }

  // Split number into array of digits
  const digits = number.toString().split("").map(Number);

  // Reverse the array to start from the units place
  digits.reverse();

  let koreanString = "";

  // Loop through each digit and its corresponding unit
  digits.forEach((digit, index) => {
    if (digit !== 0) {
      // Exclude zero digits
      koreanString =
        digitToKorean(digit, index) + koreanUnits[index] + koreanString;
    } else if (index === 4) {
      // Include '만' for units place
      koreanString = koreanUnits[index] + koreanString;
    }
  });

  return koreanString;
}

ㄴselectors는 atom을 기반으로 새로운 값을 정의하는 함수이다. 

 

 

[App.jsx] //부모 컴포넌

import { useEffect, useState } from 'react';
import Left1 from '@components/Left1';
import Right1 from '@components/Right1';
import { RecoilRoot } from 'recoil';

function App() {
 
  useEffect(()=>{
    console.log('# App 렌더링.');
  });

  return (
    <RecoilRoot>
      <div id="container">
        <h1>App - Recoil</h1>
        <div id="grid">
          <Left1 />
          <Right1 />
        </div>
      </div>
    </RecoilRoot>
  );
}

export default App;

부모 노드에 RecoilRoot로 상태를 변화시킬 자식 컴포넌트들을 감싼다. 

 

[Left3.jsx] //자식 컴포넌트 상태 값을 getter

import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { countState } from '@recoil/atoms.js';

function Left3() {
  useEffect(()=>{
    console.log('#### Left3 렌더링.');
  });

  // getter(자동으로 구독, 리렌더링)
  const count = useRecoilValue(countState);

  return (
    <div>
      <h1>Left3 : { count }</h1>
    </div>
  );
}

export default Left3;

 

[Right3.jsx] // 자식 컴포넌트 상태 값을 setter

import { countState } from '@recoil/atoms.js';
import { useEffect } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';

function Right3() {
  useEffect(()=>{
    console.log('#### Right3 렌더링.');
  });

  // getter/setter 모두 사용(구독)
  // const [count, setCount] = useRecoilState(countState);

  const setCount = useSetRecoilState(countState);

  const countUp = function(step){
    // setCount(count + step);
    setCount(count => count + step);
  };

  /*
  let count = 5;
  function setCount(state){
    if(typeof state === 'function'){
      count = state(count);
    }else{
      count = state;
    }
  }
  */

  return (
    <div>
      <h1>Right3</h1>
      <button onClick={ () => countUp(1) }>+</button>
    </div>
  );
}

export default Right3;
 

 

getter 에서는 : useRecoilValue
setter 에서는 : useSetRecoilState
getter & setter : useSetRecoilState

 

 

 

zustand

 

[counter.mjs]

import { create } from "zustand";

const useCounterStore = create((set, get) => ({
  //객체를 리턴해야하기 때문에 괄호를 빼먹으면 안됨!!!!
  count: 5,
  //아래 두가지 방법으로 쓸 수 있는 것!
  // countUp: (step) => set({ count: get().count + step }),
  countDown: (step) => set((state) => ({ count: state.count - step })),
  countUp: (step) =>
    set((state) => {
      if (state.count % 10 == 9) {
        state.count++;
        return { count: state.count + step };
      }
      return { count: state.count + step };
    }),
}));

export default useCounterStore;

create 함수 정의한다. 

상태 값은 count, 

함수는 countDown, countUp

*함수는 두가지 방법으로 정의할 수 있다. 

1) countUp : (step) => set({count: get().count + step})

ㄴ set함수를 통해 객체를 리턴해주어야 한다. 이 때 상태값 count를 get으로 가져옴

2) countUp : (step) => set((state)=>({count:state.count+step}))

 

[Right3.jsx] //자식 컴포넌트

import useCounterStore from '@zustand/couter.mjs';
import { useEffect } from 'react';

function Right3() {
  useEffect(()=>{
    console.log('#### Right3 렌더링.');
  });

  const { countUp, countDown} = useCounterStore();


  return (
    <div>
      <h1>Right3</h1>
      <button onClick={ () => {countUp(1)} }>+</button>
      <button onClick={ () => {countDown(1)} }>-</button>
    </div>
  );
}

export default Right3;

 

[Left3.jsx]  //자식 컴포넌트

import useCounterStore from '@zustand/couter.mjs';
import { useEffect } from 'react';

function Left3() {
  useEffect(()=>{
    console.log('#### Left3 렌더링.');
  });

  const {count, countUp, countDown} = useCounterStore();

  return (
    <div>
      <h1>Left3 : {count}</h1>
    </div>
  );
}

export default Left3;

ㄴ 자식컴포넌트에서는 counter.mjs 에 정의했던 함수를 import 해온다!