컴포넌트가 Flux의 Stores에서 엔티티를 읽어야하는 중첩 수준은 무엇입니까?


89

Flux를 사용하기 위해 앱을 다시 작성하고 있는데 Stores에서 데이터를 검색하는 데 문제가 있습니다. 나는 많은 구성 요소를 가지고 있으며 많이 중첩됩니다. 그들 중 일부는 크고 ( Article), 일부는 작고 단순합니다 ( UserAvatar, UserLink).

구성 요소 계층 구조에서 Stores에서 데이터를 읽어야하는 곳에서 어려움을 겪고 있습니다.
두 가지 극단적 인 접근 방식을 시도했지만 어느 쪽도 마음에 들지 않았습니다.

모든 엔티티 구성 요소는 자체 데이터를 읽습니다.

Store에서 일부 데이터가 필요한 각 구성 요소는 엔터티 ID 만 받고 자체적으로 엔터티를 검색합니다.
예를 들어, Article전달 articleId, UserAvatar그리고 UserLink전달됩니다 userId.

이 접근 방식에는 몇 가지 중요한 단점이 있습니다 (코드 샘플에서 논의 됨).

var Article = React.createClass({
  mixins: [createStoreMixin(ArticleStore)],

  propTypes: {
    articleId: PropTypes.number.isRequired
  },

  getStateFromStores() {
    return {
      article: ArticleStore.get(this.props.articleId);
    }
  },

  render() {
    var article = this.state.article,
        userId = article.userId;

    return (
      <div>
        <UserLink userId={userId}>
          <UserAvatar userId={userId} />
        </UserLink>

        <h1>{article.title}</h1>
        <p>{article.text}</p>

        <p>Read more by <UserLink userId={userId} />.</p>
      </div>
    )
  }
});

var UserAvatar = React.createClass({
  mixins: [createStoreMixin(UserStore)],

  propTypes: {
    userId: PropTypes.number.isRequired
  },

  getStateFromStores() {
    return {
      user: UserStore.get(this.props.userId);
    }
  },

  render() {
    var user = this.state.user;

    return (
      <img src={user.thumbnailUrl} />
    )
  }
});

var UserLink = React.createClass({
  mixins: [createStoreMixin(UserStore)],

  propTypes: {
    userId: PropTypes.number.isRequired
  },

  getStateFromStores() {
    return {
      user: UserStore.get(this.props.userId);
    }
  },

  render() {
    var user = this.state.user;

    return (
      <Link to='user' params={{ userId: this.props.userId }}>
        {this.props.children || user.name}
      </Link>
    )
  }
});

이 접근 방식의 단점 :

  • 잠재적으로 Stores를 구독하는 100 개의 구성 요소가 있다는 것은 실망 스럽습니다.
  • 각 구성 요소가 데이터를 독립적으로 검색 하기 때문에 데이터가 업데이트되는 방식과 순서를 추적하기어렵습니다 .
  • 이미 상태에있는 항목 이있을 수 있지만 해당 ID를 자식에게 전달해야하며, 자식은 다시 검색하거나 일관성을 깨뜨립니다.

모든 데이터는 최상위 수준에서 한 번 읽고 구성 요소로 전달됩니다.

버그 추적에 지쳤을 때 검색하는 모든 데이터를 최상위 수준에 두려고했습니다. 그러나 이것은 일부 엔티티의 경우 여러 수준의 중첩이 있기 때문에 불가능한 것으로 판명되었습니다.

예를 들면 :

  • A Category에는 UserAvatar해당 범주에 기여하는 사람들이 포함됩니다 .
  • 에는 s Article가 여러 개있을 수 있습니다 Category.

따라서의 수준에서 Stores의 모든 데이터를 검색 Article하려면 다음을 수행해야합니다.

  • 에서 기사 검색 ArticleStore;
  • 에서 모든 기사의 카테고리를 검색합니다 CategoryStore.
  • 에서 각 카테고리의 기여자를 별도로 검색합니다 UserStore.
  • 어떻게 든 모든 데이터를 구성 요소로 전달합니다.

더욱 실망스럽게도 깊이 중첩 된 엔터티가 필요할 때마다 각 중첩 수준에 코드를 추가하여 추가로 전달해야합니다.

합산

두 접근법 모두 결함이있는 것 같습니다. 이 문제를 어떻게 가장 우아하게 해결합니까?

내 목표 :

  • 상점에 엄청난 수의 구독자가 있어서는 안됩니다. 부모 구성 요소가 이미 그것을 UserLink하고 UserStore있다면 각 사람 이 듣는 것은 어리석은 일입니다.

  • 부모 구성 요소가 저장소 (예 :)에서 일부 개체를 검색 한 user경우 중첩 된 구성 요소가 다시 가져올 필요가 없습니다. 소품을 통해 전달할 수 있어야합니다.

  • 관계 추가 또는 제거가 복잡해지기 때문에 최상위 수준에서 모든 엔터티 (관계 포함)를 가져올 필요는 없습니다. 중첩 된 엔터티가 새 관계를 얻을 때마다 모든 중첩 수준에서 새 소품을 도입하고 싶지 않습니다 (예 : 범주가를 얻음 curator).


1
게시 해 주셔서 감사합니다. 방금 여기에 매우 유사한 질문을 게시했습니다. groups.google.com/forum/… 내가 본 모든 것은 관련 데이터가 아닌 플랫 데이터 구조를 다루었습니다. 이에 대한 모범 사례를 보지 못한 것이 놀랍습니다.
uberllama

답변:


37

대부분의 사람들은 계층 구조의 맨 위 근처에있는 컨트롤러보기 구성 요소에서 관련 저장소를 듣는 것으로 시작합니다.

나중에 관련없는 많은 소품이 계층 구조를 통해 일부 깊이 중첩 된 구성 요소로 전달되는 것처럼 보일 때 일부 사람들 은 더 깊은 구성 요소가 상점의 변경 사항을 수신하도록하는 것이 좋습니다. 이것은 구성 요소 트리의 더 깊은 분기에 관한 문제 도메인의 더 나은 캡슐화를 제공합니다. 이 일을 현명하게하기위한 좋은 주장이 있습니다.

그러나 나는 항상 상단에서 듣고 모든 데이터를 전달하는 것을 선호합니다. 때로는 매장의 전체 상태를 단일 객체로 계층 구조를 통해 전달하고 여러 매장에 대해이 작업을 수행 할 수도 있습니다. 그래서 나는 ArticleStore의 상태에 대한 prop 과의 상태 등에 대한 prop을 갖게 될 것입니다 UserStore. 깊이 중첩 된 컨트롤러 뷰를 피하는 것은 데이터에 대한 단일 진입 점을 유지하고 데이터 흐름을 통합한다는 것을 발견했습니다. 그렇지 않으면 데이터 소스가 여러 개 있고 디버깅하기 어려울 수 있습니다.

이 전략에서는 유형 검사가 더 어렵지만 React의 PropTypes를 사용하여 소품으로서의 대형 객체에 대해 "모양"또는 유형 템플릿을 설정할 수 있습니다. 참조 : https://github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91 http://facebook.github.io/react/docs/reusable-components.html#prop -확인

상점 자체의 상점간에 데이터를 연관시키는 논리를 넣을 수 있습니다. 당신 그래서 ArticleStore힘 , 그리고 모든과 관련 사용자 등 이 통해 제공 기록을 . 뷰에서이 작업을 수행하는 것은 논리를 뷰 레이어로 푸시하는 것처럼 들리므로 가능할 때마다 피해야하는 관행입니다.waitFor()UserStoreArticlegetArticles()

을 사용 transferPropsTo()하고 싶을 수도 있고 많은 사람들이이 작업을 좋아하지만, 가독성과 유지 관리를 위해 모든 것을 명시 적으로 유지하는 것을 선호합니다.

FWIW, 내 이해는 David Nolen이 루트 노드의 단일 데이터 진입 점을 사용하여 Om 프레임 워크 ( Flux 호환 가능 )와 유사한 접근 방식을 취한다는 것입니다 .Flux 에서 이에 상응하는 것은 하나의 컨트롤러 뷰만 갖는 것입니다. 모든 상점을 듣고 있습니다. 이것은 shouldComponentUpdate()===와 함께 참조로 비교할 수있는 변경 불가능한 데이터 구조를 사용하여 효율적 입니다. 불변 데이터 구조의 경우 David 's mori 또는 Facebook의 immutable-js를 확인하십시오 . Om에 대한 나의 제한된 지식은 주로 JavaScript MVC 프레임 워크의 미래 에서 비롯됩니다 .


4
자세한 답변 감사합니다! 나는 상점 상태의 전체 스냅 샷을 모두 전달하는 것을 고려했습니다. 그러나 이것은 UserStore의 모든 업데이트로 인해 화면의 모든 기사가 다시 렌더링되고 PureRenderMixin은 업데이트해야 할 기사와 업데이트하지 않는 기사를 알 수 없기 때문에 성능 문제가 발생할 수 있습니다. 두 번째 제안에 대해서도 생각했지만 기사의 사용자가 각 getArticles () 호출에 "주입"된 경우 기사 참조 동등성을 유지하는 방법을 알 수 없습니다. 이것은 다시 잠재적으로 성능 문제를 일으킬 것입니다.
Dan Abramov 2014 년

1
나는 당신의 요점을 알지만 해결책이 있습니다. 우선, 기사에 대한 사용자 ID 배열이 변경된 경우 shouldComponentUpdate ()를 확인할 수 있습니다. 이는 기본적으로이 특정 경우에 PureRenderMixin에서 실제로 원하는 기능과 동작을 직접 롤링하는 것입니다.
fisherwebdev 2014 년

3
맞아요, 그리고 어쨌든 나는 최대 분당 여러 번 업데이트하는 상점의 경우는 안되는 성능 문제 있다고 확신 할 때만 필요합니다 . 다시 감사합니다!
Dan Abramov 2014 년

8
구축중인 응용 프로그램의 종류에 따라 동일한 루트에 직접 속하지 않는 더 많은 "위젯"이 화면에 표시 되 자마자 하나의 진입 점을 유지하는 것이 손상 될 것입니다. 강제로 그렇게하려고하면 루트 노드가 너무 많은 책임을지게됩니다. KISS와 SOLID는 클라이언트 측이지만.
Rygu 2014 년

37

내가 도착한 접근 방식은 각 구성 요소가 해당 데이터 (ID가 아님)를 소품으로받는 것입니다. 일부 중첩 된 구성 요소에 관련 엔터티가 필요한 경우이를 검색하는 것은 부모 구성 요소에 달려 있습니다.

이 예에서, Article을 가져야한다article 객체 ( ArticleList또는에서 검색 소품이ArticlePage .

Article또한 렌더링 UserLink하고 UserAvatar기사 작성자를 원하기 때문에 구독 하고 상태를 UserStore유지 author: UserStore.get(article.authorId)합니다. 그런 다음 렌더링됩니다.UserLinkUserAvatar이와 this.state.author. 더 멀리 전달하고 싶다면 할 수 있습니다. 이 사용자를 다시 검색하는 데 하위 구성 요소가 필요하지 않습니다.

다시 말하면 :

  • 어떤 구성 요소도 소품으로 ID를받지 않습니다. 모든 구성 요소는 각각의 개체를받습니다.
  • 자식 구성 요소에 엔터티가 필요한 경우이를 검색하고 소품으로 전달하는 것은 부모의 책임입니다.

이것은 내 문제를 아주 잘 해결합니다. 이 접근 방식을 사용하도록 재 작성된 코드 예제 :

var Article = React.createClass({
  mixins: [createStoreMixin(UserStore)],

  propTypes: {
    article: PropTypes.object.isRequired
  },

  getStateFromStores() {
    return {
      author: UserStore.get(this.props.article.authorId);
    }
  },

  render() {
    var article = this.props.article,
        author = this.state.author;

    return (
      <div>
        <UserLink user={author}>
          <UserAvatar user={author} />
        </UserLink>

        <h1>{article.title}</h1>
        <p>{article.text}</p>

        <p>Read more by <UserLink user={author} />.</p>
      </div>
    )
  }
});

var UserAvatar = React.createClass({
  propTypes: {
    user: PropTypes.object.isRequired
  },

  render() {
    var user = this.props.user;

    return (
      <img src={user.thumbnailUrl} />
    )
  }
});

var UserLink = React.createClass({
  propTypes: {
    user: PropTypes.object.isRequired
  },

  render() {
    var user = this.props.user;

    return (
      <Link to='user' params={{ userId: this.props.user.id }}>
        {this.props.children || user.name}
      </Link>
    )
  }
});

이것은 가장 안쪽의 구성 요소를 어리석게 유지하지만 최상위 구성 요소에서 지옥을 복잡하게 만들지 않습니다.


이 답변 위에 관점을 추가하십시오. 개념적으로, 소유권 개념을 개발할 수 있습니다. 데이터 수집이 주어지면 실제로 데이터를 소유 한 사람 (따라서 저장소에 귀를 기울임)을 가장 많이 볼 수 있습니다. 몇 가지 예 : 1. AuthData는 App Component에서 소유해야합니다. Auth의 변경 사항은 App Component에 의해 모든 하위에 props로 전파되어야합니다. 2. ArticleData는 답변에서 제안 된대로 ArticlePage 또는 기사 목록이 소유해야합니다. 이제 ArticleList의 모든 자식은 실제로 해당 데이터를 가져온 구성 요소에 관계없이 ArticleList에서 AuthData 및 / 또는 ArticleData를받을 수 있습니다.
Saumitra R. Bhave

2

내 솔루션은 훨씬 간단합니다. 자체 상태를 가진 모든 구성 요소는 상점과 대화하고 청취 할 수 있습니다. 이들은 매우 컨트롤러와 유사한 구성 요소입니다. 상태를 유지하지 않고 렌더링 만하는 더 깊은 중첩 구성 요소는 허용되지 않습니다. 그들은 순수한 렌더링, 매우보기와 같은 소품 만받습니다.

이렇게하면 모든 것이 상태 저장 구성 요소에서 상태 비 저장 구성 요소로 흐릅니다. Stateful을 낮게 유지합니다.

귀하의 경우 Article은 상태 저장이므로 상점 및 UserLink 등과 대화하면 article.user를 prop으로 수신 할 수 있도록 렌더링됩니다.


1
기사에 사용자 객체 속성이 있으면 이것이 작동한다고 생각하지만 제 경우에는 userId 만 있습니다 (실제 사용자는 UserStore에 저장되기 때문입니다). 아니면 내가 당신의 요지를 놓치고 있습니까? 예를 들어 보시겠습니까?
Dan Abramov

기사에서 사용자에 대한 가져 오기를 시작합니다. 또는 사용자 ID를 "확장 가능"하도록 API 백엔드를 변경하여 사용자를 한 번에 얻을 수 있습니다. 어느 쪽이든 더 깊은 중첩 된 구성 요소를 단순보기로 유지하고 소품에만 의존합니다.
Rygu 2014 년

2
함께 렌더링되어야하므로 기사에서 가져 오기를 시작할 여유가 없습니다. 확장 가능한 API의 경우 이전 에는 사용 했지만 Store에서 구문 분석하기에는 매우 문제가 있습니다. 나는 바로 이런 이유로 응답을 평탄화하기 위해 라이브러리 를 작성 했습니다 .
Dan Abramov

0

두 가지 철학에 설명 된 문제는 모든 단일 페이지 응용 프로그램에 공통적입니다.

https://www.youtube.com/watch?v=IrgHurBjQbg 및 Relay ( https://facebook.github.io/relay )는 이 비디오에서 간략하게 설명합니다. 설명하는 트레이드 오프를 극복하기 위해 Facebook에서 개발했습니다.

Relay의 접근 방식은 매우 데이터 중심적입니다. "서버에 대한 한 번의 쿼리에서이 뷰의 각 구성 요소에 필요한 데이터를 어떻게 얻습니까?"라는 질문에 대한 대답입니다. 동시에 Relay는 구성 요소가 여러 뷰에서 사용되는 경우 코드간에 결합이 거의 없도록합니다.

Relay가 옵션이 아닌 경우 "All entity components read their own data"는 설명하는 상황에 대해 더 나은 접근 방식으로 보입니다. Flux의 오해는 가게가 무엇인지라고 생각합니다. 상점의 개념은 모델이나 물건 모음이 보관되는 장소가 아닙니다. 스토어는 뷰가 렌더링되기 전에 애플리케이션이 데이터를 저장하는 임시 장소입니다. 이들이 존재하는 진짜 이유는 서로 다른 저장소에있는 데이터에 대한 종속성 문제를 해결하는 것입니다.

Flux가 지정하지 않는 것은 매장이 모델 및 객체 컬렉션 (백본)의 개념과 관련되는 방식입니다. 그런 의미에서 일부 사람들은 실제로 플럭스 스토어를 전체 시간 동안 플러시되지 않는 특정 유형의 객체 컬렉션을 놓을 장소로 만들고 있습니다. 사용자는 브라우저를 열어 두지 만 플럭스를 이해하기 때문에 그것은 스토어가 아닙니다. 있어야합니다.

해결책은 뷰를 렌더링하는 데 필요한 엔터티 (잠재적으로 더 많은 항목)가 저장되고 업데이트 된 상태로 유지되는 다른 레이어를 갖는 것입니다. 이 계층이 모델과 컬렉션을 추상화하는 경우 하위 구성 요소가 자체 데이터를 얻기 위해 다시 쿼리해야하는 경우 문제가되지 않습니다.


1
내가 이해하는 한 Dan의 접근 방식은 다른 유형의 객체를 유지하고 ID를 통해 참조하면 해당 객체의 캐시를 유지하고 한곳에서만 업데이트 할 수 있습니다. 예를 들어 동일한 사용자를 참조하는 기사가 여러 개 있고 사용자 이름이 업데이트되면 어떻게이 작업을 수행 할 수 있습니까? 유일한 방법은 새로운 사용자 데이터로 모든 기사를 업데이트하는 것입니다. 이것은 백엔드를 위해 문서와 관계형 db 중에서 선택할 때 발생하는 것과 똑같은 문제입니다. 그것에 대해 어떻게 생각하십니까? 감사합니다.
Alex Ponomarev 2015
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.