ReactJS 두 컴포넌트 통신 버튼이있는 목록입니다. 현재

방금 ReactJS를 시작했으며 문제가 조금 있습니다.

내 응용 프로그램은 본질적으로 필터와 레이아웃을 변경하는 버튼이있는 목록입니다. 현재 세 가지 구성 요소를 사용 하고 있습니다 . <list />, < Filters /><TopBar />이제 설정을 변경하면 뷰를 업데이트하는 < Filters />몇 가지 방법을 트리거 <list />하려고합니다.

이 3 가지 구성 요소가 서로 상호 작용하도록하려면 어떻게해야합니까? 아니면 그냥 변경할 수있는 일종의 전역 데이터 모델이 필요합니까?



답변

가장 좋은 방법은 이러한 구성 요소를 어떻게 계획 할 것인지에 달려 있습니다. 지금 염두에 두는 몇 가지 시나리오 예 :

  1. <Filters /> 의 하위 구성 요소입니다 <List />
  2. 모두 <Filters /><List />부모 구성 요소의 자식입니다
  3. <Filters />그리고 <List />완전히 별도의 루트 구성 요소에 살고 있습니다.

내가 생각하지 않는 다른 시나리오가있을 수 있습니다. 당신이 이것에 맞지 않으면 알려주세요. 다음은 처음 두 시나리오를 처리 한 방법에 대한 매우 거친 예입니다.

시나리오 # 1

핸들러를에서 <List />로 전달할 수 있습니다 .<Filters /> 하면 onChange이벤트에서 호출되어 현재 값으로 목록을 필터링 할 수 있습니다 .

# 1 JSFiddle →

/** @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);

시나리오 # 2

시나리오 # 1과 유사하지만 상위 구성 요소는 핸들러 함수를로 <Filters />전달하고 필터링 된 목록을로 전달하는 구성 요소입니다 <List />. 이 방법을 <List />에서 분리하기 때문에이 방법이 더 좋습니다 <Filters />.

2 번 JSFiddle →

/** @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);

시나리오 # 3

구성 요소가 어떤 종류의 상위-하위 관계간에 통신 할 수없는 경우 글로벌 이벤트 시스템 설정을 권장합니다 .


답변

구성 요소가 통신하도록하는 여러 가지 방법이 있습니다. 일부는 사용 사례에 적합 할 수 있습니다. 여기 내가 아는 유용한 것들의 목록이있다.

반응

부모 / 아동 직접 커뮤니케이션

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

여기서 자식 구성 요소는 부모가 제공 한 콜백을 값으로 호출하고 부모는 자식이 부모의 값을 제공 할 수 있습니다.

앱의 기능 / 페이지를 구축하는 경우 콜백 / 상태 ( container또는 라고도 함 smart component)를 관리하는 단일 부모가 있고 모든 자식은 상태를 유지하지 않고 부모에게만보고하는 것이 좋습니다. 이렇게하면 부모의 상태를 필요한 모든 어린이에게 쉽게 “공유”할 수 있습니다.


문맥

React Context를 사용하면 구성 요소 계층의 루트에서 상태를 유지할 수 있으며 모든 중간 구성 요소에 소품을 전달할 필요없이 매우 깊게 중첩 된 구성 요소 에이 상태를 쉽게 주입 할 수 있습니다.

지금까지 컨텍스트는 실험적인 기능 이었지만 React 16.3에서 새로운 API를 사용할 수 있습니다.

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

소비자가 render prop / children 함수 패턴을 사용하고 있습니다

자세한 내용은 이 블로그 게시물 을 확인하십시오.

React 16.3 이전 에는 매우 유사한 API를 제공하고 이전 컨텍스트 API를 사용하는 react-broadcast 를 사용하는 것이 좋습니다 .


포털

일반적인 부모 / 자식과 같이 간단한 구성 요소와 통신하기 위해 2 개의 구성 요소를 서로 가깝게 유지하려는 경우 포털을 사용하지만이 두 구성 요소가 DOM에서 부모 / 자식 관계를 갖기를 원하지 않습니다. 시각적 / CSS 제약 조건 (z-index, opacity …와 같은)을 의미합니다.

이 경우 “포털”을 사용할 수 있습니다. 포털을 사용하는 다른 반응 라이브러리가 있으며 일반적으로 모달에 사용됩니다 , 팝업, 툴팁에 사용됩니다 …

다음을 고려하세요:

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

내부에서 렌더링 될 때 다음 DOM을 생성 할 수 있습니다 reactAppContainer.

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

자세한 내용은 여기


슬롯

슬롯을 어딘가에 정의한 다음 렌더 트리의 다른 위치에서 슬롯을 채 웁니다.

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>

이것은 채워진 컨텐츠가 사용자가 정의한 슬롯에 렌더링된다는 점을 제외하고는 포털과 약간 유사하지만, 포털은 일반적으로 새로운 DOM 노드 (종종 문서의 하위)를 렌더링합니다.

체크는 반응-슬롯 채우기 라이브러리를


이벤트 버스

React 문서에 명시된 바와 같이 :

부모-자식 관계가없는 두 구성 요소 간의 통신을 위해 고유 한 글로벌 이벤트 시스템을 설정할 수 있습니다. componentDidMount ()에서 이벤트를 구독하고 componentWillUnmount ()에서 구독을 취소하고 이벤트를 수신하면 setState ()를 호출하십시오.

이벤트 버스를 설정하는 데 사용할 수있는 많은 것들이 있습니다. 리스너의 배열을 만들 수 있으며 이벤트 게시시 모든 리스너가 이벤트를 수신합니다. 또는 EventEmitter 또는 PostalJs 와 같은 것을 사용할 수 있습니다


유량

유량 는 기본적으로 이벤트 수신기이며, 이벤트 수신자는 상점입니다. 상태가 React 외부에서 관리된다는 점을 제외하면 기본 이벤트 버스 시스템과 유사합니다.

독창적 인 Flux 구현은 이벤트 소싱을 해키 방식으로 시도하는 것처럼 보입니다.

리덕스 는 이벤트 소싱에서 가장 가까운 Flux 구현으로 시간 여행 기능과 같은 많은 이벤트 소싱 이점을 제공합니다. React에 엄격하게 연결되어 있지 않으며 다른 기능적 뷰 라이브러리와 함께 사용할 수도 있습니다.

Egghead의 Redux 비디오 튜토리얼 은 정말 좋으며 내부적으로 어떻게 작동하는지 설명합니다 (실제로는 간단합니다).


커서

커서는 ClojureScript / Om 에서 나오며 React 프로젝트에서 널리 사용됩니다. React 외부의 상태를 관리 할 수 ​​있으며 구성 요소 트리에 대해 알 필요없이 여러 구성 요소가 상태의 동일한 부분에 대한 읽기 / 쓰기 액세스 권한을 갖도록합니다.

ImmutableJS , React-cursorsOmniscient를 포함한 많은 구현이 존재합니다.

2016 편집 : 사람들은 커서가 작은 응용 프로그램에서는 잘 작동하지만 복잡한 응용 프로그램에서는 잘 확장되지 않는다는 데 동의합니다. Om Next에는 더 이상 커서가 없습니다 (초기에는 개념을 도입 한 것은 Om입니다)


느릅 나무 건축

느릅 나무 아키텍처는 아키텍처가 사용할 수 있도록 제안 느릅 나무 언어 . Elm이 ReactJS가 아니더라도 Rem에서도 Elm 아키텍처를 수행 할 수 있습니다.

Redux의 저자 인 Dan Abramov는 React를 사용하여 Elm 아키텍처를 구현했습니다 .

Redux와 Elm은 정말 훌륭하며 프론트 엔드에서 이벤트 소싱 개념을 강화하여 시간 여행 디버깅, 실행 취소 / 다시 실행, 재생을 허용합니다 …

Redux와 Elm의 주요 차이점은 Elm이 상태 관리에 대해 훨씬 더 엄격한 경향이 있다는 것입니다. Elm에서는 로컬 구성 요소 상태 또는 마운트 / 마운트 후크를 가질 수 없으며 모든 DOM 변경은 글로벌 상태 변경으로 트리거되어야합니다. Elm 아키텍처 는 단일 불변 객체 내에서 모든 상태 를 처리 할 수있는 확장 가능한 접근 방식을 제안하는 반면, Redux 는 단일 불변 객체에서 상태의 대부분 을 처리하도록 초대하는 접근 방식을 제안 합니다.

Elm의 개념 모델은 매우 우아하고 아키텍처가 큰 앱에서 잘 확장 될 수 있지만 실제로 마운트하거나 입력 한 후 입력에 초점을 맞추거나 기존 라이브러리와 통합하는 등 간단한 작업을 수행하기에는 더 어렵거나 보일러 플레이트가 더 많이 포함될 수 있습니다. 명령형 인터페이스 (예 : JQuery 플러그인) 관련 문제 .

또한 Elm 아키텍처에는 더 많은 코드 상용구가 포함됩니다. 장황하거나 작성하기가 복잡하지는 않지만 Elm 아키텍처는 정적으로 유형이 지정된 언어에 더 적합하다고 생각합니다.


FRP

RxJS, BaconJS 또는 Kefir와 같은 라이브러리를 사용하여 구성 요소 간 통신을 처리하는 FRP 스트림을 생성 할 수 있습니다.

예를 들어 Rx-React를 시도 할 수 있습니다

이 libs를 사용하는 것은 ELM 언어가 신호 와 함께 제공하는 것을 사용하는 것과 매우 유사하다고 생각합니다 .

CycleJS 프레임 워크는 ReactJS를 사용하지 않고 vdom을 사용 합니다 . Elm 아키텍처와 많은 유사점을 공유하지만 (vdom 후크를 허용하므로 실제 생활에서 사용하기가 더 쉽습니다) 함수 대신 RxJ를 광범위하게 사용하며 FRP를 함께 사용하려는 경우 좋은 영감의 원천이 될 수 있습니다 반응. CycleJs Egghead 비디오 는 작동 방식을 이해하는 것이 좋습니다.


CSP

CSP (Communicating Sequential Processes)는 현재 널리 사용되지만 (주로 Go / goroutines 및 core.async / ClojureScript로 인해) JS-CSP 와 함께 javascript에서도 사용할 수 있습니다 .

James Long은 React와 함께 사용하는 방법을 설명하는 비디오 를 만들었습니다.

사 가스

saga는 “프로세스 관리자”라고도하는 DDD / EventSourcing / CQRS 세계에서 제공되는 백엔드 개념입니다. 그것은 redux-saga 프로젝트에 의해 대중화되고 있으며 , 주로 부작용 (예 : API 호출 등)을 처리하기 위해 redux-thunk를 대체합니다. 대부분의 사람들은 현재 부작용에 대해서만 서비스를 제공한다고 생각하지만 실제로는 구성 요소를 분리하는 것에 관한 것입니다.

사가는 결국 플럭스 액션을 내기 때문에 완전히 새로운 통신 시스템보다 Flux 아키텍처 (또는 Redux)에 대한 칭찬입니다. widget1과 widget2가 있고 이들을 분리하려는 경우 widget1에서 widget2를 대상으로하는 조치를 실행할 수 없습니다. 따라서 widget1은 자신을 대상으로하는 작업 만 실행하도록하고 saga는 widget1 작업을 수신하고 widget2를 대상으로하는 작업을 전달할 수있는 “백그라운드 프로세스”입니다. saga는 두 위젯 간의 연결 지점이지만 위젯은 분리되어 있습니다.

관심이 있으시면 여기 에서 내 대답을 살펴보십시오.


결론

서로 다른 스타일을 사용하는 동일한 작은 앱의 예를 보려면이 저장소 의 분기를 확인하십시오 .

장기적으로 최상의 옵션이 무엇인지 모르지만 Flux가 이벤트 소싱처럼 보이는 방식이 정말 좋습니다.

이벤트 소싱 개념을 모르는 경우이 교육적인 블로그를 살펴보십시오. Apache Samza로 데이터베이스를 내부로 돌리면 Flux가 왜 좋은지 이해해야합니다. (FRP에도 적용될 수 있음) )

커뮤니티는 가장 유망한 Flux 구현이 Redux 이며 핫 리로드 덕분에 매우 생산적인 개발자 경험을 점진적으로 허용 할 것이라고 동의합니다 . Bret Victor ‘s Inventing on Principle 비디오 의 인상적인 라이브 코딩 이 가능합니다!


답변

좋아, 그것을 할 수있는 몇 가지 방법이 있지만, 나는 독점적으로 Redux 를 사용하여 상점을 사용하는 데 집중하고 싶습니다. 이 상황에 대해서만 당신의 삶을 훨씬 쉽게 만들 수 있습니다. 응용 프로그램이 커짐에 따라 실제 큰 응용 프로그램과 구성 요소 간의 통신 이 점점 어려워집니다 …

Redux 가 무엇 을합니까?

Redux는 응용 프로그램의 다른 장소에서 데이터를 사용해야 할 때마다 사용할 수있는 응용 프로그램의 로컬 저장소와 같습니다.

기본적으로 Redux 아이디어는 원래 플럭스에서 비롯되었지만 하나의 매장을 만들어서 하나의 진실 소스를 갖는 개념을 포함하여 근본적인 변화가있었습니다 …

FluxRedux의 차이점을 보려면 아래 그래프를 참조하십시오 …

응용 프로그램이 구성 요소 간 통신이 필요한 경우 처음부터 응용 프로그램에 Redux 를 적용하는 것이 좋습니다 .

또한 Redux Documentation에서 다음 단어를 읽는 것이 도움이 될 수 있습니다.

JavaScript 단일 페이지 응용 프로그램에 대한 요구 사항이 점점 복잡해지면서 코드는 그 어느 때보 다 많은 상태를 관리해야합니다 . 이 상태에는 서버 응답 및 캐시 된 데이터뿐만 아니라 아직 서버에 유지되지 않은 로컬로 작성된 데이터가 포함될 수 있습니다. 활성 경로, 선택된 탭, 스피너, 페이지 매김 컨트롤 등을 관리해야하므로 UI ​​상태도 점점 복잡해지고 있습니다.

끊임없이 변화하는이 상태를 관리하는 것은 어렵습니다. 모델이 다른 모델을 업데이트 할 수 있으면 뷰가 모델을 업데이트하여 다른 모델을 업데이트하면 다른 뷰가 업데이트 될 수 있습니다. 어떤 시점에서 더 이상 언제, 왜, 어떻게 상태에 대한 제어권을 잃어 앱에서 어떤 일이 발생하는지 이해하지 못합니다. 시스템이 불투명하고 결정적이지 않은 경우 버그를 재현하거나 새로운 기능을 추가하기가 어렵습니다.

이것이 나쁘지 않은 것처럼 프런트 엔드 제품 개발에서 새로운 요구 사항이 일반화되는 것을 고려하십시오. 개발자는 낙관적 업데이트, 서버 측 렌더링, 경로 전환을 수행하기 전에 데이터 가져 오기 등을 처리해야합니다. 우리는 이전에 다룰 필요가 없었던 복잡성을 관리하려고 노력하고 있으며 필연적으로 질문을합니다. 포기할 시간입니까? 내 대답은 아니오 야.

인간이 생각하기 어려운 두 가지 개념, 즉 돌연변이와 비동기 성이 혼합되어 있기 때문에 이러한 복잡성을 다루기가 어렵습니다. 나는 그들을 멘토스와 콜라라고 부릅니다. 둘 다 분리가 잘 될 수 있지만 함께 엉망이됩니다. React와 같은 라이브러리는 비동기 및 직접 DOM 조작을 모두 제거하여 뷰 계층에서이 문제를 해결하려고합니다. 그러나 데이터 상태 관리는 사용자의 몫입니다. Redux가 시작되는 곳입니다.

의 단계에 따라 플럭스, CQRS 및 이벤트 소싱 , 업데이트가 일어날 수있는 방법과시기에 특정 제한을 부과하여 국가 돌연변이가 예측 가능하게 돌아 오는 시도 . 이러한 제한은 Redux의 세 가지 원칙에 반영됩니다.


답변

이것이 내가 처리 한 방법입니다. Month 에 <select>가 있고 Day에 <select>
가 있다고 가정 해 봅시다 . 일 수는 선택한 월에 따라 다릅니다.

두 목록 모두 왼쪽 패널 인 세 번째 개체가 소유합니다. <select> 둘 다 leftPanel <div>
자식이기도 합니다. LeftPanel 컴포넌트 에 콜백과 핸들러가있는 게임입니다 .

테스트하려면 코드를 두 개의 분리 된 파일로 복사하고 index.html을 실행하십시오 . 그런 다음 월을 선택하고 일수가 어떻게 변하는 지 확인하십시오.

dates.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')
    );

왼쪽 패널 구성 요소 index.html 을 실행하기위한 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>

답변

질문에 이미 답변되어 있음을 알았지 만 자세한 내용을 알고 싶다면 구성 요소 사이에3 가지의 통신 ​​사례가 있습니다 .

  • 사례 1 : 부모와 자녀 간의 의사 소통
  • 사례 2 : 자녀 간 커뮤니케이션
  • 사례 3 : 관련없는 구성 요소 (모든 구성 요소와 모든 구성 요소) 통신

답변

구성 요소가 어떤 종류의 부모-자식 관계간에 통신 할 수없는 경우 @MichaelLaCroix에 대한 답변 확장 글로벌 문서 시스템을 설정하는 것이 좋습니다.

의 경우 <Filters /><TopBar />위의 관계 중 하나를하지 않아도 간단한 글로벌 에미는 다음과 같이 사용될 수있다 :

componentDidMount -이벤트 구독

componentWillUnmount -이벤트 탈퇴

React.js 및 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);
    }

}

NotificationComponent.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() {

    }
}

답변

부모-자녀 관계가 아닌 경우에도 그러한 가능성이 있습니다. Alt.JS (Alt-Container 사용)라는 구현에는 꽤 좋습니다 (개인적으로).

예를 들어 구성 요소 세부 정보에 설정된 내용에 종속되는 사이드 바를 가질 수 있습니다. 구성 요소 사이드 바는 SidebarActions 및 SidebarStore에 연결되어 있으며 Details는 DetailsActions 및 DetailsStore입니다.

그런 다음 AltContainer를 사용할 수 있습니다.

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

{this.props.content}

어느 상점을 유지할 것입니다 (물론 “stores”prop 대신 “store”를 사용할 수 있습니다). 이제 {this.props.content}는 경로에 따라 세부 정보가 될 수 있습니다. / Details가 우리를 해당 뷰로 리디렉션한다고 가정하겠습니다. 세부 사항은 예를 들어 사이드 바 요소를 선택하면 X에서 Y로 변경하는 확인란이 있습니다.

기술적으로 그들 사이에는 아무런 관계가 없으며 플럭스 없이는 어려울 것입니다. 그러나 그것은 오히려 쉽다.

이제 DetailsActions로갑니다. 우리는 거기서 만들 것입니다

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

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

and DetailsStore

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

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

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

그리고 지금, 이곳은 마술이 시작되는 곳입니다.

보시다시피 setSiteComponent가 사용될 경우 SidebarActions.ComponentStatusChanged에 bindListener가 있습니다.

이제 사이드 바에서

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

우리는 그런 것을 가지고 있습니다. 호출시 해당 객체를 전달합니다. 그리고 상점의 setSiteComponent가 사용되면 호출됩니다 (예 : onOT on on Button OT와 같이 구성 요소에서 사용할 수 있음)

이제 SidebarStore에서 우리는

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

이제 여기에서 DetailsStore를 기다릴 수 있습니다. 무슨 뜻이에요? 다소이 방법은이 메소드가 자체를 업데이트하기 전에 DetailsStoreto가 업데이트되기를 기다려야 함을 의미합니다.

tl; dr One Store는 상점의 메소드를 수신하고 있으며 구성 요소 조치에서 조치를 트리거하여 자체 상점을 업데이트합니다.

어떻게 든 도움이되기를 바랍니다.