redux는 스토어를 사용.
state
1. 하나의 애플리케이션은 하나의 스토어가 있다.
2. 상태는 읽기 전용이다.
3. 변화를 일으키는 함수 리듀서를 사용해서 이전의 상태를 절대로 변경하지 않고 새로운 상태객체를 만들어서 반환한다.
똑같은 파라미터로 호출된 리듀서 함수는 똑같은 결과값을 나타내야한다.
new Date() / Math.random() / axios.get <=== 이 아이들은 똑같지 않아서 사용 X
reducer()
createStore()
store.getState()
store.subscribe()
store.dispatch()
learn-redux
npm install redux
components/redux_exercise.js
import { createStore } from "redux";
// 1. 상태 초기값 설정
// 2. 액션 타입 선언
// 3. 액션생성함수 정의
// 4. reducer 정의
// 5. store 생성
// 1. 리덕스에서 관리할 상태 정의
const initialState = {
counter: 0,
text: '',
list: []
}
// 2. 액션타입선언
const INCREASE = "INCREASE";
const DECREASE = "DECREASE";
const CHANGE_TEXT = "CHANGE_TEXT";
const ADD_TO_LIST = "ADD_TO_LIST";
// 3. 액션 생성함수 정의
function increase() {
return {
type: INCREASE
}
}
// 위 함수를 화살표 함수로 쓴다면
// const increase = () => {
// return {
// type: INCREASE
// }
// }
// 화살표 함수에서 return을 생략한다면
const decrease = () => ({
type: DECREASE
})
const changeText = (text) => ({
type: CHANGE_TEXT,
text
// text: text 위에처럼 text로만 적어도 됨
})
const addToList = (item) => ({
type: ADD_TO_LIST,
item
})
// 리듀서 만들기
function reducer(state = initialState, action) {
switch(action.type) {
case INCREASE:
return {
...state,
counter: state.counter + 1
}
case DECREASE:
return {
...state,
counter: state.counter - 1
}
case CHANGE_TEXT:
return {
...state,
counter: action.text
}
case ADD_TO_LIST:
return {
...state,
counter: state.list.concat(action.item)
}
default:
return state;
}
}
// 스토어 만들기
const store = createStore(reducer);
console.log(store.getState());
const listener = () => {
const state = store.getState();
console.log(state)
}
const unsubscribe = store.subscribe(listener);
// 액션 디스패치 해보기
store.dispatch(increase())
// 위에거랑 같은거임
// store.dispatch({
// type: INCREASE
// })
store.dispatch(decrease())
store.dispatch(addToList({}))
// store.dispatch(changeText('안녕하세요'))
store.dispatch(addToList({id: 1, name: "green", age: 20}))
console.log(decrease())
App.js
import './App.css';
import './components/redux_exercise'
function App() {
return (
<div className="App">
</div>
);
}
export default App;
리액트 프로젝트에 리덕스를 사용하기
액션, 리덕스
하나의 파일에 만들 수도 있음
액션 생성함수, 액셔타입, 리듀서를 하나의 파일에 작성하기
-> ducks 패턴
reducer는 export default로 내보내기
액션생성함수는 export로 내보내기
액션생성함수, 액션타입, 리듀서가 작성된 파일을 리덕스 모듈이라 한다.
루트 리듀서 만들기
하나의 프로젝트에 여러개의 리듀서가 있을 때 한 리듀서로 합쳐서 사용
합쳐린 리듀서를 루트 리듀서라고 한다.
* 리덕스에 내장함수인 combineReducers() 함수 사용
스토어 생성하기
index.js
const store = createStore(rootReducer)
리액트 프로젝트에 리덕스 사용하기
import { Provider } from 'react-recux'
<Provider store = {store}>
</Provider>
* Provider로 store를 넣어서 App을 감싸게 되면 랜더링하는 그 어떤 컴포넌트에서 리덕스 스토어로 접근할 수 있음
npm install react-redux
moduels/counter.js (상태는 number, diff이고, 이를 관리할 것이다.)
// 리덕스 모듈 만들기
// 1. 초기상태선언
// 2. 액션타입선언
// 3. 액션생성함수정의
// 4. 리듀서선언
// 1. 초기상태선언
const initalState = {
number: 0,
diff: 1
}
// 2. 액션타입
// 다른 모듈과 액션이름이 중복되는 것을 방지
const SET_DIFF = "counter/SET_DIFF"
const INCREASE = "counter/INCREASE"
const DECREASE = "counter/DECREASE";
// 3. 액션생성함수선언
// 액션생성함수는 export 키워드를 사용하여 내보내기
export const setDiff = diff => ({type:SET_DIFF, diff});
export const increase = () => ({type: INCREASE});
export const decrease = () => ({type: DECREASE});
// 4. 리듀서 선언
export default function counter(state =initalState, action) {
switch(action.type) {
case SET_DIFF:
return {
...state,
diff: action.diff
}
case INCREASE:
return {
...state,
number: state.number + state.diff
}
case DECREASE:
return {
...state,
number: state.number -state.diff
}
default:
return state;
}
}
moduels/todos.js
// 리덕스 모듈 만들기
// 1. 초기상태선언
// 2. 액션타입선언
// 3. 액션생성함수정의
// 4. 리듀서선언
// 1. 초기상태선언
const initialState = [
// {
// id:1,
// text: "예시",
// done: false
// }
]
// 2. 액션타입선언
const ADD_TODO = 'todos/ADD_TODO';
const TOGGLE_TODO = 'todos/TOGGLE_TODO';
// 3. 액션 생성함수
let nextId = 1
const addTodo = text => ({
type: ADD_TODO,
todo: {
id: nextId++,
text: text,
done: false
}
});
export const toggleTodo = id => ({
type: TOGGLE_TODO,
id
});
// 4. 리듀서 선언
export default function todos(state=initialState, action) {
switch(action.type) {
case ADD_TODO:
return state.concat(action.todo)
case TOGGLE_TODO:
return state.map(
todo =>
todo.id === action.id ? // id가 일치하면
{ ...todo, done: !todo.done } // done 값을 반전
: todo // 아니라면 그대로 둠
)
default:
return state;
}
}
modules/index.js
import { combineReducers } from 'redux'
import counter from './counter';
import todos from './todos';
const rootReducer = combineReducers({
counter,
todos
})
export default rootReducer;
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore } from 'redux'
import { Provider } from 'react-redux';
import rootReducer from './modules';
// 스토어 만들기
const store = createStore(rootReducer);
console.log(store.getState())
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</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();
카운터 구현하기
1) 프리젠테이셔널 컴포넌트
리덕스스토어에 직접적으로 접근하지 않고 필요한 값, 함수를 props로 받아와서 사용하는 컴포넌트
2) 컴테이너 컴포넌트
리덕스 스토어의 상태를 조회하거나 액션을 디스패치 할 수 있는 컴포넌트
html태그들을 사용하지 않고 프레젠테이셔널 컴포넌트를 불러와서 사용
components/CounterContainer.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increase, decrease, setDiff } from '../modules/counter'
import Counter from './Counter';
const CounterContainer = () => {
// useSelector는 리덕스 스토어의 상태를 조회하는 Hook함수
// store.getState() 할 때의 결과 동일함
const { number, diff } = useSelector(state => (state.counter))
// { number: 0, diff: 1 }
// useDispatch는 리덕스 스토어의 dispatch를 함수에서 사용할 수 있게 해주는 Hook함수
const dispatch = useDispatch();
// 각 액션을 dispatch하는 함수
// dispatcth는 상태를 업데이트 시켜주는 reduer를 실행시킴
const onIncrease = () => dispatch(increase());
const onDecrease = () => dispatch(decrease());
const onSetDiff = diff => dispatch(setDiff(diff));
return (
<Counter
number={number}
diff={diff}
onIncrease={onIncrease}
onDecrease={onDecrease}
onSetDiff={onSetDiff}
/>
);
};
export default CounterContainer;
components/Counter.js
import React from 'react';
const Counter = ({ number, diff, onIncrease, onDecrease, onSetDiff }) => {
const onChange = e => {
onSetDiff(parseInt(e.target.value))
}
return (
<div>
<h2>{number}</h2>
<div>
<input type="number" value={diff}
min="1" onChange={onChange} />
<button onClick={onIncrease}>+</button>
<button onClick={onDecrease}>-</button>
</div>
</div>
);
};
export default Counter;
App.js
import './App.css';
import CounterContainer from './components/CounterContainer';
// import './components/redux_exercise'
function App() {
return (
<div className="App">
<CounterContainer/>
</div>
);
}
export default App;
npm install redux-devtools-extension
= yarn add redux-devtools-extension
import { composeWithDevTools } from 'redux-devtools-extension'
createStore(rootReducer, composeWithDevTools())
index.js에 추가시켜주기 !
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore } from 'redux'
import { Provider } from 'react-redux';
import rootReducer from './modules';
import { composeWithDevTools } from 'redux-devtools-extension';
// 스토어 만들기
const store = createStore(rootReducer, composeWithDevTools);
console.log(store.getState())
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</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();
components/TodosContainer.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, toggleTodo } from '../modules/todos';
import Todos from './Todos';
const TodoContainer = () => {
// dispatch 함수 만들기
const todos = useSelector(state => (state.todos))
const dispatch = useDispatch();
// dispatch({
// type: "ㅇ",
// text: text,
// })
const onCreate = text => dispatch(addTodo(text));
const onToggle = id => dispatch(toggleTodo(id));
return (
<Todos todos={todos} onCreate={onCreate} onToggle={onToggle}/>
);
};
export default TodoContainer;
components/Todos.js
import React, { useState } from 'react';
const TodoList = ({todos, onToggle}) => {
return (
<ul>
{
todos.map(todo=>(
<TodoItem todo={todo} key={todo.id} onToggle={onToggle} />
))
}
</ul>
)
}
const TodoItem = ({todo, onToggle}) => {
return (
<li onClick={()=>onToggle(todo.id)}
style={{color:todo.done ? "red" : "black"}}
>{todo.text}</li>
)
}
const Todos = ({todos, onCreate, onToggle}) => {
const [ text, setText ] = useState("");
const onChange = e => {
setText(e.target.value)
}
const onSubmit= e => {
e.preventDefault(); // submit 이벤트 발생시 새로고침 방지
onCreate(text);
setText('')
}
console.log(todos)
return (
<div>
<form onSubmit={onSubmit}>
<input value={text}
placeholder="할 일을 등록하세요"
onChange={onChange}
/>
<button type="submit">등록</button>
</form>
<TodoList todos={todos} onToggle={onToggle}/>
</div>
);
};
export default Todos;
'Stack > React' 카테고리의 다른 글
[React] json-server을 이용하여 데이터 받아오기 (0) | 2022.07.15 |
---|---|
[React] Redux Middleware - 리덕스 미들웨어 (0) | 2022.07.14 |
[React] HTML / 자바스크립트로 Redux 맛보기 (0) | 2022.07.11 |
[React] git clone 한 뒤, 오류 없이 npm start 하는 방법 (0) | 2022.07.09 |
[React] 고객관리 사이트 구현 (Server) (0) | 2022.07.07 |