Eric Elliott 멘티에게 setState와 Redux 사용 추천
이에 따른 다양한 반응들
“React team member checking in. Please learn to use setState before other approaches.”
“Those ‘adanced’ users will get left behind when we turn on async scheduling by default in React 17”
Can't take full advantage of React prioritization model if state/actions are all global.
“Fiber has a strategy for pausing, splitting, rebasing, aborting updates that doesn’t work if you deviate from component state”
또 다른 멘티가 setState에 대한 고민
“React has a setState() problem: Asking newbies to use setState() is a recipe for headaches. Advanced users have secret cures.”
사람들이 API에 대해 혼란을 겪는다면, 그것은 그 API를 향상시킬 기회이다.
리액트의 문제점
위의 모든 경우의 혼란이 리액트 컴포넌트 라이프사이클의 제한에 의해 발생된다.
스테이트를 업데이트 할 때, 업데이트하는 그 값은 가끔 리액트가 우리를 돕기 위한 것들에 의해 달려있다.
만약 이런 의존성 상태를 갖고 있고 간단하게 state 업데이트를 하려는 경우 리액트는 너무 이해하기 어려울 것이다. 간단하게 생각하고 하는 일들이 생각만큼 잘 되지 않을 것이다.
이런 setState()의 제한적인 행동이 API문서에 잘 나와있지 않았었지만, 최근 추가됬다.
setState(nextState,Callback)
현재 state에서 nextState를 위해 얕은 merge가 수행된다. 이벤트핸들러, 서버 요청에 대한 콜백으로부터 UI 업데이트를 수행하기 위핸 주요한 메소드이다.
setState호출은 동기적으로 작동하는것이 보장되지 않으며, 성능 향상을 위해 일괄적으로 처리될 수 있다.
이 두가지가 같이 작용하는 결과 사용자들에게 많은 버그를 야기한다.
// assuming state.count === 0
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
// state.count === 1, not 3
위의 코드는 아래와 같다.
Object.assign(state,
{count: state.count + 1},
{count: state.count + 1},
{count: state.count + 1}
); // {count: 1}
또한, setState의
함수와 그 시그니쳐 function(state,props) => newState 전달도 가능하다. 이것은 어떤 값을 설정하기 전에 상태와 소품의 이전 값을 참조하는 원자 업데이트를 나타낸다.
This enqueues an atomic update that consults the previous value of state and props before setting any values.
...
setState()는 즉시 this.state를 변경하지 않지만 state 전환 보류 상태를 만든다. this.state에 접근한 후에 이 메소드는 호출되고 기존의 값을 리턴할 가능성이 있다.
setState에 대해 약간의 설명을 하고 있지만, 버그에 대해서는 자세히 나와있지 않다.
라이프사이클 타이밍 이슈는 StackOverflow에서 setState()에 대해 질문한 많은 부분을 차지한다.
함수보다 객체를 받는 setState를 사용해라. 첫번째 인자로 이전의 state 그리고 두번째 인자는 업데이트가 적용되는 props이다.
// Correct
this.setState((prevState, props) => ({
count: prevState.count + props.increment
}));
함수 파라미터 폼은 (때때로 함수형 setState라고도 한다)
[
{increment: 1},
{increment: 1},
{increment: 1}
].reduce((prevState, props) => ({
count: prevState.count + props.increment
}), {count: 0}); // {count: 3}
updater 함수의 키이다.
(prevState, props) => ({
count: prevState.count + props.increment
})
이것은 기본적으로 리듀서이다. prevState가 accumulator처럼 작동하고 props는 새로 업데이트될 데이터의 소스로 작동한다. Redux의 리듀서와 유사하게, 이 함수를 표준적인 reduce 유틸리티를 사용해서reduce할수 있다.(Array.prototype.reduce()를 포함해서)
리덕스처럼 리듀서 또한 순수 함수여아한다.
여기서 prevState를 직접 변경하려는게 새로운 유저들에게 공통된 오해이다.
문제가 발생할 때 리액트를 넘기 위해 싸울수도 있고, 리액트에게 그 일을 하게 내버려두고 흐름에 따라 갈수도 있다.
You can fight with React over when things happen, or you can let React do its thing and go with the flow.
현재 시스템에서는 state를 다루기 위해 대표적인 두가지 방법이 있다.
위 두가지 방법을 가르쳐본 결과, 첫 번째 방법이 두 번째 보다 훨씬 혼란을 주고 에러발생확률이 높았다.
setState의 경우 state 업데이트를 간단하게 막고 일괄처리하거나 지연시키는 경우 문제에 대한 올바른 해결책은 아니다.
기본적인 추천 : redux
state 관리를 container 컴포넌트(혹은 Redux)로의 이동은 컴포넌트가 렌더되기 전에 컴포넌트의 state가 강제 되기 때문에 생각을 다르게 만들어 준다.
Before you render, decide the state!
render() 메소드 안에 setState()를 사용하는 것은 너무 당연스러운 안티패턴이다.
dependent state를 render 메소드 안에서 계산하는건 괜찮지만 container컴포넌트에서 계산하고 props로 넘겨주는 방법을 선호한다.
객체 리터럴 형식은 더이상 사용하지 않는다.
state and Lifecycle docs 언급
리액트가 존재하는 이유의 전부이다라고 말할지도 모른다.
리액트를 만들게된 한가지 동기는 결정론적인 렌더를 확립하는것이다.
(앱의 state를 주고, 특정한 아웃풋을 렌더한다. 이상적으로, 같은 state의 경우 항상 같은 아웃풋이 렌더된다.)
이렇게 만들기 위해서는 리액트는 변화가 발생할 때 그것을 제한하여 관리해야한다.
DOM을 직접 조작해서 변화시키는 대신 리액트는 DOM을 렌더시키고, state가 변화할 경우 리액트는 어떻게 렌더할지 결정한다. DOM을 렌더하는건 우리가 하는게 아니라 리액트가 한다.
이걸 하기 위해서 cycle을 업데이트 하는 동안은 렌더를 다시 트리거 하지 않는다.
(: 리액트가 렌더를 위해 사용하는 state는 DOM 렌더중에 변화시키지 않는다. 컴포넌트 업데이트 될때의 state는 우리가 결정하는게 아니라 리액트가 결정한다
setState()를 호출할 때, state를 설정한다고 생각하지만 아니다!
나는 오직 setState()를 지속적인 state가 필요없고 기능적으로 자급적인 단위에서 사용한다.
예를 들어, 재사용가능한 form 유효성 컴포넌트, 커스텀 날짜, 시간을 나타내주는 위젯, 데이터 시각화 위젯 등 그들의 뷰 상태를 변경이 필요한 경우
widget이라 부르는 2개 이상으로 구성된 컴포넌트들(내부 상태를 관리하는 컨테이너와 DOM을 관리하고 presentation 측면을 처리하는 하나 혹은 두개이상의 자식 컴포넌트들)
여기 간단한 리트머스 테스트가 있다 :
만약 두가지 답변이 아니요라면 setState를 사용하기에 괜찮다. 그렇지 않다면 다른것을 생각해봐라.
내가알기론 페이스북에서는 setState()를 관리하기위해 Relay container를 사용해서 큰 페이스북 앱 안에서 세부화된 다른 페이스북의 UI처럼 캡슐화 한다.(GraphQL)
그들에게 있어, 복잡한 데이터 의존성을 실제로 사용하는 컴포넌트와 결합시키는 좋은 방법이다.
대부분 앱에서는 redux 추천, don't use Redux until you feel the pain
“Those who are unaware they are walking in darkness will never seek the light”.
Mobx