ReactJS Hai thành phần giao tiếp


321

Tôi mới bắt đầu với ReactJS và có chút bế tắc về một vấn đề mà tôi gặp phải.

Ứng dụng của tôi về cơ bản là một danh sách với các bộ lọc và nút để thay đổi bố cục. Hiện nay tôi đang sử dụng ba thành phần: <list />, < Filters /><TopBar />, bây giờ rõ ràng là khi tôi thay đổi các thiết lập trong < Filters />tôi muốn kích hoạt một số phương pháp trong <list />để cập nhật quan điểm của tôi.

Làm cách nào tôi có thể làm cho 3 thành phần đó tương tác với nhau hoặc tôi cần một mô hình dữ liệu toàn cầu nào đó để tôi có thể thay đổi?


Có phải tất cả ba thành phần anh chị em hoặc là một trong những thành phần khác?
pundy2

Chúng là cả ba thành phần, tôi đã sắp xếp lại ứng dụng của mình để bây giờ tất cả chúng đều có cùng cha mẹ có thể cung cấp cho chúng dữ liệu.
woutr_be

4
Đây là nơi bạn có thể sử dụng mô hình thông lượng hoặc pubsub. Dựa trên các tài liệu trong các tài liệu phản ứng, họ để lại một câu hơi mơ hồ: "Để liên lạc giữa hai thành phần không có mối quan hệ cha-con, bạn có thể thiết lập hệ thống sự kiện toàn cầu của riêng mình." facebook.github.io/react/tips/ từ
BingeBoy 3/03/2015

@BingeBoy đúng Flux là cách tuyệt vời để viết các ứng dụng Reacjs, có thể xử lý vấn đề về luồng dữ liệu, chia sẻ dữ liệu bởi nhiều thành phần.
Ankit Patial

4
Nếu bạn không muốn vào Flux hoặc Redux, đây là một bài viết tuyệt vời về chủ đề này andrewhfarmer.com/component-c truyền thông
garajo

Câu trả lời:


318

Cách tiếp cận tốt nhất sẽ phụ thuộc vào cách bạn định sắp xếp các thành phần đó. Một vài ví dụ điển hình xuất hiện trong tâm trí ngay bây giờ:

  1. <Filters /> là một thành phần con của <List />
  2. Cả hai <Filters /><List />là con của một thành phần cha mẹ
  3. <Filters /><List />sống trong các thành phần gốc riêng biệt hoàn toàn.

Có thể có những kịch bản khác mà tôi không nghĩ đến. Nếu bạn không phù hợp với những điều này, thì hãy cho tôi biết. Dưới đây là một số ví dụ rất sơ lược về cách tôi đã xử lý hai tình huống đầu tiên:

Cảnh 1

Bạn có thể chuyển một trình xử lý từ <List />đến <Filters />, sau đó có thể được gọi trong onChangesự kiện để lọc danh sách với giá trị hiện tại.

JSFiddle cho # 1 →

/** @jsx React.DOM */

var Filters = React.createClass({
  handleFilterChange: function() {
    var value = this.refs.filterInput.getDOMNode().value;
    this.props.updateFilter(value);
  },
  render: function() {
    return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
  }
});

var List = React.createClass({
  getInitialState: function() {
    return {
      listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
      nameFilter: ''
    };
  },
  handleFilterUpdate: function(filterValue) {
    this.setState({
      nameFilter: filterValue
    });
  },
  render: function() {
    var displayedItems = this.state.listItems.filter(function(item) {
      var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
      return (match !== -1);
    }.bind(this));

    var content;
    if (displayedItems.length > 0) {
      var items = displayedItems.map(function(item) {
        return <li>{item}</li>;
      });
      content = <ul>{items}</ul>
    } else {
      content = <p>No items matching this filter</p>;
    }

    return (
      <div>
        <Filters updateFilter={this.handleFilterUpdate} />
        <h4>Results</h4>
        {content}
      </div>
    );
  }
});

React.renderComponent(<List />, document.body);

Kịch bản # 2

Tương tự như kịch bản # 1, nhưng thành phần cha mẹ sẽ là hàm truyền xuống hàm xử lý <Filters />và sẽ chuyển danh sách đã lọc sang <List />. Tôi thích phương pháp này tốt hơn vì nó tách ra <List />từ <Filters />.

JSFiddle cho # 2 →

/** @jsx React.DOM */

var Filters = React.createClass({
  handleFilterChange: function() {
    var value = this.refs.filterInput.getDOMNode().value;
    this.props.updateFilter(value);
  },
  render: function() {
    return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
  }
});

var List = React.createClass({
  render: function() {
    var content;
    if (this.props.items.length > 0) {
      var items = this.props.items.map(function(item) {
        return <li>{item}</li>;
      });
      content = <ul>{items}</ul>
    } else {
      content = <p>No items matching this filter</p>;
    }
    return (
      <div className="results">
        <h4>Results</h4>
        {content}
      </div>
    );
  }
});

var ListContainer = React.createClass({
  getInitialState: function() {
    return {
      listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
      nameFilter: ''
    };
  },
  handleFilterUpdate: function(filterValue) {
    this.setState({
      nameFilter: filterValue
    });
  },
  render: function() {
    var displayedItems = this.state.listItems.filter(function(item) {
      var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
      return (match !== -1);
    }.bind(this));

    return (
      <div>
        <Filters updateFilter={this.handleFilterUpdate} />
        <List items={displayedItems} />
      </div>
    );
  }
});

React.renderComponent(<ListContainer />, document.body);

Kịch bản # 3

Khi các thành phần không thể giao tiếp giữa bất kỳ mối quan hệ cha-con nào, tài liệu khuyên bạn nên thiết lập một hệ thống sự kiện toàn cầu .


6
Những điều tốt đẹp với # 2 là họ chỉ dựa vào một phụ huynh đi một chỗ dựa để từng thành phần: một chức năng như updateFilterđể <Filters />và một mảng như itemsđể <List />. Bạn có thể sử dụng các thành phần con đó trong các bậc cha mẹ khác với các hành vi khác nhau, cùng nhau hoặc độc tấu. Ví dụ: nếu bạn muốn hiển thị danh sách động nhưng không cần lọc.
Michael LaCroix

2
@woutr_be Không chắc nó có phù hợp với yêu cầu của bạn không nhưng khi gặp tình huống tương tự, chúng tôi đã sử dụng hai hàm sau để sắp xếp giao tiếp giữa các thành phần con và cha mẹ: - listTo: function (eventName, eventCallback) {$ ( window.document) .bind (eventName, eventCallback);} triggerEvent: function (eventName, params) {$ .event.trigger (eventName, params);} Hy vọng nó sẽ giúp! (xin lỗi không thể định dạng nó tốt hơn)
5122014009

29
Đối với kịch bản 3, có một cách tiếp cận được đề xuất? Bất kỳ tài liệu hoặc ví dụ về điều này bằng cách tạo ra các sự kiện tổng hợp tùy chỉnh? Tôi đã không tìm thấy bất cứ điều gì trong các tài liệu chính.
pwray

1
Kịch bản # 2 nhiều ý nghĩa ... cho đến khi bạn phải gây nguy hiểm cho thiết kế (nếu chỉ là Bố cục) - thì bạn nhận ra sự cần thiết cho EventHub / PubSub.
Cody

4
Liên kết # 3 kịch bản đã chết và chuyển hướng đến một trang tài liệu React không liên quan ngay bây giờ.
beporter

170

Có nhiều cách để làm cho các thành phần giao tiếp. Một số có thể phù hợp với usecase của bạn. Dưới đây là danh sách một số tôi thấy hữu ích để biết.

Phản ứng

Giao tiếp trực tiếp giữa phụ huynh / trẻ em

const Child = ({fromChildToParentCallback}) => (
  <div onClick={() => fromChildToParentCallback(42)}>
    Click me
  </div>
);

class Parent extends React.Component {
  receiveChildValue = (value) => {
    console.log("Parent received value from child: " + value); // value is 42
  };
  render() {
    return (
      <Child fromChildToParentCallback={this.receiveChildValue}/>
    )
  }
}

Ở đây, thành phần con sẽ gọi một cuộc gọi lại được cung cấp bởi cha mẹ với một giá trị và cha mẹ sẽ có thể nhận được giá trị được cung cấp bởi con cái trong cha mẹ.

Nếu bạn xây dựng một tính năng / trang của ứng dụng của bạn, nó tốt hơn để có một người mẹ độc thân quản lý callbacks / nhà nước (còn gọi là containerhay smart component), và tất cả Childs được không quốc tịch, chỉ có báo cáo điều cần phụ huynh. Bằng cách này, bạn có thể dễ dàng "chia sẻ" trạng thái của cha mẹ cho bất kỳ đứa trẻ nào cần nó.


Bối cảnh

React Context cho phép giữ trạng thái ở gốc của hệ thống phân cấp thành phần của bạn và có thể dễ dàng đưa trạng thái này vào các thành phần được lồng rất sâu, mà không gặp rắc rối khi phải truyền đạo cụ cho mọi thành phần trung gian.

Cho đến hiện tại, bối cảnh là một tính năng thử nghiệm, nhưng API mới có sẵn trong React 16.3.

const AppContext = React.createContext(null)

class App extends React.Component {
  render() {
    return (
      <AppContext.Provider value={{language: "en",userId: 42}}>
        <div>
          ...
          <SomeDeeplyNestedComponent/>
          ...
        </div>
      </AppContext.Provider>
    )
  }
};

const SomeDeeplyNestedComponent = () => (
  <AppContext.Consumer>
    {({language}) => <div>App language is currently {language}</div>}
  </AppContext.Consumer>
);

Người tiêu dùng đang sử dụng mẫu chức năng render prop / children

Kiểm tra bài đăng trên blog này để biết thêm chi tiết.

Trước React 16.3, tôi khuyên bạn nên sử dụng phản ứng phát sóng cung cấp API khá giống nhau và sử dụng API ngữ cảnh cũ.


Cổng thông tin

Sử dụng cổng thông tin khi bạn muốn giữ 2 thành phần gần nhau để khiến chúng giao tiếp với các chức năng đơn giản, như trong cha mẹ / con bình thường, nhưng bạn không muốn 2 thành phần này có mối quan hệ cha / con trong DOM, bởi vì các ràng buộc trực quan / CSS mà nó ngụ ý (như z-index, opacity ...).

Trong trường hợp này, bạn có thể sử dụng một "cổng thông tin". Có khác nhau phản ứng thư viện sử dụng cổng thông tin , thường được sử dụng cho modals , quảng cáo, tooltips ...

Hãy xem xét những điều sau đây:

<div className="a">
    a content
    <Portal target="body">
        <div className="b">
            b content
        </div>
    </Portal>
</div>

Có thể tạo DOM sau khi được hiển thị bên trong reactAppContainer:

<body>
    <div id="reactAppContainer">
        <div className="a">
             a content
        </div>
    </div>
    <div className="b">
         b content
    </div>
</body>

Thêm chi tiết tại đây


Máy đánh bạc

Bạn xác định một vị trí ở đâu đó và sau đó bạn điền vào vị trí đó từ một nơi khác trong cây kết xuất của bạn.

import { Slot, Fill } from 'react-slot-fill';

const Toolbar = (props) =>
  <div>
    <Slot name="ToolbarContent" />
  </div>

export default Toolbar;

export const FillToolbar = ({children}) =>
  <Fill name="ToolbarContent">
    {children}
  </Fill>

Điều này hơi giống với các cổng ngoại trừ nội dung được điền sẽ được hiển thị trong một vị trí bạn xác định, trong khi các cổng thường hiển thị một nút dom mới (thường là con của document.body)

Kiểm tra thư viện phản ứng-slot-fill


Xe buýt sự kiện

Như đã nêu trong tài liệu React :

Để liên lạc giữa hai thành phần không có mối quan hệ cha-con, bạn có thể thiết lập hệ thống sự kiện toàn cầu của riêng mình. Theo dõi các sự kiện trong thành phầnDidMount (), hủy đăng ký thành phầnWillUnmount () và gọi setState () khi bạn nhận được một sự kiện.

Có nhiều thứ bạn có thể sử dụng để thiết lập một xe buýt sự kiện. Bạn chỉ có thể tạo một mảng người nghe và trên xuất bản sự kiện, tất cả người nghe sẽ nhận được sự kiện. Hoặc bạn có thể sử dụng một cái gì đó như EventEuctor hoặc PostalJs


Tuôn ra

Flux về cơ bản là một xe buýt sự kiện, ngoại trừ người nhận sự kiện là các cửa hàng. Điều này tương tự như hệ thống xe buýt sự kiện cơ bản ngoại trừ trạng thái được quản lý bên ngoài React

Việc triển khai Flux ban đầu trông giống như một nỗ lực để thực hiện tìm nguồn cung ứng Sự kiện theo cách hacky.

Redux đối với tôi là triển khai Flux gần nhất với nguồn cung cấp sự kiện, một lợi ích của nhiều lợi thế tìm nguồn cung ứng sự kiện như khả năng du hành thời gian. Nó không được liên kết chặt chẽ với React và cũng có thể được sử dụng với các thư viện xem chức năng khác.

Video hướng dẫn Redux của Egghead thực sự rất hay và giải thích cách thức hoạt động bên trong (nó thực sự đơn giản).


Con trỏ

Các con trỏ đến từ ClojureScript / Om và được sử dụng rộng rãi trong các dự án React. Chúng cho phép quản lý trạng thái bên ngoài React và để nhiều thành phần có quyền truy cập đọc / ghi vào cùng một phần của trạng thái mà không cần biết gì về cây thành phần.

Nhiều triển khai tồn tại, bao gồm ImmutableJS , React-cursorsOmniscient

Chỉnh sửa 2016 : có vẻ như mọi người đồng ý rằng con trỏ hoạt động tốt cho các ứng dụng nhỏ hơn nhưng nó không mở rộng tốt trên các ứng dụng phức tạp. Om Next không còn con trỏ nữa (trong khi đó Om đã giới thiệu khái niệm ban đầu)


Kiến trúc Elm

Các kiến trúc Elm là một kiến trúc được đề nghị sử dụng bởi các ngôn ngữ Elm . Ngay cả khi Elm không phải là ReactJS, kiến ​​trúc Elm cũng có thể được thực hiện trong React.

Dan Abramov, tác giả của Redux, đã thực hiện kiến trúc Elm bằng React.

Cả Redux và Elm đều thực sự tuyệt vời và có xu hướng trao quyền cho các khái niệm tìm nguồn cung ứng sự kiện trên frontend, cả hai đều cho phép gỡ lỗi du hành thời gian, hoàn tác / làm lại, phát lại ...

Sự khác biệt chính giữa Redux và Elm là Elm có xu hướng nghiêm ngặt hơn rất nhiều về quản lý nhà nước. Trong Elm, bạn không thể có trạng thái thành phần cục bộ hoặc móc nối / ngắt kết nối và tất cả các thay đổi DOM phải được kích hoạt bởi các thay đổi trạng thái toàn cầu. Kiến trúc Elm đề xuất một cách tiếp cận có thể mở rộng cho phép xử lý TẤT CẢ trạng thái bên trong một đối tượng bất biến duy nhất, trong khi Redux đề xuất một phương pháp mời bạn xử lý MOST của trạng thái trong một đối tượng bất biến duy nhất.

Mặc dù mô hình khái niệm của Elm rất thanh lịch và kiến ​​trúc cho phép mở rộng tốt trên các ứng dụng lớn, nhưng thực tế nó có thể khó khăn hoặc liên quan đến nhiều nồi hơi hơn để đạt được các nhiệm vụ đơn giản như tập trung vào đầu vào sau khi cài đặt hoặc tích hợp với thư viện hiện có với giao diện bắt buộc (tức là plugin JQuery). Vấn đề liên quan .

Ngoài ra, kiến ​​trúc Elm liên quan đến nhiều mẫu mã hơn. Nó không dài dòng hay phức tạp để viết nhưng tôi nghĩ kiến ​​trúc Elm phù hợp hơn với các ngôn ngữ gõ tĩnh.


FRP

Các thư viện như RxJS, BaconJS hoặc Kefir có thể được sử dụng để tạo ra các luồng FRP để xử lý giao tiếp giữa các thành phần.

Bạn có thể thử ví dụ Rx-React

Tôi nghĩ rằng việc sử dụng các lib này khá giống với việc sử dụng những gì mà ngôn ngữ ELM cung cấp với các tín hiệu .

Khung Chu trình không sử dụng ReactJS mà sử dụng vdom . Nó có nhiều điểm tương đồng với kiến ​​trúc Elm (nhưng dễ sử dụng hơn trong cuộc sống thực vì nó cho phép móc vdom) và nó sử dụng RxJ rộng rãi thay vì các chức năng và có thể là nguồn cảm hứng tốt nếu bạn muốn sử dụng FRP với Phản ứng. Các video của EgJead Egghead rất hay để hiểu cách thức hoạt động của nó.


CSP

CSP (Giao tiếp quy trình tuần tự) hiện đang phổ biến (chủ yếu là do Go / goroutines và core.async / ClojureScript) nhưng bạn cũng có thể sử dụng chúng trong javascript với JS-CSP .

James Long đã thực hiện một video giải thích làm thế nào nó có thể được sử dụng với React.

Sagas

Một saga là một khái niệm phụ trợ xuất phát từ thế giới DDD / EventSource / CQRS, còn được gọi là "trình quản lý quy trình". Nó đang được phổ biến bởi dự án redux-saga , chủ yếu là thay thế cho redux-thunk để xử lý các hiệu ứng phụ (ví dụ như các lệnh gọi API, v.v.). Hầu hết mọi người hiện nay nghĩ rằng nó chỉ phục vụ cho các tác dụng phụ nhưng thực sự là về các thành phần tách rời.

Nó giống như một lời khen cho kiến ​​trúc Flux (hoặc Redux) hơn là một hệ thống giao tiếp hoàn toàn mới, bởi vì saga phát ra các hành động Flux ở cuối. Ý tưởng là nếu bạn có widget1 và widget2 và bạn muốn tách chúng ra, bạn không thể thực hiện hành động nhắm mục tiêu widget2 từ widget1. Vì vậy, bạn tạo widget1 chỉ bắn các hành động nhắm mục tiêu vào chính nó và saga là một "quá trình nền" lắng nghe các hành động của widget1 và có thể gửi các hành động nhắm đến widget2. Saga là điểm kết nối giữa 2 widget nhưng các widget vẫn tách rời.

Nếu bạn quan tâm hãy xem câu trả lời của tôi ở đây


Phần kết luận

Nếu bạn muốn xem một ví dụ về cùng một ứng dụng nhỏ sử dụng các kiểu khác nhau này, hãy kiểm tra các nhánh của kho lưu trữ này .

Tôi không biết đâu là lựa chọn tốt nhất trong dài hạn nhưng tôi thực sự thích cách Flux trông giống như tìm nguồn cung ứng sự kiện.

Nếu bạn không biết các khái niệm tìm nguồn cung ứng sự kiện, hãy xem blog rất sư phạm này: Biến cơ sở dữ liệu ra ngoài với apache Samza , đây là điều cần phải đọc để hiểu tại sao Flux lại tốt (nhưng điều này cũng có thể áp dụng cho FRP )

Tôi nghĩ rằng cộng đồng đồng ý rằng việc triển khai Flux hứa hẹn nhất là Redux , điều này sẽ dần dần cho phép trải nghiệm rất hiệu quả của nhà phát triển nhờ tải lại nóng. Có thể tạo video ấn tượng ala Bret Victor's Inventing on Princuctor video !


2
Câu trả lời tuyệt vời!
Ali Gajani

7

OK, có một vài cách để làm điều đó, nhưng tôi chỉ muốn tập trung vào việc sử dụng cửa hàng bằng Redux , điều này giúp cuộc sống của bạn dễ dàng hơn cho những tình huống này thay vì chỉ cung cấp cho bạn một giải pháp nhanh chóng trong trường hợp này, sử dụng React thuần túy sẽ gây rối ứng dụng lớn thực sự và giao tiếp giữa các Thành phần ngày càng khó hơn khi ứng dụng phát triển ...

Vậy Redux làm gì cho bạn?

Redux giống như bộ nhớ cục bộ trong ứng dụng của bạn, có thể được sử dụng bất cứ khi nào bạn cần sử dụng dữ liệu ở những nơi khác nhau trong ứng dụng của mình ...

Về cơ bản, ý tưởng Redux xuất phát từ thông lượng ban đầu, nhưng với một số thay đổi cơ bản bao gồm khái niệm có một nguồn sự thật bằng cách chỉ tạo một cửa hàng ...

Nhìn vào biểu đồ bên dưới để thấy một số khác biệt giữa FluxRedux ...

Redux và thông lượng

Cân nhắc áp dụng Redux trong ứng dụng của bạn ngay từ đầu nếu ứng dụng của bạn cần liên lạc giữa các Thành phần ...

Ngoài ra, đọc những từ này từ Tài liệu Redux có thể hữu ích để bắt đầu với:

Vì các yêu cầu cho các ứng dụng một trang JavaScript ngày càng trở nên phức tạp, mã của chúng tôi phải quản lý trạng thái nhiều hơn bao giờ hết . Trạng thái này có thể bao gồm phản hồi của máy chủ và dữ liệu được lưu trong bộ nhớ cache, cũng như dữ liệu được tạo cục bộ chưa được duy trì cho máy chủ. Trạng thái UI cũng đang tăng về độ phức tạp, vì chúng ta cần quản lý các tuyến đang hoạt động, các tab được chọn, spinners, điều khiển phân trang, v.v.

Quản lý nhà nước luôn thay đổi này là khó khăn. Nếu một mô hình có thể cập nhật một mô hình khác, thì một khung nhìn có thể cập nhật một mô hình, cập nhật một mô hình khác và chính điều này có thể khiến một khung nhìn khác cập nhật. Tại một số điểm, bạn không còn hiểu những gì xảy ra trong ứng dụng của mình vì bạn đã mất quyền kiểm soát thời gian, lý do và trạng thái của nó. Khi một hệ thống mờ đục và không xác định, thật khó để tái tạo lỗi hoặc thêm các tính năng mới.

Như thể điều này không đủ tệ, hãy xem xét các yêu cầu mới trở nên phổ biến trong phát triển sản phẩm front-end. Là nhà phát triển, chúng tôi dự kiến ​​sẽ xử lý các cập nhật lạc quan, kết xuất phía máy chủ, tìm nạp dữ liệu trước khi thực hiện chuyển đổi tuyến đường, v.v. Chúng tôi thấy mình đang cố gắng quản lý một sự phức tạp mà trước đây chúng tôi chưa bao giờ phải đối phó và chúng tôi chắc chắn đặt câu hỏi: đã đến lúc phải từ bỏ? Câu trả lời là không.

Sự phức tạp này rất khó xử lý khi chúng ta trộn lẫn hai khái niệm rất khó để tâm trí con người suy luận về: đột biến và không điển hình. Tôi gọi chúng là Mentos và Coke. Cả hai có thể tuyệt vời trong sự tách biệt, nhưng cùng nhau họ tạo ra một mớ hỗn độn. Các thư viện như React cố gắng giải quyết vấn đề này trong lớp khung nhìn bằng cách loại bỏ cả thao tác DOM không đồng bộ và trực tiếp. Tuy nhiên, việc quản lý trạng thái dữ liệu của bạn tùy thuộc vào bạn. Đây là nơi Redux đi vào.

Theo các bước của Flux, CQRS và Tìm nguồn sự kiện , Redux cố gắng tạo ra các đột biến trạng thái có thể dự đoán được bằng cách áp đặt một số hạn chế nhất định về cách thức và thời điểm cập nhật có thể xảy ra . Những hạn chế này được phản ánh trong ba nguyên tắc của Redux.


Làm thế nào có thể Redux giúp đỡ? nếu tôi có một phương thức cho một datepicker(như một thành phần) và thành phần đó có thể được tải từ nhiều thành phần sống trên cùng một trang, thì datepickerthành phần đó sẽ biết Hành động nào được gửi đến để chuyển hướng? Đây là bản chất của vấn đề, liên kết một hành động trong một thành phần này với thành phần khác và KHÔNG phải bất kỳ thành phần nào khác . (xem xét datepickerbản thân nó là một thành phần sâu, sâu trong chính thành phần phương thức)
vsync

@vsync đừng nghĩ reudx là một giá trị tĩnh duy nhất, redux thực sự có thể có các đối tượng, mảng ... vì vậy bạn có thể lưu chúng dưới dạng đối tượng hoặc mảng hoặc bất cứ thứ gì trong cửa hàng của bạn, nó có thể là mapdispatchtoprops và mỗi được lưu trong mảng đối tượng như: [{name: "picker1", value: "01/01/1970"}, {name: "picker2", value: "01/01/1980"}] và sau đó sử dụng mapstatetoprops trong cha mẹ và chuyển nó xuống từng thành phần hoặc bất cứ nơi nào bạn muốn, không chắc nó có trả lời câu hỏi của bạn không, nhưng không thấy mã ... nếu chúng nằm trong các nhóm riêng biệt, bạn cũng có thể phản đối với nhiều chi tiết hơn ... nhưng tất cả tùy thuộc vào cách bạn muốn nhóm họ ..
Alireza

Câu hỏi không phải là về reduxvà những gì bạn có thể lưu trữ, nhưng LÀM THẾ NÀO để chuyển hành động đi sâu vào những gì cần kích hoạt nó. Làm thế nào để một thành phần sâu biết chính xác những gì cần được kích hoạt? vì trong ví dụ tôi đã đưa ra một thành phần chung mà nên kích hoạt để một giảm cụ thể, tùy thuộc vào kịch bản, vì vậy nó có thể được giảm khác nhau từ một datepicker phương thức có thể được sử dụng cho bất kỳ thành phần.
vsync

5

Đây là cách tôi xử lý này.
Giả sử bạn có <select> cho Tháng và <select> cho Ngày . Số ngày tùy thuộc vào tháng được chọn.

Cả hai danh sách được sở hữu bởi một đối tượng thứ ba, bảng điều khiển bên trái. Cả <select> cũng là con của leftPanel <div>
Đây là một trò chơi với các hàm gọi lại và các trình xử lý trong thành phần LeftPanel .

Để kiểm tra nó, chỉ cần sao chép mã vào hai tệp riêng biệt và chạy index.html . Sau đó chọn một tháng và xem số ngày thay đổi như thế nào.

date.js

    /** @jsx React.DOM */


    var monthsLength = [0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    var MONTHS_ARR = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];

    var DayNumber = React.createClass({
        render: function() {
            return (
                <option value={this.props.dayNum}>{this.props.dayNum}</option>
            );
        }
    });

    var DaysList = React.createClass({
        getInitialState: function() {
            return {numOfDays: 30};
        },
        handleMonthUpdate: function(newMonthix) {
            this.state.numOfDays = monthsLength[newMonthix];
            console.log("Setting days to " + monthsLength[newMonthix] + " month = " + newMonthix);

            this.forceUpdate();
        },
        handleDaySelection: function(evt) {
            this.props.dateHandler(evt.target.value);
        },
        componentDidMount: function() {
            this.props.readyCallback(this.handleMonthUpdate)
        },
        render: function() {
            var dayNodes = [];
            for (i = 1; i <= this.state.numOfDays; i++) {
                dayNodes = dayNodes.concat([<DayNumber dayNum={i} />]);
            }
            return (
                <select id={this.props.id} onChange = {this.handleDaySelection}>
                    <option value="" disabled defaultValue>Day</option>
                        {dayNodes}
                </select>
                );
        }
    });

    var Month = React.createClass({
        render: function() {
            return (
                <option value={this.props.monthIx}>{this.props.month}</option>
            );
        }
    });

    var MonthsList = React.createClass({
        handleUpdate: function(evt) {
            console.log("Local handler:" + this.props.id + " VAL= " + evt.target.value);
            this.props.dateHandler(evt.target.value);

            return false;
        },
        render: function() {
            var monthIx = 0;

            var monthNodes = this.props.data.map(function (month) {
                monthIx++;
                return (
                    <Month month={month} monthIx={monthIx} />
                    );
            });

            return (
                <select id = {this.props.id} onChange = {this.handleUpdate}>
                    <option value="" disabled defaultValue>Month</option>
                        {monthNodes}
                </select>
                );
        }
    });

    var LeftPanel = React.createClass({
        dayRefresh: function(newMonth) {
            // Nothing - will be replaced
        },
        daysReady: function(refreshCallback) {
            console.log("Regisering days list");
        this.dayRefresh = refreshCallback;
        },
        handleMonthChange: function(monthIx) {
            console.log("New month");
            this.dayRefresh(monthIx);
        },
        handleDayChange: function(dayIx) {
            console.log("New DAY: " + dayIx);
        },
        render: function() {
            return(
                <div id="orderDetails">
                    <DaysList id="dayPicker" dateHandler={this.handleDayChange} readyCallback = {this.daysReady} />
                    <MonthsList data={MONTHS_ARR} id="monthPicker" dateHandler={this.handleMonthChange}  />
                </div>
            );
        }
    });



    React.renderComponent(
        <LeftPanel />,
        document.getElementById('leftPanel')
    );

Và HTML để chạy chỉ mục thành phần bảng điều khiển bên trái.html

<!DOCTYPE html>
<html>
<head>
    <title>Dates</title>

    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
    <script src="//fb.me/react-0.11.1.js"></script>
    <script src="//fb.me/JSXTransformer-0.11.1.js"></script>
</head>

    <style>

        #dayPicker {
            position: relative;
            top: 97px;
            left: 20px;
            width: 60px;
            height: 17px;
        }

        #monthPicker {
            position: relative;
            top: 97px;
            left: 22px;
            width: 95px;
            height: 17px;
        }

        select {
            font-size: 11px;
        }

    </style>


    <body>
        <div id="leftPanel">
        </div>

        <script type="text/jsx" src="dates.js"></script>

    </body>
</html>

nếu bạn có thể xóa 80% mã ví dụ mà vẫn giữ quan điểm của mình, nó sẽ là tốt nhất. hiển thị CSS trong ngữ cảnh của chủ đề này không liên quan
vsync

3

Tôi thấy rằng câu hỏi đã được trả lời, nhưng nếu bạn muốn tìm hiểu thêm chi tiết, có tổng cộng 3 trường hợp giao tiếp giữa các thành phần :

  • Trường hợp 1: Giao tiếp giữa cha mẹ và con cái
  • Trường hợp 2: Giao tiếp từ con đến cha mẹ
  • Trường hợp 3: Truyền thông các thành phần không liên quan (bất kỳ thành phần nào đến bất kỳ thành phần nào)

1

Mở rộng câu trả lời của @MichaelLaCroix khi một kịch bản là các thành phần không thể giao tiếp giữa bất kỳ mối quan hệ cha-con nào, tài liệu khuyên bạn nên thiết lập một hệ thống sự kiện toàn cầu.

Trong trường hợp <Filters /><TopBar />không có bất kỳ mối quan hệ nào ở trên, một trình phát toàn cầu đơn giản có thể được sử dụng như thế này:

componentDidMount - Theo dõi sự kiện

componentWillUnmount - Hủy đăng ký từ sự kiện

Mã React.js và EventSystem

EventSystem.js

class EventSystem{

    constructor() {
        this.queue = {};
        this.maxNamespaceSize = 50;
    }

    publish(/** namespace **/ /** arguments **/) {
        if(arguments.length < 1) {
            throw "Invalid namespace to publish";
        }

        var namespace = arguments[0];
        var queue = this.queue[namespace];

        if (typeof queue === 'undefined' || queue.length < 1) {
            console.log('did not find queue for %s', namespace);
            return false;
        }

        var valueArgs = Array.prototype.slice.call(arguments);

        valueArgs.shift(); // remove namespace value from value args

        queue.forEach(function(callback) {
            callback.apply(null, valueArgs);
        });

        return true;
    }

    subscribe(/** namespace **/ /** callback **/) {
        const namespace = arguments[0];
        if(!namespace) throw "Invalid namespace";
        const callback = arguments[arguments.length - 1];
        if(typeof callback !== 'function') throw "Invalid callback method";

        if (typeof this.queue[namespace] === 'undefined') {
            this.queue[namespace] = [];
        }

        const queue = this.queue[namespace];
        if(queue.length === this.maxNamespaceSize) {
            console.warn('Shifting first element in queue: `%s` since it reached max namespace queue count : %d', namespace, this.maxNamespaceSize);
            queue.shift();
        }

        // Check if this callback already exists for this namespace
        for(var i = 0; i < queue.length; i++) {
            if(queue[i] === callback) {
                throw ("The exact same callback exists on this namespace: " + namespace);
            }
        }

        this.queue[namespace].push(callback);

        return [namespace, callback];
    }

    unsubscribe(/** array or topic, method **/) {
        let namespace;
        let callback;
        if(arguments.length === 1) {
            let arg = arguments[0];
            if(!arg || !Array.isArray(arg)) throw "Unsubscribe argument must be an array";
            namespace = arg[0];
            callback = arg[1];
        }
        else if(arguments.length === 2) {
            namespace = arguments[0];
            callback = arguments[1];
        }

        if(!namespace || typeof callback !== 'function') throw "Namespace must exist or callback must be a function";
        const queue = this.queue[namespace];
        if(queue) {
            for(var i = 0; i < queue.length; i++) {
                if(queue[i] === callback) {
                    queue.splice(i, 1); // only unique callbacks can be pushed to same namespace queue
                    return;
                }
            }
        }
    }

    setNamespaceSize(size) {
        if(!this.isNumber(size)) throw "Queue size must be a number";
        this.maxNamespaceSize = size;
        return true;
    }

    isNumber(n) {
        return !isNaN(parseFloat(n)) && isFinite(n);
    }

}

Thông báoComponent.js

class NotificationComponent extends React.Component {

    getInitialState() {
        return {
            // optional. see alternative below
            subscriber: null
        };
    }

    errorHandler() {
        const topic = arguments[0];
        const label = arguments[1];
        console.log('Topic %s label %s', topic, label);
    }

    componentDidMount() {
        var subscriber = EventSystem.subscribe('error.http', this.errorHandler);
        this.state.subscriber = subscriber;
    }

    componentWillUnmount() {
        EventSystem.unsubscribe('error.http', this.errorHandler);

        // alternatively
        // EventSystem.unsubscribe(this.state.subscriber);
    }

    render() {

    }
}

0

Có khả năng như vậy ngay cả khi họ không phải là mối quan hệ Cha mẹ - Con cái - và đó là Flux. Có một triển khai khá tốt (đối với cá nhân tôi) cho cái đó gọi là Alt.JS (với Alt-Container).

Ví dụ: bạn có thể có Sidebar phụ thuộc vào nội dung được đặt trong Chi tiết thành phần. Thanh bên thành phần được kết nối với SidebarActions và SidebarStore, trong khi Chi tiết là Chi tiết hành động và Chi tiết.

Bạn có thể sử dụng AltContainer như thế

<AltContainer stores={{
                    SidebarStore: SidebarStore
                }}>
                    <Sidebar/>
</AltContainer>

{this.props.content}

Điều này sẽ giữ cho các cửa hàng (tôi cũng có thể sử dụng "cửa hàng" thay vì "cửa hàng" chống đỡ). Bây giờ, {this.props.content} CÓ THỂ Chi tiết tùy theo tuyến đường. Hãy nói rằng / Chi tiết chuyển hướng chúng ta đến quan điểm đó. Ví dụ, chi tiết sẽ có một hộp kiểm thay đổi thành phần Sidebar từ X thành Y nếu nó được chọn.

Về mặt kỹ thuật không có mối quan hệ giữa họ và sẽ khó thực hiện nếu không có thông lượng. NHƯNG VỚI RATNG nó khá dễ dàng.

Bây giờ chúng ta hãy đến Chi tiết hành động. Chúng tôi sẽ tạo ra ở đó

class SiteActions {
constructor() {
    this.generateActions(
        'setSiteComponentStore'
    );
}

setSiteComponent(value) {
    this.dispatch({value: value});
}
}

và Chi tiếtStore

class SiteStore {
constructor() {
    this.siteComponents = {
        Prop: true
    };

    this.bindListeners({
        setSiteComponent: SidebarActions.COMPONENT_STATUS_CHANGED
    })
}

setSiteComponent(data) {
    this.siteComponents.Prop = data.value;
}
}

Và bây giờ, đây là nơi bắt đầu phép thuật.

Như bạn có thể thấy có bindListener với SidebarActions.ComponentStatusChanged sẽ được sử dụng NẾU setSiteComponent sẽ được sử dụng.

hiện có trong SidebarActions

    componentStatusChanged(value){
    this.dispatch({value: value});
}

Chúng tôi có những điều như vậy. Nó sẽ gửi đối tượng đó trong cuộc gọi. Và nó sẽ được gọi nếu setSiteComponent trong cửa hàng sẽ được sử dụng (ví dụ bạn có thể sử dụng trong thành phần trong khi onChange trên Nút ot bất cứ điều gì)

Bây giờ trong SidebarStore chúng ta sẽ có

    constructor() {
    this.structures = [];

    this.bindListeners({
        componentStatusChanged: SidebarActions.COMPONENT_STATUS_CHANGED
    })
}

    componentStatusChanged(data) {
    this.waitFor(DetailsStore);

    _.findWhere(this.structures[0].elem, {title: 'Example'}).enabled = data.value;
}

Bây giờ ở đây bạn có thể thấy, nó sẽ chờ Chi tiết. Nó có nghĩa là gì? ít nhiều có nghĩa là phương pháp này cần chờ cập nhật Chi tiếtStoreto trước khi có thể tự cập nhật.

tl; dr One Store đang lắng nghe các phương thức trong một cửa hàng và sẽ kích hoạt một hành động từ hành động thành phần, sẽ cập nhật cửa hàng của chính nó.

Tôi hy vọng nó có thể giúp bạn bằng cách nào đó.


0

Nếu bạn muốn khám phá các tùy chọn giao tiếp giữa các thành phần và cảm thấy như nó ngày càng khó hơn, thì bạn có thể cân nhắc áp dụng một mẫu thiết kế tốt: Flux .

Nó chỉ đơn giản là một tập hợp các quy tắc xác định cách bạn lưu trữ và thay đổi trạng thái rộng của ứng dụng và sử dụng trạng thái đó để kết xuất các thành phần.

Có nhiều triển khai Flux và triển khai chính thức của Facebook là một trong số đó. Mặc dù nó được coi là một trong đó chứa hầu hết các mã soạn sẵn, nhưng nó dễ hiểu hơn vì hầu hết mọi thứ đều rõ ràng.

Một số lựa chọn thay thế khác là fluxx fluxxor fluxibleredux .


0

Tôi đã từng là nơi bạn đang ở ngay bây giờ, khi mới bắt đầu, đôi khi bạn cảm thấy lạc lõng với cách phản ứng để làm điều này. Tôi sẽ cố gắng giải quyết giống như cách tôi nghĩ về nó ngay bây giờ.

Các tiểu bang là nền tảng cho giao tiếp

Thông thường những gì nó nói đến là cách bạn thay đổi các trạng thái trong thành phần này trong trường hợp của bạn, bạn chỉ ra ba thành phần.

<List />: Có thể sẽ hiển thị danh sách các mục tùy thuộc vào bộ lọc <Filters />: Tùy chọn bộ lọc sẽ thay đổi dữ liệu của bạn. <TopBar />: Danh sách các lựa chọn.

Để phối hợp tất cả các tương tác này, bạn sẽ cần một thành phần cao hơn, hãy gọi nó là Ứng dụng, điều đó sẽ truyền lại các hành động và dữ liệu cho từng thành phần này để có thể trông như thế này

<div>
  <List items={this.state.filteredItems}/>
  <Filter filter={this.state.filter} setFilter={setFilter}/>
</div>

Vì vậy, khi setFilterđược gọi, nó sẽ ảnh hưởng đến bộ lọc được lọc và kết xuất lại cả hai thành phần;. Trong trường hợp điều này không hoàn toàn rõ ràng, tôi đã làm cho bạn một ví dụ với hộp kiểm mà bạn có thể kiểm tra trong một tệp duy nhất:

import React, {Component} from 'react';
import {render} from 'react-dom';

const Person  = ({person, setForDelete}) => (
          <div>
            <input type="checkbox" name="person" checked={person.checked} onChange={setForDelete.bind(this, person)} />
            {person.name}
          </div>
);


class PeopleList extends Component {

  render() {

    return(
      <div>
       {this.props.people.map((person, i) => {
         return <Person key={i} person={person} setForDelete={this.props.setForDelete} />;
       })}
       <div onClick={this.props.deleteRecords}>Delete Selected Records</div>
     </div>
    );
  }

} // end class

class App extends React.Component {

  constructor(props) {
    super(props)
    this.state = {people:[{id:1, name:'Cesar', checked:false},{id:2, name:'Jose', checked:false},{id:3, name:'Marbel', checked:false}]}
  }

  deleteRecords() {
    const people = this.state.people.filter(p => !p.checked);

    this.setState({people});
 }

  setForDelete(person) {
    const checked = !person.checked;
    const people = this.state.people.map((p)=>{
      if(p.id === person.id)
        return {name:person.name, checked};
      return p;
    });

    this.setState({people});
  }

  render () {

    return <PeopleList people={this.state.people} deleteRecords={this.deleteRecords.bind(this)} setForDelete={this.setForDelete.bind(this)}/>;
  }
}

render(<App/>, document.getElementById('app'));

0

Đoạn mã sau giúp tôi thiết lập liên lạc giữa hai anh chị em. Việc thiết lập được thực hiện trong cha mẹ của chúng trong các lệnh gọi render () và thành phầnDidMount (). Nó dựa trên https://reactjs.org/docs/refs-and-the-dom.html Hy vọng nó có ích.

class App extends React.Component<IAppProps, IAppState> {
    private _navigationPanel: NavigationPanel;
    private _mapPanel: MapPanel;

    constructor() {
        super();
        this.state = {};
    }

    // `componentDidMount()` is called by ReactJS after `render()`
    componentDidMount() {
        // Pass _mapPanel to _navigationPanel
        // It will allow _navigationPanel to call _mapPanel directly
        this._navigationPanel.setMapPanel(this._mapPanel);
    }

    render() {
        return (
            <div id="appDiv" style={divStyle}>
                // `ref=` helps to get reference to a child during rendering
                <NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
                <MapPanel ref={(child) => { this._mapPanel = child; }} />
            </div>
        );
    }
}

Đây là TypeScript, có lẽ nên được đề cập trong câu trả lời của bạn. Khái niệm tốt mặc dù.
serraosays

0

Kì lạ không ai nhắc đến mobx. Ý tưởng tương tự như redux. Nếu tôi có một phần dữ liệu mà nhiều thành phần được đăng ký vào nó, thì tôi có thể sử dụng dữ liệu này để lái nhiều thành phần.

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.