React.js : 한 컴포넌트를 다른 컴포넌트로 랩핑


187

많은 템플릿 언어에는 "슬롯"또는 "수율"문이있어 한 템플릿을 다른 템플릿 안에 감쌀 수 있도록 일종의 제어 반전을 수행 할 수 있습니다.

Angular에는 "transclude"옵션이 있습니다.

Rails는 yield statement를 가지고 있습니다. React.js에 yield 문이 있으면 다음과 같습니다.

var Wrapper = React.createClass({
  render: function() {
    return (
      <div className="wrapper">
        before
          <yield/>
        after
      </div>
    );
  }
});

var Main = React.createClass({
  render: function() {
    return (
      <Wrapper><h1>content</h1></Wrapper>
    );
  }
});

원하는 출력 :

<div class="wrapper">
  before
    <h1>content</h1>
  after
</div>

아아, React.js에는가 없습니다 <yield/>. 동일한 출력을 얻기 위해 래퍼 구성 요소를 어떻게 정의합니까?


답변:



159

사용 children

const Wrapper = ({children}) => (
  <div>
    <div>header</div>
    <div>{children}</div>
    <div>footer</div>
  </div>
);

const App = ({name}) => <div>Hello {name}</div>;

const WrappedApp = ({name}) => (
  <Wrapper>
    <App name={name}/>
  </Wrapper>
);

render(<WrappedApp name="toto"/>,node);

이것은 transclusionAngular 라고도 합니다.

childrenReact의 특별한 소품이며 구성 요소의 태그 안에있는 것을 포함합니다 (여기 <App name={name}/>에는 inside Wrapper이므로children

children구성 요소에 고유 한을 반드시 사용해야 할 필요는 없으며 원하는 경우 일반 소품도 사용하거나 소품과 자식을 혼합 할 수 있습니다.

const AppLayout = ({header,footer,children}) => (
  <div className="app">
    <div className="header">{header}</div>
    <div className="body">{children}</div>
    <div className="footer">{footer}</div>
  </div>
);

const appElement = (
  <AppLayout 
    header={<div>header</div>}
    footer={<div>footer</div>}
  >
    <div>body</div>
  </AppLayout>
);

render(appElement,node);

이것은 많은 유스 케이스에 간단하고 훌륭하며 대부분의 소비자 앱에 권장합니다.


소품을 렌더링

렌더링 함수를 구성 요소에 전달할 수 있으며이 패턴은 일반적으로이라고 render prop하며 childrenprop은 종종 해당 콜백을 제공하는 데 사용됩니다.

이 패턴은 실제로 레이아웃 용이 아닙니다. 랩퍼 구성 요소는 일반적으로 상태를 유지 및 관리하고 렌더링 기능에 삽입하는 데 사용됩니다.

카운터 예 :

const Counter = () => (
  <State initial={0}>
    {(val, set) => (
      <div onClick={() => set(val + 1)}>  
        clicked {val} times
      </div>
    )}
  </State>
); 

훨씬 더 화려하고 객체를 제공 할 수 있습니다

<Promise promise={somePromise}>
  {{
    loading: () => <div>...</div>,
    success: (data) => <div>{data.something}</div>,
    error: (e) => <div>{e.message}</div>,
  }}
</Promise>

반드시 사용할 필요는 없습니다 children. 맛 / API 문제입니다.

<Promise 
  promise={somePromise}
  renderLoading={() => <div>...</div>}
  renderSuccess={(data) => <div>{data.something}</div>}
  renderError={(e) => <div>{e.message}</div>}
/>

오늘날 많은 도서관은 사람들이이 API를 HOC보다 쉽게 ​​찾는 경향이 있기 때문에 렌더링 소품 (React context, React-motion, Apollo ...)을 사용하고 있습니다. react-powerplug 는 간단한 렌더링 소품 구성 요소 모음입니다. react-adopt 는 작곡을 도와줍니다.


더 높은 주문 구성 요소 (HOC).

const wrapHOC = (WrappedComponent) => {
  class Wrapper extends React.PureComponent {
    render() {
      return (
        <div>
          <div>header</div>
          <div><WrappedComponent {...this.props}/></div>
          <div>footer</div>
        </div>
      );
    }  
  }
  return Wrapper;
}

const App = ({name}) => <div>Hello {name}</div>;

const WrappedApp = wrapHOC(App);

render(<WrappedApp name="toto"/>,node);

고차원 적 구성 요소 / HOC는 일반적으로 구성 요소를 소요하고 새 구성 요소를 반환하는 함수이다.

고차원 적 구성 요소를 사용하면 사용하는 것보다 더 확대됨에 될 수 children또는 render props래퍼는 단락 회로로 렌더링 한 단계 앞서 수있는 능력을 가질 수 있기 때문에, shouldComponentUpdate.

여기서 우리는을 사용하고 PureComponent있습니다. 앱을 다시 렌더링 할 때 WrappedApp이름 소품이 시간이 지남에 따라 변경되지 않으면 래퍼는 "소품 (실제로 이름)이 이전과 같기 때문에 렌더링 할 필요가 없습니다"라고 말할 수 있습니다. 위의 children기반 솔루션을 사용하면 래퍼가 PureComponent인 경우에도 부모가 렌더링 할 때마다 자식 요소가 다시 만들어지기 때문에 그렇지 않습니다. 이는 래핑 된 구성 요소가 순수한 경우에도 래퍼가 항상 다시 렌더링 될 수 있음을 의미합니다. 이것을 완화 하고 시간이 지남에 따라 일정한 요소를 보장하는 데 도움 이되는 babel 플러그인 이 있습니다 children.


결론

주문이 많을수록 성능이 향상됩니다. 그렇게 복잡하지는 않지만 처음에는 비우호적 인 것처럼 보입니다.

이 코드를 읽은 후 전체 코드베이스를 HOC로 마이그레이션하지 마십시오. 앱의 중요한 경로에서 성능상의 이유로 런타임 래퍼 대신 HOC를 사용할 수 있습니다. 특히 동일한 래퍼를 여러 번 사용하는 경우 HOC로 만드는 것이 좋습니다.

Redux는 처음에는 런타임 래퍼를 사용 <Connect>하고 나중에 connect(options)(Comp)성능상의 이유로 HOC로 전환했습니다 (기본적으로 래퍼는 순수하고 사용 shouldComponentUpdate). 이것은이 답변에서 강조하고 싶었던 것을 완벽하게 보여줍니다.

컴포넌트에 render-prop API가있는 경우 일반적으로 그 위에 HOC를 작성하는 것이 쉽습니다. 따라서 lib 작성자 인 경우 먼저 render prop API를 작성하고 결국 HOC 버전을 제공해야합니다. 이것이 Apollo가 <Query>render-prop 구성 요소와 graphqlHOC를 사용하여 수행하는 작업입니다.

개인적으로 두 가지를 모두 사용하지만 의심 스러울 때 HOC를 선호합니다.

  • compose(hoc1,hoc2)(Comp)렌더링 소품과 비교하여 작곡하는 것이 관용적입니다 ( ).
  • 그것은 더 나은 공연을 줄 수 있습니다
  • 이 스타일의 프로그래밍에 익숙합니다

내가 좋아하는 도구의 HOC 버전을 사용 / 만들기를 주저하지 않습니다.

  • 반응의 Context.Consumercomp
  • 미지의 Subscribe
  • 렌더 소품 graphql대신 아폴로 HOC 사용Query

내 의견으로는, 때로는 렌더링 소품이 코드를 더 읽기 쉽고 때로는 더 적게 만듭니다 ... 나는 내가 가진 제약 조건에 따라 가장 실용적인 솔루션을 사용하려고합니다. 때로는 가독성이 공연보다 중요하지만 때로는 그렇지 않습니다. 현명하게 선택하고 2018 년 모든 것을 렌더 소품으로 변환하는 추세를 따르지 마십시오.


1
이 방법을 사용하면 소품을 하위 구성 요소 (이 경우 Hello)로 쉽게 전달할 수 있습니다. React 0.14. *부터 소품을 하위 구성 요소로 전달하는 유일한 방법은 React.createClone을 사용하는 것입니다.
Mukesh Soni

2
질문 : 대답은 "더 나은 성능"을 언급합니다. 이해하지 못하는 것 : 다른 솔루션에 비해 더 나은가?
Philipp

1
HOC는 런타임 래퍼에 비해 성능이 더 좋을 수 있습니다. 렌더링을 더 일찍 단락시킬 수 있기 때문입니다.
Sebastien Lorber

1
감사합니다! 👍 내 달 말을했다 것입니다하지만 당신은 더 큰 재능을 표현
MacKentoch

1
이것은 훨씬 더 나은 답변입니다 :] 감사합니다!
cullanrocks

31

Sophie의 답변 외에도 하위 구성 요소 유형을 보내면서 다음과 같은 작업을 수행하는 용도를 찾았습니다.

var ListView = React.createClass({
    render: function() {
        var items = this.props.data.map(function(item) {
            return this.props.delegate({data:item});
        }.bind(this));
        return <ul>{items}</ul>;
    }
});

var ItemDelegate = React.createClass({
    render: function() {
        return <li>{this.props.data}</li>
    }
});

var Wrapper = React.createClass({    
    render: function() {
        return <ListView delegate={ItemDelegate} data={someListOfData} />
    }
});

2
에 대한 문서를 보지 못했습니다 delegate. 어떻게 찾았습니까?
NVI

4
행 4에서 this.props.delegate를 사용하여 구성 요소에 원하는 소품을 추가하고 원하는 이름을 지정할 수 있지만 다른 이름을 지정할 수도 있습니다.
krs
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.