ReactJS 상태 대 소품


121

이것은 대답 할 수있는 것과 의견이있는 것 사이의 경계를 넘을 수 있지만 복잡성이 커지고 어떤 방향을 사용할 수있을 때 ReactJS 구성 요소를 구성하는 방법에 대해 앞뒤로 진행할 것입니다.

AngularJS에서 왔기 때문에 모델을 구성 요소에 속성으로 전달하고 구성 요소가 모델을 직접 수정하도록하고 싶습니다. 아니면 모델을 다양한 state속성 으로 분할하고 업스트림으로 다시 보낼 때 다시 함께 컴파일해야합니까? ReactJS 방식은 무엇입니까?

블로그 게시물 편집기의 예를 들어 보겠습니다. 모델을 직접 수정하려고하면 다음과 같이됩니다.

var PostEditor = React.createClass({
  updateText: function(e) {
    var text = e.target.value;
    this.props.post.text = text;
    this.forceUpdate();
  },
  render: function() {
    return (
      <input value={this.props.post.text} onChange={this.updateText}/>
      <button onClick={this.props.post.save}/>Save</button>
    );
  }
});

잘못된 것 같습니다.

text모델 속성 을 만들고 state다음과 같이 저장하기 전에 모델로 다시 컴파일하는 것이 React 방법입니까?

var PostEditor = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },
  componentWillMount: function() {
    this.setState({
      text: this.props.post.text
    });
  },
  updateText: function(e) {
    this.setState({
      text: e.target.value
    });
  },
  savePost: function() {
    this.props.post.text = this.state.text;
    this.props.post.save();
  },
  render: function() {
    return (
      <input value={this.state.text} onChange={this.updateText}/>
      <button onClick={this.savePost}/>Save</button>
    );
  }
});

를 호출 할 필요는 this.forceUpdate()없지만 모델이 성장함에 따라 (게시물에 작성자, 주제, 태그, 댓글, 등급 등이있을 수 있음) 구성 요소가 정말 복잡해지기 시작합니다.

ReactLink 의 첫 번째 방법이 갈 길입니까?

답변:


64

두 번째 접근 방식은 더 비슷합니다. React는 과 앱을 통해 흐르는 방식에 관심이있는만큼 모델에 관심이 없습니다 . 이상적으로는 게시물 모델이 루트의 단일 구성 요소에 저장됩니다. 그런 다음 각각 모델의 일부를 사용하는 하위 구성 요소를 만듭니다.

데이터를 수정해야하는 자식에게 콜백을 전달하고 자식 구성 요소에서 호출 할 수 있습니다.

this.props 또는 this.state를 직접 수정하는 것은 좋은 생각이 아닙니다. React가 변경 사항을 적용 할 수 없기 때문입니다. React가 post prop을 얕게 비교하여 변경되었는지 확인하기 때문입니다.

데이터가 외부에서 내부 구성 요소로 어떻게 흐를 수 있는지 보여주기 위해이 jsfiddle 을 만들었습니다 .

handleClick메서드는 상태를 (부적절하게) 업데이트하는 세 가지 방법을 보여줍니다.

var Outer = React.createClass({

  getInitialState: function() {
    return {data: {value: 'at first, it works'}};
  },

  handleClick: function () {

    // 1. This doesn't work, render is not triggered.
    // Never set state directly because the updated values
    // can still be read, which can lead to unexpected behavior.

    this.state.data.value = 'but React will never know!';

    // 2. This works, because we use setState

    var newData = {value: 'it works 2'};
    this.setState({data: newData});

    // 3. Alternatively you can use React's immutability helpers
    // to update more complex models.
    // Read more: http://facebook.github.io/react/docs/update.html

    var newState = React.addons.update(this.state, {
      data: {value: {$set: 'it works'}}
    });
    this.setState(newState);
 },

  render: function() {
      return <Inner data={this.state.data} handleClick={this.handleClick} />;
  }
});

하지만 자체 상태 조작 기능이있는 불투명 모델이 있다면 어떻게해야할까요? 예를 들어 text필드 대신 setText 유효성 검사 및 기타 작업을 수행 하는 메서드가 있다고 가정합니다 . setText순수한 경우 메서드 (2)가 작동 하고 모델의 새로운 인스턴스를 반환하는 것을 볼 수 있습니다. 그러나 setText내부 상태 만 업데이트하는 경우 에도을 호출해야합니다 forceUpdate.
hugomg 2014 년

1
예,을 호출 할 수 forceUpdate있지만 그 시점에서 React에서 "누수"됩니다. setState()다시 렌더링을 수동으로 트리거하지 않으려면 불투명 모델에 콜백 을 전달하는 것이 좋습니다 .
jxg 2014 년

나는 아직도 내가 완전히 이해하고 있는지 잘 모르겠습니다. 따라서 데이터를 수정하려는 구성 요소는 전달 된 props의 딥 카피를 수행해야합니까? 그런 다음 원본 데이터를 수정하지 않도록 해당 복사본을 수정하여 업스트림으로 보내시겠습니까? 결국 변경 사항은 루트로 이동하여 처리되고 전체 앱이 다시 렌더링됩니까? 맞습니까?
니콜라스

97

2016 년 업데이트 : React가 변경되고 "props vs state"설명이 매우 간단 해졌습니다. 구성 요소가 데이터를 변경해야하는 경우-상태에 놓으십시오. 그렇지 않으면 소품에 넣으십시오. 이제 소품이 읽기 전용 이기 때문 입니다.

소품과 상태의 정확한 차이점은 무엇입니까?

여기에서 좋은 설명을 찾을 수 있습니다 (정식 버전).

소품 및 상태 변경


1
실제로 setProps는 () 구성 요소 내부에 소품을 변경하고 다시 렌더링 게재 할 수
WaiKit 쿵

2
setProps더 이상 사용되지 않으며 사용해서는 안됩니다. 교체는 컴포넌트를 다시 렌더링하고 React가 차이점을 처리하도록하는 것입니다.
jdmichal

그리고 당신은 설명자 비디오를 찾고 있다면 : youtube.com/watch?v=qh3dYM6Keuw
jaisonDavis

35

React 문서에서

props는 변경할 수 없습니다. 부모로부터 전달되고 부모가 "소유"합니다. 상호 작용을 구현하기 위해 컴포넌트에 가변 상태를 도입합니다. this.state는 구성 요소에 대해 비공개이며 this.setState ()를 호출하여 변경할 수 있습니다. 상태가 업데이트되면 구성 요소가 자체적으로 다시 렌더링됩니다.

에서 TrySpace : 소품 (또는 상태) (setProps / 또는 상위 setState를 통해) 갱신 될 때 구성 요소뿐만 아니라, 재 - 렌더링한다.


16

Thinking in React 의 읽기 :

각각을 살펴보고 어느 것이 상태인지 알아 봅시다. 각 데이터에 대해 세 가지 질문 만하면됩니다.

  1. 소품을 통해 부모로부터 전달됩니까? 그렇다면 아마도 상태가 아닐 것입니다.
  2. 시간이 지남에 따라 변합니까? 그렇지 않다면 아마도 상태가 아닐 것입니다.

  3. 컴포넌트의 다른 상태 또는 소품을 기반으로 계산할 수 있습니까? 그렇다면 상태가 아닙니다.


13

귀하의 질문에 대답하고 있는지 확실하지 않지만 특히 대규모 / 성장하는 애플리케이션에서 컨테이너 / 구성 요소 패턴이 매우 잘 작동한다는 것을 발견했습니다.

기본적으로 두 가지 React 구성 요소가 있습니다.

  • 스타일과 DOM 상호 작용을 다루는 "순수한"디스플레이 컴포넌트;
  • 외부 데이터 액세스 / 저장, 상태 관리 및 디스플레이 구성 요소 렌더링을 처리하는 컨테이너 구성 요소입니다.

NB 이 예제는이 패턴의 이점을 설명하기에는 너무 간단 할 수 있습니다. 이러한 간단한 경우에는 매우 장황합니다.

/**
 * Container Component
 *
 *  - Manages component state
 *  - Does plumbing of data fetching/saving
 */

var PostEditorContainer = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },

  componentWillMount: function() {
    this.setState({
      text: getPostText()
    });
  },

  updateText: function(text) {
    this.setState({
      text: text
    });
  },

  savePost: function() {
    savePostText(this.state.text);
  },

  render: function() {
    return (
      <PostEditor
        text={this.state.text}
        onChange={this.updateText.bind(this)}
        onSave={this.savePost.bind(this)}
      />
    );
  }
});


/**
 * Pure Display Component
 *
 *  - Calculates styling based on passed properties
 *  - Often just a render method
 *  - Uses methods passed in from container to announce changes
 */

var PostEditor = React.createClass({
  render: function() {
    return (
      <div>
        <input type="text" value={this.props.text} onChange={this.props.onChange} />
        <button type="button" onClick={this.props.onSave} />
      </div>
    );
  }
});

혜택

디스플레이 로직과 데이터 / 상태 관리를 별도로 유지함으로써 다음과 같은 재사용 가능한 디스플레이 구성 요소를 갖게됩니다.

  • react-component-playground 와 같은 것을 사용하여 다양한 소품 세트로 쉽게 반복 할 수 있습니다.
  • 다른 동작을 위해 다른 컨테이너로 래핑 할 수 있습니다 (또는 다른 구성 요소와 결합하여 애플리케이션의 더 큰 부분을 빌드 할 수 있음).

또한 모든 외부 통신을 처리하는 컨테이너 구성 요소도 있습니다. 이렇게하면 나중에 심각한 변경 사항이있을 경우 데이터에 액세스하는 방식을보다 쉽게 ​​유연하게 만들 수 있습니다 *.

이 패턴은 또한 단위 테스트를 작성하고 구현하는 것을 훨씬 더 간단하게 만듭니다.

대규모 React 앱을 몇 번 반복 한 결과,이 패턴은 특히 계산 된 스타일이나 복잡한 DOM 상호 작용이있는 더 큰 구성 요소가있을 때 비교적 고통스럽지 않다는 것을 발견했습니다.

* 플럭스 패턴을 읽고 Marty.js를 살펴보십시오. 이 답변에 큰 영향미쳤 습니다 (최근에 많이 사용했습니다) Redux (및 react-redux ).이 패턴을 매우 잘 구현합니다.

2018 년 이후에이 글을 읽는 분들을위한 참고 사항 :

이 답변이 작성된 이후 React는 특히 Hooks 도입과 함께 상당히 발전했습니다 . 그러나이 예제의 기본 상태 관리 로직은 동일하게 유지되며 더 중요한 것은 상태 및 프리젠 테이션 로직을 별도로 유지함으로써 얻을 수있는 이점이 동일한 방식으로 계속 적용된다는 것입니다.


0

페이스 북이 이미이 링크 에서 설명한 안티 패턴을 사용하고있는 것 같습니다

당신이 찾은 것은 다음과 같습니다.

React.createClass({
  getInitialState: function() {
    return { value: { foo: 'bar' } };
  },

  onClick: function() {
    var value = this.state.value;
    value.foo += 'bar'; // ANTI-PATTERN!
    this.setState({ value: value });
  },

  render: function() {
    return (
      <div>
        <InnerComponent value={this.state.value} />
        <a onClick={this.onClick}>Click me</a>
      </div>
    );
  }
});

내부 구성 요소가 처음 렌더링되면 값 소품으로 {foo : 'bar'}가 있습니다. 사용자가 앵커를 클릭하면 상위 구성 요소의 상태가 {value : {foo : 'barbar'}}로 업데이트되어 내부 구성 요소의 다시 렌더링 프로세스가 트리거되고 {foo : 'barbar'}가 다음과 같이 수신됩니다. 소품의 새 값입니다.

문제는 부모 및 내부 구성 요소가 동일한 개체에 대한 참조를 공유하기 때문에 개체가 onClick 함수의 라인 2에서 변경되면 내부 구성 요소가 가진 소품이 변경된다는 것입니다. 따라서 다시 렌더링 프로세스가 시작되고 shouldComponentUpdate가 호출되면 this.props.value.foo는 nextProps.value.foo와 동일합니다. 실제로 this.props.value는 nextProps.value와 동일한 객체를 참조하기 때문입니다.

결과적으로 prop의 변경 사항을 놓치고 다시 렌더링 프로세스를 단락 시키므로 UI가 'bar'에서 'barbar'로 업데이트되지 않습니다.


Innercomponents코드를 게시 해 주 시겠습니까?
압둘라 칸
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.