React는 형제 컴포넌트들 관의 데이터를 전달 해 주기 위해서는 반드시 부모 컴포넌트(App)를 거쳐야만 전달이 가능하다.
App아래의 두 형제 컴포넌트는 서로 대화하기를 어려워해 부모 컴포넌트인 App를 통해 소통한다. 부모 컴포넌트에게 컴포넌트 B는 상태(state)나 input, onCreate 메소드 등을 props로 전달 받는다고 가정하고, toDo라는 배열을 업데이트 해줬다.
업데이트 된 toDO 배열을 다시 부모 컴포넌트에게 전달하고, 부모 컴포넌트는 C컴포넌트에게 전달해주어 화면에 보이게 렌더링한다. 이런식으로 단방향으로 데이터가 흐르게 된다.
컴포넌트 G에게 Props를 전달 해줘야 한다면 App는 B를 거치고 E를 지나서 G로 넘겨주고 C.js에서 G를 렌더링 한다.
<B prop={props}/>
.
.
.
<G prop={props}/>
이런 식으로 자식 컴포넌트가 요구하는 props가 생길 수록 일일히 파일들을 열어가며 수정해줘야 하기 때문에 복잡하고 귀찮게된다.
위와 같은 상황이 동일하게 연출될 했을 때 Redux를 적용시키면 Store라는 녀석이 생기게된다. 여기서 Store는 상태(state)가 관리되는 오직 하나 뿐인 저장소를 말한다.
그러니까 React에서는 컴포넌트 자신이 개별로 상태(state)를 관리하면 Redux는 상태(state)를 단일 저장소인 Store에 저장한다.
다른 컴포넌트에서 컴포넌트 G를 통해 상태가 필요하다면, 컴포넌트 G에서 상태를 store에 넣고 뒤 각 컴포넌트에서 가져와 사용한다.
redux를 공식페이지에서 나온 것처럼 데이터의 흐름은 다음과 같다.
* 브라우저에서 어느 버튼을 클릭하면 이벤트가 발생해서 Dispatch된 Action를 전달한다.
* 저장소는 Reducer기능을 실행하여 새로운 상태로 생성한다.
* UI는 새 상태를 읽고 새로운 값을 표시한다.
컴포넌트가 많을수록 state가 업데이트된 이유를 찾기위해 컴포넌트 트리를 추적한다. 이런 과정은 효율성을 떨어뜨리므로 Redux는 단일 저장소에 상태를 저장하여 관리한다.
state는 읽기 전용이다. 상태를 업데이트 하기 위해서는 Redux의 Action이라고 부르는 행위가 존재한다.
export const ADD_TODO_LIST = "ADD_TODO_LIST";
export const DELETE_TODO_LIST = "DELETE_TODO_LIST";
ToDo리스트를 추가하는 액션과 제거하는 액션을 뜻한다.
Reducer는 이전 상태(prevState)와 액션(Action)을 인자로 받아 새로운 상태를 리턴하는 순수 함수이다. 다시말해 이전 상태를 변경하지 않고 새로운 상태를 반환해야 한다.
공식문서에서 가져온 코드로 예시를 들겠다
https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow
Store에 존재하는 state에 직접 접근하기 위해서는 Action이라는 행동을 취해야 한다. 구조는 아래와 같다.
{
type: "Action의 종류를 식별하기 위한 심볼 혹은 문자열"
payload: "액션 실행에 필요한 임의의 데이터(Optional)"
}
어떠한 아이템을 추가할 경우 이렇게 쓸 수 있을 것 같다.
{
type: "ADD_TEXT";
payload: {id: 0, text: "대충 인삿말"}
}
{
type: "CHANGE_INPUT";
payload: "대충 아무말"
}
하지만 이러한 Action 오브젝트들을 계속 만들어간다면, 하나하나 작성해줄 때마다 무척 괴로울 것이다... 이걸 해결 하기 위해 const와 함수를 쓰는게 일반적인 방법이다. 외부 파일에서 참고할 수도 있으므로 export도 넣어준다.
export const ADD_TEXT = "ADD_TEXT";
epxort const changeInput = (text) => ({
type: "CHANGE_INPUT"
text
})
Reducer는 순수한 함수(pure function)이며, 이전 상태(state)에 Action을 주어 새로운 상태(newState)를 생성하는 작업이다. 하지만 이전 상태의 데이터는 불변성을 유지해야한다.
reducer은 3가지 특정 규칙을 따라야한다.
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// 우선 Action.type에 해당 내용이 일치하는지 확인
if (action.type === 'counter/incremented') {
// 일치하면 상태(state)의 복사본을 만든다.
return {
...state,
//새 값으로 업데이트한다.
value: state.value + 1 //Object.assign의 다른방법....................
}
}
// 일치하지 않을경우 기존 상태를 변경하지 않고 반환함
return state
}
Store는 우리의 어플리케이션에서 상태가 보관되는 하나의 저장소이다. 모든 상태는 스토어에서 관리되고, 추가적인 내장함수들을 가지고있다.
redux를 npm으로 설치한 후 에 import를 사용하여 sotreRedux 라이브러리에서 객체를 생성하는 기능을 가져온다.
import { createStore } from 'redux';
이제 createStore 메소드를 활용해 reducer를 연결한다.
const store = createStore(reducer);
export default store;
Store의 내장함수 중 하나이다. 액션을 발생시키는 거라고 해석해도 좋을 것 같다.
dispatch라는 함수는 액션을 파라미터로 store에게 전달해 Reducer함수를 실행시킨다. 해당 액션을 처리하는 로직이 있다면 새로운 상태를 생성한다.
store.dispatch({ type:'counter/incremented' })
React의 Hooks를 이용하여 다음과 같이 사용이 가능하다
import { useDispatch, useSelector } from 'react-redux'
import {setAddText} from './PATH'
const dispatch = useDispatch()
dispatch(setAddText(params...));
Store의 상태 값에서 특정 정보를 추출하는 기능을 가진 함수이다. 애플리케이션이 커짐에 따라 앱의 다른부분의 동일한 데이터를 읽어야하기 떄문에 반복되는 논리를 방지할 수 있다.
const selectCounterValue = (state) => state.value
const currentValue = selectCounterValue ( store . getState ( ) )
출처 - 이해하기 쉬운 Redux???