[React] SPA와 React-Redux
SPA
SPA은 Single Page Application의 약어로 한 개의 페이지로 이루어진 애플리케이션이다.
뷰 렌더링을 사용자의 브라우저가 담당하도록 하고, 우선 애플리케이션을 브라우저에 불러와서 실행시킨 후에 사용자와의 인터렉션이 발생하면 필요한 부분만 자바스크립트를 사용하여 업데이트한다. 만약 새로운 데이터가 필요하다면 서버 API를 호출하여 필요한 데이터만 새로 불러와 애플리케이션에서 사용할 수 있다.
라우팅 : 다른 주소에 다른 화면을 보여 주는 것
SPA 단점 : 앱의 규모가 커지면 자바스크립트 파일이 너무 커진다. -> 코드 스플리팅(Code splitting)을 이용해 해결 가능
React - Redux
React : 소문으로 전파
- 필요 없는 소문을 듣게 됨 - render 함수가 호출돼서 performanc가 떨어짐.
- 집집마다 연결이 되어있어야 함 - props와 event를 매개로해서 연결
Redux : 미디어로 전파
- redux라는 언론사가 전 component에게 전달. 하지만 모든 component에게 전달되어야 하는 단점이 여전히 존재
React-Redux
react와 redux를 연결해주는 라이브러리
그 소식이 필요한 component에게만 전달 가능 - 그 component 들에게만 render 함수 실행
React-Redux 학습을 위한 프로젝트 (feat. velopert)
버튼 1: 값 1씩 증가
버튼 2: 값 1씩 감소
버튼3: 배경화면 색상 랜덤화
폴더 구조
위와 같이 src 파일 밑에 actions, components, reducers 폴더 생성.
이후 파일 추가
기본 컴포넌트 생성
Value.js - 멍청
Control.js -멍청
Counter.js : Value, Control 두개를 담을 부모 컴퍼넌트 - 똑똑
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
Components 폴더
// components/App.js
import React, { Component } from 'react';
import Value from './Value';
import Control from './Control';
class Counter extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Value />
<Control />
</div>
);
}
}
export default Counter;
// components/Value.js
import React, { Component } from 'react';
class Value extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>{this.props.number}</h1>
</div>
);
}
}
export default Value;
// components/Control.js
import React, { Component } from 'react';
function createWarning(funcName) {
return () => console.warn(funcName + ' is not defined');
}
const defaultProps = {
onPlus: createWarning('onPlus'),
onSubtract: createWarning('onSubtract'),
onRandomizeColor: createWarning('onRandomizeColor'),
};
class Control extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<button onClick={this.props.onPlus}>+</button>
<button onClick={this.props.onSubtract}>-</button>
<button onClick={this.props.onRandomizeColor}>Randomize Color</button>
</div>
);
}
}
export default Control;
// components/Counter.js
import React, { Component, PropTypes } from 'react';
import Value from './Value';
import Control from './Control';
class Counter extends Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
<Value/>
<Control/>
</div>
)
}
}
export default Counter;
action
- 작업에 대한 정보를 지닌 객체
- action 생성자로 만든 function이 실제로 reducer안에서 실행되는 것을 액션이라고 함. 즉, 액션은 모든 리듀서로 흐르는 오브젝트
- 액션 객체는 기본적으로 type을 가지고 있어야 함
// actions/ActionTypes.js
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
export const SET_COLOR = "SET_COLOR";
action 생성자
- 액션반환하는 함수
- 액션을 그때그때 생성하기 귀찮아서 있는 것이 action 생성자
//actions/index.js
import * as types from './ActionTypes';
export funtion increment() {
return {
type: types.INCREMENT
};
}
export funtion decrement() {
return {
type: types.DECREMENT
};
}
export function setColor(color) {
return {
type: types.SET_COLOR,
color // color: color와 동일한 의미
};
}
reducer
- '이전 상태'와 '액션'을 받아서 '다음 상태'를 반환한다 : (previousState, action) => newState
- 이전 상태를 변경하는게 아님 !!!!!! 단지 새로운 상태를 반환하는 것
- 변화를 일으키는 함수
- 순수해야함 : 1) 비동기 작업 X 2) 인수 변경 X 3) 동일한 인수 = 동일한 결과
숫자를 세는 counter reducer 함수와 color 관련 ui reducer 함수를 생성해 보자. 그리고 index 파일을 만들어 두 reducer 함수를 묶어보자.
// reducers/counter.js
// actionTypes를 불러옴
import * as types from '../actions/ActionTypes';
// reducer 초기 상태 정하기. 상수 형태의 객체로 정의
const initialState = {
number: 0,
dummy: 'dumbdumb',
dumbObject: {
d: 0,
u: 1,
m: 2,
b: 3
}
};
export default function counter(state = initialState, action) {
switch(action.type) {
case types.INCREAMENT:
// number 값, dumbOject의 u 값 덮어 씌움. ...state를 안쓰면 사용하지 않는 dummy가 지워짐
return {
...state,
number: state.number + 1,
dumbObject: { ...state.dumbObject, u: 0}
};
case types.DECREMENT:
return {
...state,
number: state.number -1
};
default:
return state;
}
}
// reducers/ui.js
import * as types from '../actions/ActionTypes';
const initialState = { // 초기 상태
color: [255, 255, 255] // rgb 형태
};
export default function ui(state = initialState, action) {
if(action.type === types.SET_COLOR) {
return {
color: action.color
};
} else {
return state;
}
}
// reducers/index.js
import { combineReducers } from 'redux';
import counter from './counter';
import ui from './ui';
const reducers = combineReducers ({
counter, ui
})
export default reducers;
store
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import { createStore } from 'redux';
import reducers from './reducers';
import * as actions from './actions';
const store = createStore(reducers); // store 생성
console.log(store.getState());
const unsubscribe = store.subscribe(()=> console.log(store.getState())); // subscribe(collback)
store.dispatch(actions.increment());
store.dispatch(actions.increment());
store.dispatch(actions.decrement());
store.dispatch(actions.setColor([200,200,200]));
unsubscribe();
store.dispatch(actions.setColor([220,200,200])); // 안 찍힘
ReactDOM.render(
<App/>, // 이 경우 App props로 store를 넘겨줄 수 있다. 하지만 구조가 복잡해져서 react-redux 사용!
document.getElementById('root')
);
dispatch(action)
- action을 보낼 때 사용
- dispatch 실행 -> store는 reducer 함수에 현재 자신의 상태 + 방금 전달받은 action을 전달-> reducer가 어떤 변화가 필요한지 알아내서 변화를 줌(현상태 -> 새상태)
getState()
- 현재 상태를 반환하는 함수
subscribe(listener)
- 상태가 바뀔 때마다 실행할 callback함수(listener) 등록
replaceReducer(nextReducer)
view layer binding - 아이티 부서와 같음. 복잡한 기술적인건 알아서 처리해쥼쓰
- Provider 하나의 컴포넌트
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import { createStore } from 'redux';
import reducers from './reducers';
import { Provider } from 'react-redux';
const store = createStore(reducers);
ReactDOM.render(
<Provider store={store}>
<App/>,
</Provider>,
document.getElementById('root')
);
App component와 Counter component에 접근하려면 connect([...options])를 설정해줘야 함
- connet([...options]): 컴포넌트를 Redux에 연결하는 함수를 반환
- connect(인자)(Counter): store에 연결된 "새로운" 컴포넌트 클래스가 반환됨, 옵션이 없으면 this.props.store로 접근 가능
connect 인자 (
- [mapStateToProps]: 함수형태의 파라미터. state를 parameter로 가지는 함수. state를 해당 component의 props로 연결 -> state.counter.number 같이 reducer 컴포넌트(counter)에 있는 number값을 가져옴
- [mapDispatchToProps]: 함수형태의 파라미터. Dispatch를 parameter로 가지는 함수. Dispatch한 함수를 props로 연결
- [mergeProps]: 함수형태의 파라미터. state와 Dispatch를 parameter로 가지는 함수. 둘을 동시에 props로 연결. 잘 안씀
- [options]: 객체형태의 파라미터. pure와 withRef가 있음.
- {[ pure = true ], [ withRef = false ]}
- pure=true이면 불필요한 업데이트 하지 않음
- withRef가 true이면 리덕스에 연결된 component를 ref에 담아서 getWrappedInstance()를 통해 접근. 잘 안씀
)
// components/Counter.js
import React, { Component } from 'react';
import Value from './Value';
import Control from './Control';
// import { connect, bindActionCreators } from 'react-redux';
import { connect } from 'react-redux';
import * as actions from '../actions'
class Counter extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Value number={this.props.number}/>
<Control />
</div>
);
}
}
// 컴포넌트의 state와 다름. 그냥 파라미터 이름 . 리덕스 state 지칭
const mapStateToProps = (state) => {
return {
number: state.counter.number,
color: state.ui.color
};
}
const mapDispatchToProps = (dispatch) => {
// return bindActionCreators(actions, dispatch);
return {
handleIncrement: () => { dispatch(actions.increment())},
handleDecrement: () => { dispatch(actions.decrement())},
handleSetColor: (color) => { dispatch(actions.setColor(color))}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
21/11/15 업데이트
connect 대신 useSelector, useDispatch를 이용하면 된다...
참조: https://bestalign.github.io/translation/cartoon-intro-to-redux/
'프로그래밍 > Javascript & React' 카테고리의 다른 글
[Javascript] Javascript 특징 및 웹 워커(멀티 스레드) (0) | 2022.01.14 |
---|---|
[Javascript] 렉시컬 스코프(Lexical Scope), 스코프 체인(Scope Chain), 클로저(Closure) (0) | 2022.01.13 |
[21/11/27] TIL_onClick 무한 렌더링, (0) | 2021.11.27 |
[React] z-index, 그의 정체는?!? (0) | 2021.11.12 |
[React] 21/11/07 TIL (0) | 2021.11.05 |
댓글