안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[리액트를 다루는 기술]의 책을 참고하여 포스팅한 개인 공부 내용입니다.
리덕스를 사용하여 리액트 애플리케이션 상태 관리하기
리액트 애플리케이션에 리덕스 적용하기
스토어를 생성하고 리액트 애플리케이션에 리덕스를 작용하는 작업은 src/index.js에서 이루어집니다.
스토어 만들기
src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { createStore } from 'redux';
import rootReducer from './modules';
const store = createStore(rootReducer);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
modules/index.js에 생성해놓았던 리듀서들을 하나로 모은 rootReducer를 불러오고 이를 통해 store를 생성하였습니다.
Provider 컴포넌트를 사용하여 프로젝트에 리덕스 적용하기
리액트 컴포넌트에서 스토어를 사용할 수 있도록 App 컴포넌트를 react-redux에서 제공하는 Provider 컴포넌트로 감싸줍니다.
이 때, store를 props로 전달해 주어야 합니다.
src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { createStore } from 'redux';
import rootReducer from './modules';
import { Provider } from 'react-redux';
const store = createStore(rootReducer);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
App 컴포넌트를 Provider로 감싸주었고 store를 props로 넘겨주었습니다.
Redux DevTools의 설치 및 적용
Redux DevTools
- 리덕스 개발자 도구
크롬 웹 스토어에서 Redux DevTools를 설치해주면 리덕스 스토어를 만드는 과정에서 아래와 같이 적용할 수 있습니다.
예시
const store = createStore(
rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSTION__()
);
하지만 패키지를 설치해 적용하면 코드가 훨씬 깔끔해집니다.
$ yarn add redux-devtools-extension
src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { createStore } from 'redux';
import rootReducer from './modules';
import { Provider } from 'react-redux';
import { devToolsEnhancer } from 'redux-devtools-extension';
const store = createStore(rootReducer, devToolsEnhancer());
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
브라우저에서 개발자 도구 - Redux를 선택하면 리덕스 개발자 도구가 잘 나오는 것을 확인할 수 있습니다.
컨테이너 컴포넌트 만들기
컴포넌트에서 리덕스 스토어에 접근하여 원하는 상태를 받아 오고, 액션도 디스패치 해주어야 합니다.
이렇듯 리덕스 스토어와 연동된 컴포넌트를 컨테이너 컴포넌트라고 합니다.
CounterContainer 만들기
src/containers/CounterContainer.js
import Counter from "../components/Counter";
const CounterContainer = () => {
return <Counter />;
}
export default CounterContainer;
해당 컴포넌트를 리덕스와 연동하려면 react-redux에서 제공해주는 connect 함수를 사용해야 합니다.
사용 예시
connect(mapStateToProps, mapDispatchToProps) (연동할 컴포넌트)
mapStateToProps : 리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기 위해 설정하는 함수
mapDispatchToProps : 액션 생성 함수를 컴포넌트의 props로 넘겨주기 위해 사용하는 함수
connect 함수를 호출하고 나면 또 다른 함수를 반환하게 됩니다.
반환된 함수의 컴포넌트를 파라미터로 넣어 주면 리덕스와 연동된 컴포넌트가 생성!!
예시
const makeContainer = connect(mapStateToProps, mapDispatchToProps)
makeContainer(타깃 컴포넌트)
containers/CounterContainer.js
import { connect } from "react-redux";
import Counter from "../components/Counter";
const CounterContainer = ({ number, increase, decrease }) => {
return (
<Counter number={number} onIncrease={increase} onDecrease={decrease} />
);
};
const mapStateToProps = state => ({
number: state.counter.number
});
const mapDispatchToProps = dispatch => ({
// 임시함수
increase: () => {
console.log('increase');
},
decrease: () => {
console.log('decrease');
}
});
export default connect(mapStateToProps, mapDispatchToProps)(CounterContainer);
컨테이너 컴포넌트를 store에 접근하여 값을 가져오거나 dispatch도 해준다고 하였습니다.
그 후 UI만을 표시하는 프레젠테이셔널 컴포넌트(Counter)에 props로 값들을 넘겨주게 되는 것이죠.
또한, 생성한 CounterContainer가 리덕스와 연동되도록 connect를 이용한 것을 확인할 수 있습니다.
mapStateToProps와 mapDispatchToProps에서 반환하는 객체 내부의 값들은 컴포넌트의 props로 전달되었고,
mapStateToProps는 state를 파라미터로 / mapDispatchToProps는 dispatch 함수를 파라미터로 받게됩니다.
App.js
import Todos from "./components/Todos";
import CounterContainer from "./containers/CounterContainer";
function App() {
return (
<div>
<CounterContainer />
<hr />
<Todos />
</div>
);
}
export default App;
이번엔 console.log 대신 액션 생성 함수를 불러와 액션 객체를 만들고 디스패치 해주겠습니다.
containers/CounterContainer.js
import { connect } from "react-redux";
import Counter from "../components/Counter";
import { increase, decrease } from "../modules/counter";
const CounterContainer = ({ number, increase, decrease }) => {
return (
<Counter number={number} onIncrease={increase} onDecrease={decrease} />
);
};
const mapStateToProps = state => ({
number: state.counter.number
});
const mapDispatchToProps = dispatch => ({
// 임시함수
increase: () => {
dispatch(increase());
},
decrease: () => {
dispatch(decrease());
}
});
export default connect(mapStateToProps, mapDispatchToProps)(CounterContainer);
counter 모듈에서 액션 생성 함수를 export 로 보내주었기 때문에 가져와 사용할 수 있습니다.
가져온 액션 생성 함수를 dispatch하여 처리했습니다.
버튼에 따라 숫자도 변경되고 Redux 탭에서도 해당 값들이 잘 표시됩니다.
mapStateToProps와 mapDispatchToProps를 미리 선언하여 사용할 수도 있지만, connect 함수 내부에서 익명 함수 형태로 사용해도 괜찮습니다.
import { connect } from "react-redux";
import Counter from "../components/Counter";
import { increase, decrease } from "../modules/counter";
const CounterContainer = ({ number, increase, decrease }) => {
return (
<Counter number={number} onIncrease={increase} onDecrease={decrease} />
);
};
export default connect(state => ({
number: state.counter.number
}),
dispatch => ({
increase: () => dispatch(increase()),
decrease: () => dispatch(decrease())
})
)(CounterContainer);
☆ 컴포넌트에서 액션을 디스패치하기 위해 각 액션 생성 함수를 호출하고 dispatch 시켜주는 작업이 다소 번거로울 수도 있습니다.
이의 경우 리덕스에서 제공하는 bindActionCreators 유틸 함수를 사용해 간편히 처리할 수 있습니다.
containers/CounterContainer.js
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import Counter from "../components/Counter";
import { increase, decrease } from "../modules/counter";
const CounterContainer = ({ number, increase, decrease }) => {
return (
<Counter number={number} onIncrease={increase} onDecrease={decrease} />
);
};
export default connect(state => ({
number: state.counter.number
}),
dispatch =>
bindActionCreators(
{
increase,
decrease
},
dispatch
)
)(CounterContainer);
이보다 더 간단하게 사용할 수도 있습니다.
import { connect } from "react-redux";
import Counter from "../components/Counter";
import { increase, decrease } from "../modules/counter";
const CounterContainer = ({ number, increase, decrease }) => {
return (
<Counter number={number} onIncrease={increase} onDecrease={decrease} />
);
};
export default connect(state => ({
number: state.counter.number
}),
{
increase,
decrease
}
)(CounterContainer);
mapDispatchToProps에 해당하는 파라미터를 함수 형태가 아닌 액션 생성 함수로 이루어진 객체 형태로 넣어주어 간편하게 처리할 수도 있습니다.
이렇게 하면, connect 함수가 내부적으로 bindActionCreators 작업을 대신해주게 됩니다.
TodoContainer 만들기
containers/TodoContainer.js
import { connect } from "react-redux";
import Todos from "../components/Todos";
import { changeInput, insert, toggle, remove } from "../modules/todos";
const TodoContainer = ({
input,
todos,
changeInput,
insert,
toggle,
remove
}) => {
return (
<Todos
input={input}
todos={todos}
onChangeInput={changeInput}
onInsert={insert}
onToggle={toggle}
onRemove={remove}
/>
)
};
export default connect(
({ todos }) => ({
input: todos.input,
todos: todos.todos
}),
{
changeInput,
insert,
toggle,
remove
}
)(TodoContainer);
todos 모듈에서 작성했던 액션 생성 함수와 상태 안에 있던 값을 컴포넌트의 props로 전달해 주었습니다.
App.js
import CounterContainer from "./containers/CounterContainer";
import TodoContainer from "./containers/TodoContainer";
function App() {
return (
<div>
<CounterContainer />
<hr />
<TodoContainer />
</div>
);
}
export default App;
components/Todos.js
const TodoItem = ({ todo, onToggle, onRemove }) => {
return(
<div>
<input
type="checkbox"
onClick={() => onToggle(todo.id)}
checked={todo.done}
readOnly={true}
/>
<span style={{ textDecoration: todo.done ? 'line-through' : '' }} >{todo.text}</span>
<button onClick={() => onRemove(todo.id)}>삭제</button>
</div>
)
};
const Todos = ({
input, // 인풋에 입력되는 텍스트
todos, // 할 일 목록이 들어있는 객체
onChangeInput,
onInsert,
onToggle,
onRemove
}) => {
const onSubmit = e => {
e.preventDefault();
onInsert(input);
onChangeInput(''); // 등록 후 인풋 초기화
};
const onChange = e => onChangeInput(e.target.value);
return(
<div>
<form onSubmit={onSubmit}> {/* 제출 방지 */}
<input value={input} onChange={onChange} />
<button type="submit">등록</button>
</form>
<div>
{todos.map(todo => (
<TodoItem
todo={todo}
key={todo.id}
onToggle={onToggle}
onRemove={onRemove}
/>
))}
</div>
</div>
);
};
export default Todos;
추가, 삭제, 체크 등이 다 잘 되는 것을 확인할 수 있습니다.
'JavaScript > React' 카테고리의 다른 글
React) 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 ④ - Hooks를 사용하여 컨테이너 컴포넌트 만들기 (0) | 2022.12.16 |
---|---|
React) 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 ③ - 리덕스 더 편하게 사용하기 (0) | 2022.12.16 |
React) 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 ① - 준비 과정 (0) | 2022.12.09 |
React) 리덕스 라이브러리 이해하기 - 리덕스의 세 가지 규칙 (2) | 2022.11.29 |
React) 리덕스 라이브러리 이해하기 - 리액트 없이 쓰는 리덕스 (리듀서, 스토어, render, 구독하기, 액션 발생시키기) (0) | 2022.11.29 |