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