React.js: Gói một thành phần vào thành phần khác


187

Nhiều ngôn ngữ mẫu có các câu lệnh "slot" hoặc "ield", cho phép thực hiện một số loại điều khiển đảo ngược để bọc một mẫu bên trong một mẫu khác.

Angular có tùy chọn "transclude" .

Rails có tuyên bố năng suất . Nếu React.js có tuyên bố lợi suất, nó sẽ trông như thế này:

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

Sản phẩm chất lượng:

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

Than ôi, React.js không có <yield/>. Làm cách nào để xác định thành phần Wrapper để đạt được cùng một đầu ra?


Câu trả lời:



159

Sử dụng 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);

Điều này cũng được gọi là transclusiontrong Angular.

childrenlà một chỗ dựa đặc biệt trong React và sẽ chứa những gì bên trong các thẻ thành phần của bạn (đây <App name={name}/>là bên trong Wrapper, vì vậy nó làchildren

Lưu ý rằng bạn không nhất thiết phải sử dụng children, là duy nhất cho một thành phần và bạn cũng có thể sử dụng đạo cụ bình thường nếu bạn muốn, hoặc trộn đạo cụ và trẻ em:

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

Điều này đơn giản và tốt cho nhiều usecase và tôi khuyên bạn nên dùng nó cho hầu hết các ứng dụng dành cho người tiêu dùng.


đạo cụ kết xuất

Có thể truyền các hàm kết xuất cho một thành phần, mẫu này thường được gọi render propchildrenprop thường được sử dụng để cung cấp hàm gọi lại đó.

Mẫu này không thực sự có ý nghĩa cho bố cục. Thành phần trình bao bọc thường được sử dụng để giữ và quản lý một số trạng thái và đưa nó vào các chức năng kết xuất của nó.

Ví dụ truy cập:

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

Bạn có thể thậm chí còn lạ mắt hơn và thậm chí cung cấp một đối tượng

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

Lưu ý bạn không nhất thiết phải sử dụng children, đó là vấn đề về hương vị / API.

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

Cho đến ngày nay, nhiều thư viện đang sử dụng các đạo cụ kết xuất (bối cảnh React, React-motion, Apollo ...) vì mọi người có xu hướng tìm thấy API này dễ dàng hơn HOC. Reac-powerplug là một tập hợp các thành phần render-prop đơn giản. phản ứng-thông qua giúp bạn làm thành phần.


Các thành phần bậc cao (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);

Một bậc cao Component / HOC thường là một chức năng mà phải mất một phần và trả về một thành phần mới.

Sử dụng Thành phần bậc cao có thể hiệu quả hơn so với sử dụng childrenhoặc render props, bởi vì trình bao bọc có thể có khả năng đoản mạch kết xuất trước một bước shouldComponentUpdate.

Ở đây chúng tôi đang sử dụng PureComponent. Khi kết xuất lại ứng dụng, nếu WrappedApptên prop không thay đổi theo thời gian, trình bao bọc có khả năng nói "Tôi không cần kết xuất vì đạo cụ (thực ra, tên) vẫn giống như trước". Với childrengiải pháp dựa trên, ngay cả khi trình bao bọc PureComponentkhông phải là trường hợp vì phần tử con được tạo lại mỗi khi cha mẹ kết xuất, điều đó có nghĩa là trình bao bọc có thể sẽ luôn hiển thị lại, ngay cả khi thành phần được bao bọc hoàn toàn. Có một plugin babel có thể giúp giảm thiểu điều này và đảm bảo một childrenyếu tố không đổi theo thời gian.


Phần kết luận

Các thành phần bậc cao hơn có thể cung cấp cho bạn hiệu suất tốt hơn. Nó không quá phức tạp nhưng ban đầu chắc chắn trông không thân thiện.

Đừng di chuyển toàn bộ cơ sở mã của bạn sang HOC sau khi đọc nó. Chỉ cần nhớ rằng trên các đường dẫn quan trọng của ứng dụng, bạn có thể muốn sử dụng HOC thay vì trình bao bọc thời gian chạy vì lý do hiệu suất, đặc biệt nếu cùng một trình bao bọc được sử dụng nhiều lần thì nên xem xét biến nó thành HOC.

Redux ban đầu được sử dụng một trình bao bọc thời gian chạy <Connect>và sau đó chuyển sang HOC connect(options)(Comp)vì lý do hiệu suất (theo mặc định, trình bao bọc là thuần túy và sử dụng shouldComponentUpdate). Đây là minh họa hoàn hảo cho những gì tôi muốn làm nổi bật trong câu trả lời này.

Lưu ý nếu một thành phần có API kết xuất, bạn thường dễ dàng tạo HOC trên đầu trang, vì vậy nếu bạn là tác giả lib, trước tiên bạn nên viết API kết xuất và cuối cùng cung cấp phiên bản HOC. Đây là những gì Apollo làm với <Query>thành phần render-prop và graphqlHOC sử dụng nó.

Cá nhân, tôi sử dụng cả hai, nhưng khi nghi ngờ tôi thích HOC hơn vì:

  • Thật đơn giản hơn khi soạn chúng ( compose(hoc1,hoc2)(Comp)) so với đạo cụ kết xuất
  • Nó có thể cho tôi những màn trình diễn tốt hơn
  • Tôi quen thuộc với phong cách lập trình này

Tôi không ngần ngại sử dụng / tạo các phiên bản HOC của các công cụ yêu thích của mình:

  • Phản ứng của Context.Consumercomp
  • Không nói Subscribe
  • sử dụng graphqlHOC của Apollo thay vì Querykết xuất prop

Theo tôi, đôi khi các đạo cụ kết xuất làm cho mã dễ đọc hơn, đôi khi ít hơn ... Tôi cố gắng sử dụng giải pháp thực dụng nhất theo các ràng buộc mà tôi có. Đôi khi khả năng đọc quan trọng hơn màn trình diễn, đôi khi không. Chọn một cách khôn ngoan và không ràng buộc theo xu hướng năm 2018 là chuyển đổi mọi thứ thành đạo cụ kết xuất.


1
Cách tiếp cận này cũng giúp bạn dễ dàng chuyển các đạo cụ xuống thành phần trẻ em (trong trường hợp này là Hello). Từ React 0.14. * Trở đi, cách duy nhất để truyền đạo cụ cho các thành phần trẻ em là sử dụng React.createClone, có thể rất tốn kém.
Mukesh Soni

2
Câu hỏi: Câu trả lời đề cập đến "hiệu suất tốt hơn" - điều tôi không hiểu: tốt hơn so với giải pháp nào khác?
Philipp

1
Các HOC có thể có hiệu suất tốt hơn so với các trình bao bọc thời gian chạy, vì chúng có thể làm ngắn mạch kết xuất trước đó.
Sebastien Lorber

1
Cảm ơn bạn! Giống như bạn đã nhận lời từ tháng của tôi nhưng bạn thể hiện chúng với tài năng lớn hơn 👍
MacKentoch

1
Đây là một câu trả lời tốt hơn nhiều:] Cảm ơn!
cullanrocks

31

Ngoài câu trả lời của Sophie, tôi cũng đã tìm thấy một cách sử dụng trong việc gửi các loại thành phần con, làm một cái gì đó như thế này:

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
Tôi chưa thấy tài liệu nào về delegate, làm thế nào bạn tìm thấy nó?
NVI

4
bạn có thể thêm bất kỳ đạo cụ nào bạn muốn vào một thành phần và đặt tên cho chúng theo ý muốn, tôi đang sử dụng this.props.delegate ở hàng 4 nhưng cũng có thể đặt tên cho nó một cái gì đó khác.
krs
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.