ReactJS 두 컴포넌트 통신


321

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

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

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


세 가지 형제 구성 요소가 모두 있습니까?
pgreen2

그것들은 모두 세 가지 구성 요소이며, 이미 데이터를 제공 할 수있는 동일한 부모를 갖도록 이미 응용 프로그램을 다시 정렬했습니다.
woutr_be

4
여기서 플럭스 또는 pubsub 패턴을 사용할 수 있습니다. 반응 형 문서의 문서를 기반으로하여 다소 모호한 문장을 남깁니다. "부모-자녀 관계가없는 두 구성 요소 간의 통신을 위해 자체 글로벌 이벤트 시스템을 설정할 수 있습니다." facebook.github.io/react/tips/…
BingeBoy

@BingeBoy가 맞습니다 Flux 는 reactjs 앱을 작성하는 훌륭한 방법으로, 데이터 흐름, 많은 구성 요소의 데이터 공유 문제를 처리 할 수 ​​있습니다.
Ankit Patial

4
Flux 나 Redux에 들어가고 싶지 않다면이 주제에 관한 훌륭한 기사입니다. andrewhfarmer.com/component-communication
garajo

답변:


318

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

  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

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


6
같은 기능 : # 2의 좋은 점은 각 구성 요소에 소품을 전달하는 부모에 의존한다는 것입니다 updateFilter<Filters />하고 배열 items<List />. 이러한 하위 구성 요소를 함께 또는 단독으로 다른 동작을 가진 다른 부모에서 사용할 수 있습니다. 예를 들어 동적 목록을 표시하려고하지만 필터링이 필요하지 않은 경우.
Michael LaCroix

2
@woutr_be 요구 사항에 맞는지 확실하지 않지만 비슷한 상황에서 언젠가는 자식과 부모 구성 요소 간의 통신을 위해 다음 두 가지 기능을 사용했습니다. window.document) .bind (eventName, eventCallback);} triggerEvent : function (eventName, params) {$ .event.trigger (eventName, params);} 도움이 되길 바랍니다. (죄송합니다. 더 나은 형식을
지정할

29
시나리오 3의 경우 권장되는 접근 방법이 있습니까? 맞춤형 합성 이벤트를 생성하여 이에 대한 문서 나 예제가 있습니까? 주요 문서에서 아무것도 찾지 못했습니다.
pwray

1
시나리오 # 2 ... 많은 이해를 할 때까지 당신이 (해당되는 경우에만, 레이아웃) 디자인을 위태롭게 할 필요가 - 당신은 EventHub / PubSub의 필요성을 알고 있습니다.
Cody

4
시나리오 # 3 링크가 종료되었으며 관련없는 React 문서 페이지로 리디렉션됩니다.
beporter

170

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

반응

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

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 비디오 의 인상적인 라이브 코딩 이 가능합니다!


2
훌륭한 답변 Seb!
알리 Gajani

7

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

Redux 가 무엇 을합니까?

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

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

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

리덕스 및 플럭스

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

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

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

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

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

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

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


redux는 어떻게 도울 수 있습니까? datepicker(구성 요소로)에 대한 모달이 있고 해당 구성 요소를 동일한 페이지에있는 여러 구성 요소에서로드 할 수 있다면 구성 요소는 redux에 발송 할 작업 을 어떻게 datepicker알 수 있습니까? 이것이 문제의 본질이며 한 구성 요소 의 작업을 다른 구성 요소가 아닌 다른 구성 요소에 연결 합니다. ( 자체 자체는 모달 컴포넌트 자체의 깊고 깊은 컴포넌트임을 고려하십시오 )datepicker
vsync

@vsync는 reudx를 단일 정적 값으로 생각하지 않습니다 .redux는 실제로 객체, 배열을 가질 수 있습니다 ... 따라서 객체 또는 배열로 저장하거나 상점에서 무엇이든 저장할 수 있습니다. 예 : [{name : "picker1", value : "01/01/1970"}, {name : "picker2", value : "01/01/1980"}] 그리고 부모에서 mapstatetoprops를 사용하여 각 구성 요소 또는 원하는 곳에서 질문에 대답하는지 확실하지 않지만 코드를 보지 않고 ... 별도의 그룹에 있으면 더 자세한 내용으로 이의를 제기 할 수 있지만 그룹화하려는 방법에 달려 있습니다. 그들에 ..
알리레자

문제는 redux무엇을 저장할 수 있는지 에 관한 것이 아니라 어떻게 조치를 취해야하는지에 대한 세부 사항을 전달하는 방법 입니다. 딥 구성 요소는 정확히 트리거해야하는 항목을 어떻게 알 수 있습니까? 이 예에서는 시나리오에 따라 특정 감속기로 트리거 해야하는 일반 구성 요소를 제공 했으므로 datepicker 모달을 모든 구성 요소에 사용할 수 있으므로 다른 감속기가 될 수 있습니다.
vsync

5

이것이 내가 처리 한 방법입니다. 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>

예제 코드의 80 %를 삭제하고 요점을 유지할 수 있다면 가장 좋습니다. 이 스레드의 컨텍스트에 CSS가 관련이 없음을 보여줌
vsync

3

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

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

1

구성 요소가 어떤 종류의 부모-자식 관계간에 통신 할 수없는 경우 @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() {

    }
}

0

부모-자녀 관계가 아닌 경우에도 그러한 가능성이 있습니다. 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는 상점의 메소드를 수신하고 있으며 구성 요소 조치에서 조치를 트리거하여 자체 상점을 업데이트합니다.

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


0

구성 요소 간 통신 옵션을 탐색하고 점점 어려워지고 있다고 생각되면 Flux 디자인 패턴을 채택하는 것이 좋습니다 .

응용 프로그램 전체 상태를 저장 및 변경하고 해당 상태를 사용하여 구성 요소를 렌더링하는 방법을 정의하는 규칙 모음입니다.

많은 Flux 구현이 있으며 Facebook의 공식 구현 은 그 중 하나입니다. 대부분의 상용구 코드를 포함하는 것으로 간주되지만 대부분의 내용이 명시 적이므로 이해하기가 더 쉽습니다.

다른 대안 중 일부는 flummox fluxxor fluxibleredux 입니다.


0

나는 한때 당신이 지금있는 곳이었습니다. 내가 생각하는 것과 같은 방식으로 해결하려고 노력할 것입니다.

국가는 의사 소통의 초석

일반적으로 문제는 세 가지 구성 요소를 지적하는 경우이 구성 요소의 상태를 변경하는 방법입니다.

<List />: 필터에 따라 항목 목록이 표시 <Filters />될 수 있습니다. 데이터를 변경하는 필터 옵션. <TopBar />: 옵션 목록.

이 모든 상호 작용을 조정하려면 더 높은 구성 요소가 필요합니다 .App이라고 부르면 작업과 데이터를이 구성 요소 각각에 전달하여 예를 들어 다음과 같이 보일 수 있습니다

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

따라서 setFilter호출되면 필터링 된 항목에 영향을 미치고 두 구성 요소를 다시 렌더링합니다. 이것이 명확하지 않은 경우 단일 파일을 체크인 할 수있는 확인란이있는 예제를 만들었습니다.

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

다음 코드는 두 형제 사이의 통신을 설정하는 데 도움이됩니다. 설정은 render () 및 componentDidMount () 호출 중에 부모에서 수행됩니다. 그것은 https://reactjs.org/docs/refs-and-the-dom.html을 기반으로 합니다.

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

이것은 TypeScript이므로 답에 언급해야합니다. 그래도 좋은 개념입니다.
serraosays 19

0

이상하게 아무도 언급하지 않았습니다 mobx. 아이디어는와 비슷합니다 redux. 여러 구성 요소를 구독 한 데이터가 있으면이 데이터를 사용하여 여러 구성 요소를 구동 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.