redux, context, useContext

문이빈·2023년 11월 21일
0


---------------index.js---------------

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

//--------------------
import { Provider } from 'react-redux';
import rootReducer from './store';
import { legacy_createStore as createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension'; //리덕스 개발자 도구

const store = createStore(rootReducer, composeWithDevTools());
//--------------------

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={ store }>
      <App />  {/* <App /> 후손까지 store를 사용해도 된다. */}
    </Provider>
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

---------------store > index.js---------------

import {combineReducers} from 'redux'

import color from './modules/color'; 
import count from './modules/count';  
import animal from './modules/animal';  

export default combineReducers({
    color,
    //color : color
    count,

    animal
})

---------------components > Color.js---------------

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { red, green, blue, magenta } from '../store/modules/color';

const Color = () => {

    const color = useSelector(state => state.color.color)
    const dispatch = useDispatch()

    return (
        <div>
            <h1 style={{ color : color }}>컬러 : { color }</h1>
            <p>
                <button onClick={ () => dispatch(red())}>RED</button>
                <button onClick={ () => dispatch(green())}>GREEN</button>
                <button onClick={ () => dispatch(blue())}>BLUE</button>
                <button onClick={ () => dispatch(magenta())}>MAGENTA</button>
            </p>
        </div>
    );
};

export default Color;

---------------store > module > color.js---------------

// 1. 액션 생성
// 모듈이름을 앞에 붙여주므로 액션명 중복 방지
const RED = 'color/RED'
const GREEN = 'color/GREEN'
const BLUE = 'color/BLUE'
const MAGENTA = 'color/MAGENTA'
    
// 2. 액션 내보내기
export const red = () => ({ type: RED})
export const green = () => ({ type: GREEN})
export const blue = () => ({ type: BLUE})
export const magenta = () => ({ type: MAGENTA})

// 3. 초기값
const initialState = { color: 'hotpink'}


// 4.리듀서 만들기 - state, action 이라는 파라메터를 참조하여, 새로운 상태 객체를 반환한다.
// state에는 반드시 초기값을 주어야 한다.
const reducer = (state=initialState, action) => { // state : 현재 상태, action : 액션 객체
    switch(action.type){
        case RED: return {color: 'red'}
        case GREEN: return {color: 'green'}
        case BLUE: return {color: 'blue'}
        case MAGENTA: return {color: 'magenta'}
        default: return state //  default는 반드시 작성해야 한다.
    }
}
export default reducer;//component가 아니라 순순 *.js 파일이다.

---------------components > Counts.js---------------

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { decrement, increment, reset } from '../store/modules/count';

const Count = () => {

    const count = useSelector(state => state.count.count)
    const dispatch = useDispatch()

    return (
        <div>
            <h1>카운트 : { count }</h1>
            <p>
                <button onClick={ () => dispatch(increment())}>증가</button>
                <button onClick={ () => dispatch(decrement())}>감소</button>
                <button onClick={ () => dispatch(reset())}>초기화</button>
            </p>
        </div>
    );
};

export default Count;

---------------store > module > counts.js---------------

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { tiger, dog, cat, chick } from '../store/modules/animal';

const Animal = () => {
    const name = useSelector(state => state.animal.name)
    const crying = useSelector(state => state.animal.crying)
    const dispatch = useDispatch()

    return (
        <div>
            <h1>동물의 울음소리</h1>
            <h1>{ name } { crying }</h1>
            <p>
                <button onClick={ () => dispatch(tiger()) }>호랑이</button>
                <button onClick={ () => dispatch(dog()) }>강아지</button>
                <button onClick={ () => dispatch(cat()) }>고양이</button>
                <button onClick={ () => dispatch(chick()) }>병아리</button>
            </p>
        </div>
    );
};

export default Animal;

---------------components > Animal.js---------------

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { tiger, dog, cat, chick } from '../store/modules/animal';

const Animal = () => {
    const name = useSelector(state => state.animal.name)
    const crying = useSelector(state => state.animal.crying)
    const dispatch = useDispatch()

    return (
        <div>
            <h1>동물의 울음소리</h1>
            <h1>{ name } { crying }</h1>
            <p>
                <button onClick={ () => dispatch(tiger()) }>호랑이</button>
                <button onClick={ () => dispatch(dog()) }>강아지</button>
                <button onClick={ () => dispatch(cat()) }>고양이</button>
                <button onClick={ () => dispatch(chick()) }>병아리</button>
            </p>
        </div>
    );
};

export default Animal;

---------------store > module > animal.js---------------

// 1. 액션 생성
// 모듈이름을 앞에 붙여주므로 액션명 중복 방지
const TIGER = 'count/TIGER'
const DOG = 'count/DOG'
const CAT = 'count/CAT'
const CHICK = 'count/CHICK'


// 2. 액션 내보내기
export const tiger = () => ({ type: TIGER})
export const dog = () => ({ type: DOG})
export const cat = () => ({ type: CAT})
export const chick = () => ({ type: CHICK})


// 3. 초기값
const initialState = { name: '', crying: '' }


// 4.리듀서 만들기 - state, action 이라는 파라메터를 참조하여, 새로운 상태 객체를 반환한다.
// state에는 반드시 초기값을 주어야 한다.
const reducer = (state=initialState, action) => { // state : 현재 상태, action : 액션 객체
    switch(action.type){
        case TIGER :
            return {name: '호랑이', crying: '어흥'}
        case DOG :
            return {name: '강이지', crying: '멍멍'}
        case CAT :
            return {name: '고양이', crying: '야옹'}
        case CHICK :
            return {name: '병아리', crying: '삐약아아앙아아아아아아아아악!'}
        default: return state //  default는 반드시 작성해야 한다.
    }
}

export default reducer; // component가 아니라 순순 *.js 파일이다.


context

  • context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다.
  • context는 React 컴포넌트 트리 안에서 전역적(global)이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법이다.
    그러한 데이터로는 현재 로그인한 유저, 테마, 선호하는 언어 등이 있다.
  • 데이터가 필요할 때마다 props를 통해 전달할 필요가 없이 context를 이용해 공유한다.

context API를 사용하기 위해서는 Provider, Consumer, createContext가 필요하다.
① createContext : context 객체를 생성한다.
createContext 함수 호출 시 Provider와 Consumer 컴포넌트 반환한다.
initialValue는 Provider를 사용하지 않았을 때 적용될 초기값을 의미한다.
② Provider : 생성한 context를 하위 컴포넌트에게 전달하는 역할을 한다.
③ Consumer : context의 변화를 감시하는 컴포넌트이다.
설정한 상태를 불러올 때 사용한다.

useContext

  • useContext 를 사용하면 기존의 Context 사용 방식보다 더 쉽고 간단하게 Context를 사용이 가능하고,
    앞서 다뤘던 useState, useEffect와 조합해서 사용하기 쉽다는 장점이 있다.
  • useContext를 사용할 때 주의해야 할 점은 Provider에서 제공한 value가 달라진다면 useContext를 사용하고 있는 모든 컴포넌트가 리렌더링 된다는 점이다.
    따라서 useContext를 사용할 때 value 부분을 메모제이션 하는데 신경을 써야한다.

    ---------------components > Count.js---------------
import React from 'react';
import { CountContext } from '../contexts/CountContext';

const Count = () => {
    return (
        <div>
            <CountContext.Consumer>
                {
                    ({count, increment, decrement}) => (
                        <>
                            <h1>카운트 : { count }</h1>
                            <p>
                                <button onClick={ () => increment() }>증가</button>
                                <button onClick={ () => decrement() }>감소</button>
                            </p>
                        </>
                    )
                }
            </CountContext.Consumer>
        </div>
    );
};



대신 


const {count, increment, decrement} = useContext(CountContext)

return(
    <>
        <h1>카운트 : { count }</h1>
        <p>
            <button onClick={ () => increment() }>증가</button>
            <button onClick={ () => decrement() }>감소</button>
        </p>
    </>
);
이렇게 써도 된다.

export default Count;

---------------contexts > CountContext.js---------------

import React, { createContext, useState } from 'react';

export const CountContext = createContext();

const CountProvider = (props) => {

    const [count, setCount] = useState(0);

    const increment = () => {
        setCount(count + 1)
    }

    const decrement = () => {
        setCount(count - 1)
    }

    return (
        // Provider에는 value라는 props가 있으며, 이것이 데이터로 하위에 있는 컴포넌트에게 전달된다.
        <CountContext.Provider value={{count, increment, decrement}}>
        	{/* children은 부모 컴포넌트에서 자식 컴포넌트를 포함할 때 자동으로 전달된다. */}
            { props.children }
        </CountContext.Provider>
    );
};

export default CountProvider;
                   


---------------App.js---------------

import React from 'react';
import CountProvider from './contexts/CountContext';
import Count from './components/Count';
import ColorProvider from './contexts/ColorContext';
import Color from './components/Color';

const App = () => {
  return (
    <div>
      {/* 
      컨텍스트를 사용할 컴포넌트의 상위 컴포넌트에서 Provider로 감싸주어야 한다.
      Provider의 모든 하위 컴포넌트가 얼마나 깊이 위치해 있는지 
      관계 없이 컨텍스트의 데이터를 읽을 수 있다.

      <ColorProvider>
        <CountProvider>
          <Color/>
        </CountProvider>
      </ColorProvider>
    </div>
  );
};

export default App;

---------------components > Color.js---------------

import React, { useContext } from 'react';
import { ColorContext } from '../contexts/ColorContext';
import { CountContext } from '../contexts/CountContext';

const Color = () => {

    // useContext => consumer역할
    const { color, onRed, onGreen, onBlue, onMagenta } = useContext(ColorContext);
    const { count } = useContext(CountContext);

    return (
        <div>
            <h1 style={{ color : color }}>컬러 : { color }, { count }</h1>
            <p>
                <button onClick={ () => onRed() }>빨강</button>
                <button onClick={ () => onGreen() }>초록</button>
                <button onClick={ () => onBlue() }>파랑</button>
                <button onClick={ () => onMagenta() }>보라</button>
            </p>
        </div>
    );
};

export default Color;

---------------contexts > ColorContext.js---------------

import React, { createContext, useState } from 'react';

export const ColorContext = createContext();

//const ColorProvider = (props) => {
const ColorProvider = ({children}) => {

    const [color,setColor] = useState('orange')

    const onRed = () => { setColor('red') }
    const onGreen = () => { setColor('green') }
    const onBlue = () => { setColor('blue') }
    const onMagenta = () => { setColor('magenta') }

    return (
        <ColorContext.Provider value={ {color, onRed, onGreen, onBlue, onMagenta}}>
            {/* { props.children } */}
            { children }
        </ColorContext.Provider>
    );
};

export default ColorProvider;


---------------contexts > Todos.js---------------

import React, { useContext } from 'react';
import { ColorContext } from '../contexts/ColorContext';
import { CountContext } from '../contexts/CountContext';
import TodoInput from './TodoInput';
import TodoList from './TodoList';

const Todos = () => {
    
    {/* { color }, { count }2중 3중으로 쓸 수 있는 것을 알려주려고 그냥 씀 */}
    const { color } = useContext(ColorContext)
    const { count } = useContext(CountContext)

    return (
        <div>
            <h1 style={{ color : color }}>할 일 만들기, { color }, { count }</h1> 
            <TodoInput/>
            <TodoList/>
        </div>
    );
};

export default Todos;

---------------contexts > TodoContext.js---------------

import React, { createContext, useContext, useRef, useState } from 'react';

// export const TodoContext = createContext();

// 훅으로 만들기
const TodoContext = createContext();
export const useTodos = () => useContext(TodoContext);


const TodoProvider = ({ children }) => {
    
    const [todos, setTodos] = useState([
        {id: 1, text: '공부하기', isChk: false },
        {id: 2, text: '운동하기', isChk: false },
        {id: 3, text: '노래하기', isChk: true },
        {id: 4, text: '산책하기', isChk: false },
        {id: 5, text: '쇼핑하기', isChk: true }
    ]);

    const [text, setText] = useState();
    const seq = useRef(todos.length + 1);

    // 체크박스
    const onToggle = (id) => {
       const newData = todos.map(todo => todo.id === id ? { ...todo, isChk: !todo.isChk } : todo)
       setTodos(newData)
    }

    // 삭제
    const onDel = (id) => {
        setTodos(todos.filter(todo => todo.id !== id))
    }

    const onInput = (e) => {
        const { value } = e.target
        setText(value)
    }

    // 추가
    const onAdd = () => {
        setTodos([
            ...todos,
            {
                id: seq.current++,
                text: text,
                isChk: false
            }
        ])

        setText('')
    }

    return (
        <TodoContext.Provider value={{ todos, onToggle, onDel, text, onInput, onAdd }}>
            { children }
        </TodoContext.Provider>
    );
};

export default TodoProvider;

---------------contexts > TodoList.js---------------

import React from 'react';
import { useTodos } from '../contexts/TodoContext';
import TodoItem from './TodoItem';

const TodoList = () => {

    const { todos } = useTodos(); // 사용자가 만든 Hook

    return (
        <div>
            <h2>해야할 일 </h2>
            <ul>
                {
                    todos.map(todo => <TodoItem key={ todo.id } todo={ todo }/>)
                }
            </ul>
        </div>
    );
};

export default TodoList;

---------------contexts > TodoItem.js---------------

import React from 'react';
import { useTodos } from '../contexts/TodoContext';

const TodoItem = ({ todo }) => {

    const { id, text, isChk } = todo;
    const { onToggle } = useTodos();
    const { onDel } = useTodos();

    return (
        <li style={{ color : isChk ? 'tomato' : '#000' }}>
            <input type='checkbox' checked={ isChk } onChange={ () => onToggle(id) }/>
            { text }
            &nbsp; <button onClick={ () => onDel(id) }>삭제</button>
        </li>
    );
};

export default TodoItem;

---------------contexts > TodoInput.js---------------

import React from 'react';
import { useTodos } from '../contexts/TodoContext';

const TodoInput = () => {

    const {text, onInput, onAdd} = useTodos();

    const onSubmit = (e) => {
        e.preventDefault()

        if(!text) return
        onAdd(text)
    }

    return (
        <form onSubmit={ onSubmit }>
            <input type='text' value={ text } onChange={ onInput } placeholder='할 일을 입력하세요 ╰(*°▽°*)╯'/>
            <button>추가</button>
        </form>
    );
};

export default TodoInput;

0개의 댓글