Redux를 React와 연동해서 사용하기 편리하도록 만든 공식적인 라이브러리이다.
Redux 는 독립적인 도구이다. React 역시 독립적인 도구이다. 한마디로 이 둘을 서로 연결하는 도구가 바로 React-redux 이다.
물론, 이전 글에서 포스트한 기본적인 Redux로 React에서 사용 가능하다.
하지만, React 관점에서 더 편하게 사용하기 위해 React-redux 를 사용한다.
공통점
store
➡️ component
➡️ action
➡️ dispatch
➡️ reducer
➡️ store
순서는 동일하다.
차이점
react-redux
는 store
➡️ component
, component
➡️ action
단계에서 connect 라는 함수를 사용한다.
React-redux 에 중요한 4가지 요소가 있다.
state
를 '어떤 컴포넌트들에게 제공할 것인가?' 에 대한 가장 바깥쪽에서 알려주는 큰 울타리를 정의하는 컴포넌트이다.
Provider
의 Props 중에 store
를 반드시 정의해줘야 한다. 이름에서 알 수 있다시피 Redux 의 createStore
로 만든 store
를 집어 넣어 준다.
아래 예제를 보면 Provider
로 감싸진 ParentComponent
라는 이름의 컴포넌트는 store
가 가진 상태를 사용할 수 있다.
그렇다면 ParentComponent
의 하위 컴포넌트인 ChildComponent
역시 store
에 담긴 데이터를 사용할 수 있는 것이다.
import {SafeAreaView, Text, View} from 'react-native';
import {createStore} from 'redux';
import {Provider, useSelector, useDispatch, connect} from 'react-redux';
function App(): JSX.Element {
const reducer = (currentState: any, action: any) => {
if (!currentState) {
return {
number: 1,
};
}
const newState = {...currentState};
return newState;
};
const store = createStore(reducer);
return (
<Provider store={store}>
<ParentComponent />
</Provider>
);
}
const ParentComponent = () => {
return (
<SafeAreaView>
<ChildComponent />
</SafeAreaView>
);
};
const ChildComponent = () => {
const number = useSelector(state => state.number);
return (
<View>
<Text>{number}</Text>
</View>
);
};
export default App;
store
가 가지고 있는 상태들 중에 '어떤 상태를 가져다 쓸래?' 에 대한 역할을 담당한다.
사용 방법은 간단하다.
아래 예제와 같이 상태를 가져다 쓰고 싶은 컴포넌트 안에서 useSelector
를 호출한 후 어떤 state
를 가져다 쓸 것인지 return
해주면 된다.
const ChildComponent = () => {
const number = useSelector(state => state.number);
return (
<View>
<Text>{number}</Text>
</View>
);
};
❗️여기서 중요한 점은, 상위 컴포넌트인 ParentComponent
로 부터 props를 전달 받아 사용하는 것이 아니라, ChildComponent
에서 곧장 state
를 호출하여 사용했다는 점이다. 이는 Redux 에서 Props drilling 이슈와 관련된다.
action
을 통해 reducer
를 호출하여 state
를 변경하는 메소드이다.
다음 예제처럼 버튼을 클릭했을 때, number
가 +1 씩 증가하는 기능을 만들어보자.
const ChildComponent = () => {
const number = useSelector(state => state.number);
const dispatch = useDispatch();
return (
<View>
<Text>{number}</Text>
<Button
title="+"
onPress={() => {
dispatch({
type: 'INCREASE',
});
}}></Button>
</View>
);
};
useDispatch
메소드를 호출하고 그 안에 action
객체 타입을 넣는다.
버튼을 누르는 순간 dispatch
가 동작하게 되고, reducer
가 호출되된다. action
에서 호출한 type
에 맞게 조건문이 발동하면서 state
가 변경되고, 변경을 감지한 store
가 상태를 업데이트 하게 되며 최종적으로 number
는 +1 이 되어 화면에 나타난다.
const reducer = (currentState: any, action: any) => {
if (!currentState) {
return {
number: 1,
};
}
const newState = {...currentState};
if (action.type === 'INCREASE') {
newState.number++;
}
return newState;
};
🔔 참고
const newState = {...currentState}
에서 state
를 복사하는 이유는 '상태의 불변성 법칙' 때문에 그렇다.
React 또는 React Native 에서는 상태를 직접적으로 변경할 수 없다. 상태를 변경하기 위해선 원본을 복사하여 복사본을 수정한 후, 그 것을 원본과 비교하여 변경 된 부분만 상태를 업데이트하기 때문에 위 같은 코드가 들어간 것이다.
connect
는 ChildComponent
와 같이 하위 컴포넌트에서 store
에 접근하기 위해 사용되는 또 다른 메소드이다.
결론적으로, useSelector
와 useDispatch
가 워낙에 편하기 때문에 사실 사용 할 일이 별로 없다. 따라서 깊이 안들어가기 위해 따로 예제도 다루지 않겠다.