컴포넌트에서 리덕스 스토어에 접근하여 원하는 상태를 받아오고, 액션도 디스패치해줄 차례이다. 리덕스 스토어와 연동된 컴포넌트를 컨테이너 컴포넌트라고 부른다.
// containers/CounterContainer.js
import React from 'react';
import Counter from '../components/Counter';
const CounterContainer = () => {
return <Counter />;
};
export default CounterContainer
위 컴포넌트를 리덕스와 연동하려면, react-redux에서 제공하는 connect 함수를 사용해야 한다. connect 함수는 다음과 같이 사용한다.
connect(mapStateToProps, mapDispatchToProps)(연동할 컴포넌트)
여기서 mapStateToProps는 리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기 위해 설정하는 함수다. mapDispatchToProps는 액션 생성함수를 컴포넌트의 props로 넘겨주기 위해 사용하는 함수이다.
더 구체화하면 const makeContainer = connect(mapStateToProps, mapDispatchToProps)makeContainer(타깃 컴포넌트)
로 적을 수 있다.
connect 함수를 호출하고 나면 또 다른 함수를 반환한다. 반환된 함수에 컴포넌트를 파라미터로 넣어주면 리덕스와 연동된 컴포넌트가 만들어진다.
// containers/CounterContainer.js
import React from 'react';
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);
mapStateToProps와 mapDispatchProps에서 반환하는 객체 내부의 값들은 컴포넌트의 props로 전달된다. mapStateToProps는 state를 파라미터로 받아오며, 이 값은 현재 스토어가 지니고 있는 상태를 가리킨다.
mapDispatchToProps의 경우 store의 내장함수 dispatch를 파라미터로 받아온다. mapDispatchToProps에서는 진행 절차를 설명하기 위해 임시로 console.log를 사용하고 있다.
여기에 (CounterContainer)
를 덧붙임으로써mapStateToProps, mapDispatchToProps를 CounterContainer과 연동한다.
App에서 Counter를 CounterContainer로 교체한다.
import React from 'react';
import Todos from './components/Todos';
import CounterContainer from './containers/CounterContainer';
const App = () => {
return (
<div>
<CounterContainer />
<hr />
<Todos />
</div>
);
};
export default App;
// containers/CounterContainer.js
import React from 'react';
import {connect} from 'react-redux';
import Counter from '../components/Counter';
// 액션 생성함수 increase, decrease
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);
console.log()
대신 액션 생성함수(import {increase, decrease} from '../module/counter'
)를 불러와서 액션 객체를 만들고 디스패치를 했다.
//container/CounterContainer.js
import React from "react";
import { connect } from "react-redux";
import Counter from "../components/Counter";
// 액션 생성함수 increase, decrease
import { increase, decrease } from "../modules/counter";
const CounterContainer = ({ number, increase, decrease }) => {
return (
<Counter number={number} onIncrease={increase} onDecrease={decrease} />
);
};1
export default connect(
(state) => ({
number: state.counter.number,
}),
(dispatch) => ({
increase: () => dispatch(increase()),
decrease: () => dispatch(decrease()),
})
)(CounterContainer);
// containers/CounterContainer.js
import React from "react";
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);
// containers/CounterContainer.js
import React from 'react';
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);
위와 같이 두 번째 파라미터를 객체 형태로 넣어주면, connect 함수가 내부적으로 bindActionCreators 작업을 대신해준다.
check 여기서 말하는 두 번째 파라미터가 뭔지
// container/TodosContainer.js
import React from 'react';
import {connect} from 'react-redux';
import {changeInput, insert, toggle, remove} from '../modules/todos';
import Todos from '../components/Todos';
const TodosContainer = ({
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,
},
)(TodosContainer);
todos 모듈에서 작성했던 액션 생성함수와 상태 안에 있던 값을 컴포넌트의 props로 전달했다.
컨테이너 컴포넌트를 다 만든 후에 App 컴포넌트에서 보여주던 Todos 컴포넌트를 TodosContainer 컴포넌트로 교체했다.
//App.js
import React from 'react';
import CounterContainer from './containers/CounterContainer';
import TodosContainer from './containers/TodosContainer';
const App = () => {
return (
<div>
<CounterContainer />
<hr/>
<TodosContainer/>
</div>
);
};
export default App;
// components/Todos.js
import React from "react";
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" : "none" }}>
{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;