Các thành phần nên đọc các thực thể từ Stores trong Flux ở mức lồng nào?


89

Tôi đang viết lại ứng dụng của mình để sử dụng Flux và tôi gặp sự cố khi truy xuất dữ liệu từ Stores. Tôi có rất nhiều thành phần, và chúng lồng vào nhau rất nhiều. Một số trong số chúng lớn ( Article), một số nhỏ và đơn giản ( UserAvatar, UserLink).

Tôi đang đấu tranh với việc tôi nên đọc dữ liệu từ các Cửa hàng ở đâu trong hệ thống phân cấp thành phần.
Tôi đã thử hai cách tiếp cận cực đoan, cả hai đều không thích:

Tất cả các thành phần thực thể đọc dữ liệu của riêng chúng

Mỗi thành phần cần một số dữ liệu từ Store chỉ nhận ID thực thể và tự truy xuất thực thể.
Ví dụ, Articleđược thông qua articleId, UserAvatarUserLinkđược thông qua userId.

Cách tiếp cận này có một số nhược điểm đáng kể (được thảo luận dưới mẫu mã).

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>
    )
  }
});

Nhược điểm của phương pháp này:

  • Thật khó chịu khi có các thành phần 100 có khả năng đăng ký Cửa hàng;
  • Thật khó để theo dõi dữ liệu được cập nhật như thế nào và theo thứ tự nào vì mỗi thành phần lấy dữ liệu của nó một cách độc lập;
  • Mặc dù bạn có thể đã có một thực thể ở trạng thái, bạn buộc phải chuyển ID của nó cho trẻ em, những người này sẽ truy xuất lại nó (hoặc nếu không sẽ phá vỡ tính nhất quán).

Tất cả dữ liệu được đọc một lần ở cấp cao nhất và được chuyển đến các thành phần

Khi tôi mệt mỏi với việc theo dõi lỗi, tôi đã cố gắng đặt tất cả dữ liệu truy xuất ở cấp cao nhất. Tuy nhiên, điều này được chứng minh là không thể vì đối với một số thực thể, tôi có một số cấp độ lồng vào nhau.

Ví dụ:

  • A Categorychứa UserAvatarnhững người đóng góp cho danh mục đó;
  • An Articlecó thể có một số Categorys.

Do đó, nếu tôi muốn truy xuất tất cả dữ liệu từ Cửa hàng ở cấp độ an Article, tôi cần phải:

  • Lấy bài báo từ ArticleStore;
  • Truy xuất tất cả các danh mục của bài viết từ CategoryStore ;
  • Truy xuất riêng từng cộng tác viên của danh mục từ UserStore ;
  • Bằng cách nào đó, truyền tất cả dữ liệu đó xuống các thành phần.

Đáng buồn hơn nữa, bất cứ khi nào tôi cần một thực thể lồng nhau sâu sắc, tôi sẽ cần thêm mã vào mỗi cấp độ lồng để bổ sung chuyển nó xuống.

Tổng hợp

Cả hai cách tiếp cận đều có vẻ thiếu sót. Làm cách nào để giải quyết vấn đề này một cách thanh lịch nhất?

Mục tiêu của tôi:

  • Các cửa hàng không nên có số lượng người đăng ký quá lớn. Thật ngu ngốc cho mỗi người UserLinknghe UserStorenếu các thành phần cha đã làm điều đó.

  • Nếu thành phần mẹ đã truy xuất một số đối tượng từ cửa hàng (ví dụ user), tôi không muốn bất kỳ thành phần lồng nhau nào phải tìm nạp lại. Tôi sẽ có thể vượt qua nó thông qua đạo cụ.

  • Tôi không cần phải tìm nạp tất cả các thực thể (bao gồm cả các mối quan hệ) ở cấp cao nhất vì việc thêm hoặc xóa các mối quan hệ sẽ phức tạp. Tôi không muốn giới thiệu các đạo cụ mới ở tất cả các cấp lồng nhau mỗi khi một thực thể lồng nhau nhận được một mối quan hệ mới (ví dụ: danh mục nhận được a curator).


1
Cảm ơn vì đã đăng bài này. Tôi vừa đăng một câu hỏi tương tự ở đây: groups.google.com/forum/… Mọi thứ tôi thấy đều xử lý cấu trúc dữ liệu phẳng chứ không phải dữ liệu liên kết. Tôi ngạc nhiên khi không thấy bất kỳ phương pháp hay nhất nào về điều này.
uberllama

Câu trả lời:


37

Hầu hết mọi người bắt đầu bằng cách lắng nghe các cửa hàng có liên quan trong thành phần chế độ xem bộ điều khiển ở gần đầu phân cấp.

Sau đó, khi có vẻ như nhiều đạo cụ không liên quan đang được chuyển qua hệ thống phân cấp cho một số thành phần lồng ghép sâu, một số người sẽ quyết định rằng nên để một thành phần sâu hơn lắng nghe những thay đổi trong cửa hàng. Điều này cung cấp một sự đóng gói tốt hơn của miền vấn đề mà nhánh sâu hơn của cây thành phần hướng tới. Có những lập luận xác đáng để thực hiện việc này một cách thận trọng.

Tuy nhiên, tôi thích luôn lắng nghe ở trên cùng và chỉ cần truyền lại tất cả dữ liệu. Đôi khi tôi thậm chí sẽ lấy toàn bộ trạng thái của cửa hàng và chuyển nó qua hệ thống phân cấp như một đối tượng duy nhất và tôi sẽ làm điều này cho nhiều cửa hàng. Vì vậy, tôi sẽ có một chỗ dựa cho ArticleStoretrạng thái của 'và một chỗ khác cho UserStoretrạng thái của', v.v. Tôi thấy rằng việc tránh các chế độ xem bộ điều khiển lồng nhau sâu sẽ duy trì một điểm nhập duy nhất cho dữ liệu và thống nhất luồng dữ liệu. Nếu không, tôi có nhiều nguồn dữ liệu và điều này có thể trở nên khó gỡ lỗi.

Việc kiểm tra kiểu khó khăn hơn với chiến lược này, nhưng bạn có thể thiết lập một "hình dạng", hoặc mẫu kiểu, cho đối tượng lớn-as-prop với PropTypes của React. Xem: https://github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91 http://facebook.github.io/react/docs/reusable-components.html#prop -Thẩm định

Lưu ý rằng bạn có thể muốn đặt logic liên kết dữ liệu giữa các cửa hàng trong chính các cửa hàng. Vì vậy, bạn ArticleStoresức waitFor()các UserStore, và bao gồm những người sử dụng có liên quan với tất cả các Articlekỷ lục nó cung cấp thông qua getArticles(). Làm điều này trong các khung nhìn của bạn nghe giống như đẩy logic vào lớp xem, đây là một thực hành bạn nên tránh bất cứ khi nào có thể.

Bạn cũng có thể bị cám dỗ để sử dụng transferPropsTo()và nhiều người thích làm điều này, nhưng tôi thích giữ mọi thứ rõ ràng để dễ đọc và do đó có thể bảo trì.

FWIW, sự hiểu biết của tôi là David Nolen có cách tiếp cận tương tự với khung công tác Om của anh ấy ( hơi tương thích với Flux ) với một điểm nhập dữ liệu duy nhất trên nút gốc - điều tương đương trong Flux sẽ là chỉ có một chế độ xem bộ điều khiển nghe tất cả các cửa hàng. Điều này được thực hiện hiệu quả bằng cách sử dụng shouldComponentUpdate()và cấu trúc dữ liệu bất biến có thể được so sánh bằng cách tham chiếu, với ===. Đối với cấu trúc dữ liệu bất biến, hãy xem David's mori hoặc Immutable-js của Facebook . Kiến thức hạn chế của tôi về Om chủ yếu đến từ Tương lai của JavaScript MVC Framework


4
Cảm ơn bạn đã trả lời chi tiết! Tôi đã xem xét chuyển xuống toàn bộ ảnh chụp nhanh của trạng thái cửa hàng. Tuy nhiên, điều này có thể khiến tôi gặp sự cố vì mỗi bản cập nhật của UserStore sẽ khiến tất cả các Bài viết trên màn hình được hiển thị lại và PureRenderMixin sẽ không thể biết bài nào cần được cập nhật và bài nào không. Đối với đề xuất thứ hai của bạn, tôi cũng đã nghĩ về nó, nhưng tôi không thể thấy làm thế nào tôi có thể giữ bình đẳng tham chiếu bài viết nếu người dùng của bài viết được "tiêm" vào mỗi lệnh gọi getArticles (). Điều này một lần nữa có khả năng mang lại cho tôi các vấn đề về hiệu suất.
Dan Abramov

1
Tôi thấy quan điểm của bạn, nhưng có giải pháp. Đối với một, bạn có thể kiểm tra shouldComponentUpdate () nếu một loạt các ID người dùng cho một bài báo đã thay đổi, về cơ bản, việc này chỉ thực hiện thủ công các chức năng và hành vi bạn thực sự muốn trong PureRenderMixin trong trường hợp cụ thể này.
fishwebdev

3
Đúng vậy, và dù sao tôi sẽ chỉ cần phải làm điều đó khi tôi chắc chắn vẫn còn vấn đề về hiệu suất mà không phải là trường hợp cho các cửa hàng rằng bản cập nhật nhiều lần trong một phút tối đa. Cám ơn bạn một lần nữa!
Dan Abramov

8
Tùy thuộc vào loại ứng dụng bạn đang xây dựng, việc giữ một điểm vào duy nhất sẽ có hại ngay khi bạn bắt đầu có nhiều "widget" trên màn hình không trực tiếp thuộc cùng một gốc. Nếu bạn cố gắng thực hiện theo cách đó, nút gốc đó sẽ có quá nhiều trách nhiệm. KISS và SOLID, mặc dù đó là phía khách hàng.
Rygu

37

Cách tiếp cận mà tôi đến là mỗi thành phần nhận dữ liệu của nó (không phải ID) làm chỗ dựa. Nếu một số thành phần lồng nhau cần một thực thể liên quan, thì thành phần mẹ có quyền truy xuất nó.

Trong ví dụ của chúng ta, Articlenên có một chỗ articledựa là một đối tượng (có lẽ được truy xuất bởi ArticleListhoặc ArticlePage).

Bởi vì Articlecũng muốn kết xuất UserLinkUserAvatarcho tác giả của bài viết, nó sẽ đăng ký UserStorevà giữ author: UserStore.get(article.authorId)ở trạng thái của nó. Sau đó nó sẽ hiển thị UserLinkUserAvatarvới điều này this.state.author. Nếu họ muốn truyền lại nó xa hơn, họ có thể. Không có thành phần con nào cần truy xuất lại người dùng này.

Để nhắc lại:

  • Không có thành phần nào nhận được ID làm chỗ dựa; tất cả các thành phần nhận được các đối tượng tương ứng của chúng.
  • Nếu các thành phần con cần một thực thể, cha mẹ có trách nhiệm truy xuất nó và chuyển như một chỗ dựa.

Điều này giải quyết vấn đề của tôi khá tốt. Ví dụ mã được viết lại để sử dụng phương pháp này:

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>
    )
  }
});

Điều này khiến các thành phần trong cùng trở nên ngu ngốc nhưng không buộc chúng ta phải phức tạp hóa các thành phần cấp cao nhất.


Chỉ để thêm một góc nhìn trên đầu câu trả lời này. Về mặt khái niệm, bạn có thể phát triển khái niệm về quyền sở hữu, Đưa ra một Bộ sưu tập dữ liệu hoàn thiện ở chế độ xem trên cùng, những người thực sự sở hữu dữ liệu (do đó lắng nghe cửa hàng của nó). Một vài ví dụ: 1. AuthData nên được sở hữu tại App Component, bất kỳ thay đổi nào trong Auth phải được truyền tải dưới dạng đạo cụ cho tất cả các phần tử con của App Component. 2. ArticleData nên thuộc sở hữu của ArticlePage hoặc Article List như được Đề xuất trong câu trả lời. giờ đây, bất kỳ phần tử con nào của ArticleList đều có thể nhận AuthData và / hoặc ArticleData từ ArticleList bất kể thành phần nào thực sự tìm nạp dữ liệu đó.
Saumitra R. Bhave

2

Giải pháp của tôi đơn giản hơn nhiều. Mọi thành phần có trạng thái riêng đều được phép nói chuyện và lắng nghe các cửa hàng. Đây là những thành phần rất giống bộ điều khiển. Các thành phần lồng nhau sâu hơn không duy trì trạng thái mà chỉ hiển thị nội dung không được phép. Họ chỉ nhận được các đạo cụ để kết xuất thuần túy, rất giống chế độ xem.

Bằng cách này, mọi thứ chuyển từ các thành phần trạng thái sang các thành phần không trạng thái. Giữ số lượng trạng thái thấp.

Trong trường hợp của bạn, Article sẽ ở trạng thái rõ ràng và do đó các cuộc trao đổi với các cửa hàng và UserLink, v.v. sẽ chỉ hiển thị để nó nhận article.user làm chỗ dựa.


1
Tôi cho rằng điều này sẽ hoạt động nếu bài viết có thuộc tính đối tượng người dùng, nhưng trong trường hợp của tôi, nó chỉ có userId (vì người dùng thực được lưu trữ trong UserStore). Hay là tôi thiếu điểm của bạn? Bạn có thể chia sẻ một ví dụ?
Dan Abramov

Bắt đầu tìm nạp cho người dùng trong bài viết. Hoặc thay đổi chương trình phụ trợ API của bạn để làm cho id người dùng "có thể mở rộng" để bạn có được người dùng ngay lập tức. Dù bằng cách nào, hãy giữ cho các thành phần lồng nhau sâu hơn chế độ xem đơn giản và chỉ dựa vào đạo cụ.
Rygu

2
Tôi không đủ khả năng để bắt đầu tìm nạp trong bài viết vì nó cần được kết xuất cùng nhau. Đối với các API mở rộng, chúng tôi sử dụng để có họ, nhưng họ rất có vấn đề để phân tích từ Cửa hàng. Tôi thậm chí đã viết một thư viện để làm phẳng các phản hồi vì lý do này.
Dan Abramov

0

Các vấn đề được mô tả trong 2 triết lý của bạn là phổ biến đối với bất kỳ ứng dụng trang đơn nào.

Chúng được thảo luận ngắn gọn trong video này: https://www.youtube.com/watch?v=IrgHurBjQbg và Tiếp sức ( https://facebook.github.io/relay ) được Facebook phát triển để vượt qua sự đánh đổi mà bạn mô tả.

Cách tiếp cận của Relay rất tập trung vào dữ liệu. Đây là câu trả lời cho câu hỏi "Làm cách nào để chỉ lấy dữ liệu cần thiết cho từng thành phần trong chế độ xem này trong một truy vấn tới máy chủ?" Và đồng thời Relay đảm bảo rằng bạn có ít khớp nối trên mã khi một thành phần được sử dụng trong nhiều chế độ xem.

Nếu Relay không phải là một tùy chọn , thì "Tất cả các thành phần thực thể đọc dữ liệu của riêng chúng" có vẻ là một cách tiếp cận tốt hơn đối với tình huống bạn mô tả. Tôi nghĩ rằng quan niệm sai lầm trong Flux là cửa hàng là gì. Khái niệm cửa hàng tồn tại không phải là nơi lưu giữ một mô hình hoặc một bộ sưu tập các đối tượng. Cửa hàng là nơi tạm thời nơi ứng dụng của bạn đặt dữ liệu trước khi hiển thị. Lý do thực sự mà chúng tồn tại là để giải quyết vấn đề phụ thuộc giữa các dữ liệu đi trong các cửa hàng khác nhau.

Điều mà Flux không chỉ rõ là cách một cửa hàng liên quan đến khái niệm mô hình và tập hợp các đối tượng (a la Backbone). Theo nghĩa đó, một số người thực sự đang làm cho một cửa hàng flux trở thành một nơi để đặt tập hợp các đối tượng thuộc một loại cụ thể mà không phải lưu trữ trong suốt thời gian người dùng giữ trình duyệt mở nhưng theo tôi hiểu thì flux không phải là một cửa hàng. được coi là.

Giải pháp là có một lớp khác nơi bạn lưu trữ và cập nhật các thực thể cần thiết để hiển thị chế độ xem của bạn (và có thể nhiều hơn nữa). Nếu bạn lớp này trừu tượng hóa các mô hình và bộ sưu tập, sẽ không có vấn đề gì nếu bạn các thành phần con phải truy vấn lại để lấy dữ liệu của riêng chúng.


1
Theo như tôi hiểu thì cách tiếp cận của Dan giữ các loại đối tượng khác nhau và tham chiếu chúng thông qua id cho phép chúng tôi lưu giữ bộ nhớ cache của các đối tượng đó và chỉ cập nhật chúng ở một nơi. Làm thế nào điều này có thể đạt được nếu, giả sử, bạn có một số Bài báo tham chiếu đến cùng một người dùng và sau đó tên người dùng được cập nhật? Cách duy nhất là cập nhật tất cả các Bài báo với dữ liệu người dùng mới. Đây chính xác là vấn đề bạn gặp phải khi chọn giữa tài liệu và db quan hệ cho chương trình phụ trợ của mình. Bạn nghĩ gì về điều đó? Cảm ơn bạn.
Alex Ponomarev
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.