1. react-redux를 사용하는 기본적인 index.js파일 세팅
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reducer from './js/reducers'
import { Provider } from 'react-redux'
import { applyMiddleware, createStore } from 'redux';
import { logger } from 'redux-logger'
// const store = createStore(reducer, applyMiddleware(logger)) // 디버깅에 유용한 logger를 사용하고 싶을 때
const store = createStore(reducer)
ReactDOM.render(
<Provider store={store} >
<App />
</Provider>
, document.getElementById('root'));
여기서 reducer는 state와 action을 파라미터로 받는 state를 컨트롤할 수 있는 함수이다. (직접 정의해야함)
랜더링될 때 Redux 컴포넌트인 <Provider>에 store를 설정해주면 그 하위 컴포넌트들에 따로 props로 전달해주지 않아도 connect될 때 store에 접근할 수 있게 해준다.
2. reducer함수 파일
import {
INPUT,
ADD,
COMPLETE,
DELETES,
OPTION,
MOUSEOVER,
MOUSEOUT
} from '../types'
const initialState = {
listAll: [],
listCompleted: [],
listActive: [],
option: 'All',
content: '',
optionList: [{fontWeight: null, cursor: null}, {fontWeight: null, cursor: null}, {fontWeight: null, cursor: null}]
}
const reducer = (state=initialState, action) => {
switch(action.type) {
case INPUT:
return Object.assign({}, state, {
content: action.content
})
case ADD:
let temp = state.listAll.slice()
temp.push([state.content, {textDecoration: 'none'}])
return Object.assign({}, state, {
listAll: temp,
content: ''
})
case COMPLETE:
let temp2 = state.listAll.slice()
let tempContent = temp2[action.index][0]
temp2.splice(action.index, 1, [tempContent, {textDecoration: 'line-through'}])
return Object.assign({}, state, {
listAll: temp2
})
case DELETES:
let temp3 = state.listAll.slice()
temp3.splice(action.index, 1)
return Object.assign({}, state, {
listAll: temp3
})
case OPTION:
let temp4 = state.listAll.slice()
let tempCompleted = []
let tempActive = []
if(action.optionType === 'Completed') {
tempCompleted = temp4.filter(function(x) {
return x[1].textDecoration === 'line-through'
})
} else if (action.optionType === 'Active') {
tempActive = temp4.filter(function(x) {
return x[1].textDecoration === 'none'
})
}
return Object.assign({}, state, {
option: action.optionType,
listCompleted: tempCompleted,
listActive: tempActive
})
case MOUSEOVER:
let tempArr = state.optionList.slice()
tempArr[action.target] = {fontWeight: 'bold', cursor: 'pointer'}
console.log(tempArr)
return Object.assign({}, state, {
optionList: tempArr
})
case MOUSEOUT:
let tempArr2 = state.optionList.slice()
tempArr2[action.target] = {fontWeight: null, cursor: null}
return Object.assign({}, state, {
optionList: tempArr2
})
default:
return state
}
}
export default reducer;
createStore의 파라미터로 들어가는 reducer함수는 state와 action을 파라미터로 갖는다.
여기서 state가 react에서의 state라고 생각해도 무방하며, 처음에 react에서 this.state를 정의하듯이 초기값들을 설정해주어야 한다.
action은 type이라는 속성을 가지고 있어야 하고, 그 type에 따라 다른 동작을 할 수 있도록 reducer함수를 작성해야한다.
reducer함수는 setState와 비슷하게 객체형식의 데이터를 리턴해주어야 하는데, 이때 보통 원래의 state를 가져와 필요한 값들만 변경할 수 있도록 Object.assign을 이용한다.
3. action 파일
export const INPUT = 'INPUT'
export const ADD = 'ADD';
export const COMPLETE = 'COMPLETE';
export const DELETES = 'DELETES';
export const OPTION = 'OPTION'
export const MOUSEOVER = 'MOUSEOVER'
export const MOUSEOUT = 'MOUSEOUT'
export function input(str) {
return {
type: INPUT,
content: str
}
}
export function add() {
return {
type: ADD,
}
}
export function complete(key) {
return {
type: COMPLETE,
index: key
}
}
export function deletes(key) {
return {
type: DELETES,
index: key
}
}
export function option(str) {
return {
type: OPTION,
optionType: str,
}
}
export function mouseOver(index) {
return {
type: MOUSEOVER,
target: index
}
}
export function mouseOut(index) {
return {
type: MOUSEOUT,
target: index
}
}
각각의 action들은 type이라는 속성을 가지고있어야 하며, 필요에 따라 파라미터로 어떤 값을 받아 reducer함수에게 전달해줄 수 있다.
예를들어 mouseOut이라는 액션의 target값에 접근하고 싶다면 reducer함수 내에서
const reducer (state, action) {
switch(action.type) {
case MOUSEOUT:
let temp = action.target
return ................
}
}
위와같이 action.target으로 값을 가져올 수 있다.
5. 각각의 컴포넌트 파일
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { option } from '../types'
import { mouseOver } from '../types'
import { mouseOut } from '../types'
class Option extends Component {
render() {
return (
<div className='option-box'>
<span onMouseOver={() => this.props.mouseOver(0)} onMouseOut={() => this.props.mouseOut(0)} style={this.props.style[0]} onClick={() => this.props.changeOption('All')}>All</span>
<span className='slash'>/</span>
<span onMouseOver={() => this.props.mouseOver(1)} onMouseOut={() => this.props.mouseOut(1)} style={this.props.style[1]} onClick={() => this.props.changeOption('Completed')}>Completed</span>
<span className='slash'>/</span>
<span onMouseOver={() => this.props.mouseOver(2)} onMouseOut={() => this.props.mouseOut(2)} style={this.props.style[2]} onClick={() => this.props.changeOption('Active')}>Active</span>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
style: state.optionList
}
}
const mapDispatchToProps = (dispatch) => {
return {
changeOption: (str) => dispatch(option(str)),
mouseOver: (index) => dispatch(mouseOver(index)),
mouseOut: (index) => dispatch(mouseOut(index))
}
}
Option = connect(mapStateToProps, mapDispatchToProps)(Option)
export default Option
위의 코드에서는 connect라는 개념이 등장하는데, connect는 react-redux의 내장 API로서, React Component를 Redux Store에 연결해주는 역할을 한다.
파라미터로 받는 mapStateToProps는 store의 state값을 가져오고 싶을 때,
mapDispatchToProps는 특정 이벤트로 인해 store의 state값을 변경해주고 싶을때 사용하는것으로 생각된다.
이렇게 두 함수를 정의하고 connect를 통해 연결시켜주면
컴포넌트에서 this.props로 위의 함수에서 리턴한 객체들의 속성값들을 사용할 수 있다.
첫 화면을 랜더링하는것에서는 react와 큰차이가 없는것 같지만 어떤 이벤트가 발생했을때의 흐름을 살펴보면,
이벤트가 발생했을 때 dispatch('action함수')가 실행되면 reducer함수에서 그 action함수 타입에 해당하는 코드들이 실행된다.
이렇게 state가 바뀌게 되고 그 바뀐부분만을 화면에 다시 랜더링해주게 되는 것으로 생각된다.
react에서는 state값을 가져오거나, 이벤트가 발생했을때 state를 변경해주기 위해서는 최상위 컴포넌트에서부터 state값이나 이벤트시 실행되어야 하는 함수를 계속해서 props로 내려주어야 하지만 react-redux에서는 store의 state나 작성해놓은 함수를 직접 가져와 사용할 수 있는 장점이 있는 것 같다.
'웹' 카테고리의 다른 글
React-practice (0) | 2020.01.28 |
---|---|
CORS (0) | 2020.01.17 |
HTTP (0) | 2020.01.15 |