.png)
리덕스를 사용하는 이유
- 리액트는 전체 데이터(state)를 루트 컴포넌트(App)에서 관리하고, 하향으로 데이터를 전달한다. (props)
- 컴포넌트끼리 교류할 때 루트 컴포넌트가 중간자 역할을 한다.
- 만약 아주 하위의 컴포넌트에서 특정 데이터가 필요한 경우, 중간 단계의 컴포넌트에서 해당 데이터가 필요하지 않더라도 그곳을 거쳐서 props를 전달해야 하므로 비효율적이다.
- 반면 리덕스는 데이터를 한 곳에서 관리하고 필요한 곳에서만 쓸 수 있도록 도와주기 때문에 효율적이다.
어플리케이션 스테이트를 반환하는 함수
예) 서점 앱을 모델링해보면,
모든 책은books라는 state로 관리되고, 여기에 관련된 함수는 Books Reducer가 된다.
현재 조회 중인 책은activeBooks라는 state로 관리되고, 관련 함수는 ActiveBooks Reducer가 된다.
(여기서 state는 리액트에서와 같이 오브젝트)
// reducers/reducer_books.js
export default function () {
return [
{ title: 'JavaScript' },
{ title: 'Harry Potter' },
{ title: 'The Dark Tower' },
{ title: 'Cosmos' },
];
}
// reducers/index.js
import { combineReducers } from 'redux';
import BooksReducer from './reducer_books';
// 전역 어플리케이션 스테이트 세팅
const rootReducer = combineReducers({
// 키는 state, 값은 reducer
books: BooksReducer,
});
export default rootReducer;
💡
combineReducers()참고
서로 다른 리듀서를 값으로 가지는 오브젝트를 받아서 하나의 리듀서로 바꿔주는 함수
리덕스에 의해 관리되는 스테이트(어플리케이션 스테이트)에 직접 접근하는 리액트 컴포넌트
react-redux 라이브러리가 필요하다.// containers/book-list.js
import React, { Component } from 'react';
// react-redux 라이브러리에서 connect 함수 import
import { connect } from 'react-redux';
class BookList extends Component {
renderList() {
return this.props.books.map((book) => {
return (
<li key={book.title} className="list-group-item">
{book.title}
</li>
);
});
}
render() {
return <ul className="list-group col-sm-4">{this.renderList()}</ul>;
}
}
function mapStateToProps(state) {
// state.books는 BooksReducer로 세팅된 값
return (books: state.books);
}
// connect()로 BookList 컴포넌트를 컨테이너로 만듦
export default connect(mapStateToProps)(BookList);
connect()는 인자로 받은 함수의 리턴값을 컴포넌트의 this.props로 세팅해줌으로써, 컨테이너를 생성한다.❗
connect()
- 클래스 기반 컴포넌트만
connect()를 사용한다.- 일반적으로 컴포넌트는 함수형 컴포넌트로 만들고 Hooks를 사용한다.
- 2019년 Hooks가 도입되면서
connect()를 거의 사용하지 않는다.
모든 리듀서로 흐르는 오브젝트
스테이트에 변화가 필요한 경우 액션을 발생시킨다.
액션을 반환하는 함수
스테이트에 변화가 일어나야 하는 경우 호출한다.
// actions/index.js
// 액션 생성자
// 타입 프로퍼티 오브젝트(액션)를 리턴한다.
export function selectBook(book) {
return {
type: 'BOOK_SELECTED',
payload: book,
};
}
type은 필수값으로 보통 대문자로 작성하며 키워드를 _로 연결한다.payload는 실제 전달할 데이터// containers/book-list.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
//
import { selectBook } from '../actions/index';
import { bindActionCreators } from 'redux';
class BookList extends Component {
...
}
function mapStateToProps(state) {
return { books: state.books };
}
function mapDispatchProps(dispatch) {
// selectBook()이 호출될 때마다 결과(액션)를 모든 리듀서에 전달한다.
return bindActionCreators({ selectBook: selectBook }, dispatch);
}
// 이제 액션 생성자도 props로 연결됨
export default connect(mapStateToProps, mapDispatchProps)(BookList);
💡
dispatch
- 액션 생성자가 리턴한 액션을 받아서 모든 리듀서에게 전달하는 함수
bindActionCreators()는 각 액션 생성자의 리턴값인 액션을dispatch의 인자로 담아주는 함수// 원래 아래처럼 dispatch 사용 const mapDispatchToProps = dispatch => ({ onIncrease: () => dispatch(increase()), onDecrease: () => dispatch(decrease()), onSetDiff: diff => dispatch(setDiff(diff)) }); // bindActionCreators()를 사용하면, const mapDispatchToProps = dispatch => bindActionCreators( { increase, decrease, setDiff }, dispatch );
dispatch를 통해 리듀서에 액션을 전달하면, 리듀서는 액션의type을 보고 그에 맞는 행동을 한다. (보통switch문으로 분기)
state와 action을 인자로 받는다.action이 발생했을 때 호출되어서 어플리케이션 스테이트를 변경하는 것... 그것이 리듀서니까...// reducers/reducer_active_book.js
// 여기서 state는 어플리케이션 스테이트가 아니라 이 리듀서에서 관리하는 state
export default function (state = null, action) {
switch (action.type) {
case 'BOOK_SELECTED':
return action.payload;
}
return state;
}
state.title = 'abc' 이런 식으로 변경을 하려고 하면 안 되고 항상 원본 오브젝트를 리턴해야 한다.export default function (state = null, action) { ... }👉
state가undefined면null로 초기화 (ES6 문법)
// reducers/index.js
import { combineReducers } from 'redux';
import BooksReducer from './reducer_books';
import ActiveBook from './reducer_active_book';
const rootReducer = combineReducers({
books: BooksReducer,
activeBook: ActiveBook,
});
export default rootReducer;
state 값은 null이다. 이를 잘못 참조하면 오류가 발생할 수 있으므로 렌더링 시에 조건을 먼저 검사한다.// containers/book-detail.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class BookDetail extends Component {
render() {
//
if (!this.props.book) {
return <div>Select a book to get started.</div>;
}
return (
<div>
<h3>Details for</h3>
<div>Title: {this.props.book.title}</div>
<div>Pages: {this.props.book.pages}</div>
</div>
);
}
}
// containers/index.js에서 세팅된 activeBook state를 book props로 매핑
function mapStateToProps(state) {
return {
book: state.activeBook,
};
}
export default connect(mapStateToProps)(BookDetail);
Redux is so SIMPLE!
🤷♀️
갈 길이 멀다....
그래도 강의 한 번 더 들으니까 흐름이 조금은 이해가 가는 느낌 🤪