Phản ứng thành phần không trạng thái chức năng, PureComponent, Thành phần; sự khác biệt là gì và khi nào chúng ta nên sử dụng cái gì?


188

Đến để biết rằng từ React v15.3.0 , chúng ta có một lớp cơ sở mới gọi là PureComponent để mở rộng với PureRenderMixin tích hợp. Những gì tôi hiểu là, dưới mui xe này sử dụng một so sánh nông cạn của các đạo cụ bên trong shouldComponentUpdate.

Bây giờ chúng ta có 3 cách để xác định thành phần React:

  1. Thành phần không trạng thái chức năng không mở rộng bất kỳ lớp nào
  2. Một thành phần mở rộng PureComponentlớp
  3. Một thành phần bình thường mở rộng Componentlớp

Thỉnh thoảng chúng tôi thường gọi các thành phần không trạng thái là Thành phần thuần túy, hoặc thậm chí là Thành phần câm. Có vẻ như toàn bộ định nghĩa của từ "thuần túy" đã thay đổi trong React.

Mặc dù tôi hiểu sự khác biệt cơ bản giữa ba điều này, tôi vẫn không chắc chắn khi nào nên chọn cái gì . Ngoài ra các tác động hiệu suất và sự đánh đổi của mỗi là gì?


Cập nhật :

Đây là những câu hỏi tôi mong đợi để được làm rõ:

  • Tôi nên chọn định nghĩa các thành phần đơn giản của mình là chức năng (vì mục đích đơn giản) hoặc mở rộng PureComponentlớp (vì mục đích hiệu suất)?
  • Là sự tăng cường hiệu suất mà tôi có được một sự đánh đổi thực sự cho sự đơn giản mà tôi đã mất?
  • Tôi có bao giờ cần phải mở rộng Componentlớp bình thường khi tôi luôn có thể sử dụng PureComponentđể có hiệu suất tốt hơn không?

Câu trả lời:


315

Làm thế nào để bạn quyết định, làm thế nào để bạn chọn giữa ba dựa trên mục đích / kích thước / đạo cụ / hành vi của các thành phần của chúng tôi?

Mở rộng từ React.PureComponenthoặc từ React.Componentmột shouldComponentUpdatephương thức tùy chỉnh có ý nghĩa về hiệu suất. Sử dụng các thành phần chức năng không trạng thái là một lựa chọn "kiến trúc" và không có bất kỳ lợi ích hiệu suất nào ngoài hộp (chưa).

  • Đối với các thành phần đơn giản, chỉ mang tính trình bày cần được tái sử dụng dễ dàng, thích các thành phần chức năng không trạng thái. Bằng cách này, bạn chắc chắn rằng chúng được tách rời khỏi logic ứng dụng thực tế, rằng chúng rất dễ kiểm tra và chúng không có tác dụng phụ không mong muốn. Ngoại lệ là nếu vì một lý do nào đó bạn có rất nhiều trong số họ hoặc nếu bạn thực sự cần tối ưu hóa phương thức kết xuất của họ (vì bạn không thể xác định shouldComponentUpdatecho một thành phần chức năng không trạng thái).

  • Mở rộng PureComponentnếu bạn biết đầu ra của mình phụ thuộc vào đạo cụ / trạng thái đơn giản ("đơn giản" nghĩa là không có cấu trúc dữ liệu lồng nhau, vì PureComponent thực hiện so sánh nông) VÀ bạn cần / có thể nhận được một số cải tiến hiệu suất.

  • Mở rộng Componentvà thực hiện của riêng shouldComponentUpdatebạn nếu bạn cần một số tăng hiệu suất bằng cách thực hiện logic so sánh tùy chỉnh giữa các đạo cụ tiếp theo / hiện tại và trạng thái. Ví dụ: bạn có thể nhanh chóng thực hiện so sánh sâu bằng cách sử dụng lodash # isEqual:

    class MyComponent extends Component {
        shouldComponentUpdate (nextProps, nextState) {
            return !_.isEqual(this.props, nextProps) || !_.isEqual(this.state, nextState);
        }
    }

Ngoài ra, thực hiện của riêng bạn shouldComponentUpdatehoặc mở rộng từ PureComponentlà tối ưu hóa, và như thường lệ, bạn chỉ nên bắt đầu xem xét điều đó nếu bạn có vấn đề về hiệu suất ( tránh tối ưu hóa sớm ). Theo nguyên tắc thông thường, tôi luôn cố gắng thực hiện các tối ưu hóa này sau khi ứng dụng ở trạng thái hoạt động, với hầu hết các tính năng đã được triển khai. Việc tập trung vào các vấn đề hiệu suất sẽ dễ dàng hơn rất nhiều khi chúng thực sự cản trở.

Thêm chi tiết

Thành phần không trạng thái chức năng:

Chúng được định nghĩa chỉ bằng cách sử dụng một chức năng. Vì không có trạng thái bên trong cho một thành phần không trạng thái, đầu ra (những gì được hiển thị) chỉ phụ thuộc vào các đạo cụ được cung cấp làm đầu vào cho chức năng này.

Ưu điểm:

  • Cách đơn giản nhất có thể để xác định một thành phần trong React. Nếu bạn không cần quản lý bất kỳ trạng thái nào, tại sao phải bận tâm với các lớp và kế thừa? Một trong những khác biệt chính giữa hàm và lớp là với hàm bạn chắc chắn đầu ra chỉ phụ thuộc vào đầu vào (không phụ thuộc vào bất kỳ lịch sử nào của các lần thực hiện trước đó).

  • Lý tưởng nhất trong ứng dụng của bạn, bạn nên đặt mục tiêu có càng nhiều thành phần không trạng thái càng tốt, bởi vì điều đó thường có nghĩa là bạn đã di chuyển logic của mình ra khỏi lớp khung nhìn và chuyển nó sang một thứ như redux, có nghĩa là bạn có thể kiểm tra logic thực của mình mà không cần phải hiển thị bất cứ điều gì (dễ kiểm tra hơn nhiều, có thể tái sử dụng nhiều hơn, v.v.).

Nhược điểm:

  • Không có phương pháp vòng đời. Bạn không có cách nào để xác định componentDidMountvà những người bạn khác. Thông thường bạn làm điều đó trong một thành phần cha mẹ cao hơn trong hệ thống phân cấp để bạn có thể biến tất cả trẻ em thành những người không quốc tịch.

  • Không có cách nào để kiểm soát thủ công khi cần kết xuất lại, vì bạn không thể xác định shouldComponentUpdate. Kết xuất lại xảy ra mỗi khi thành phần nhận được đạo cụ mới (không có cách nào để so sánh nông, v.v.). Trong tương lai, React có thể tự động tối ưu hóa các thành phần không trạng thái, hiện tại có một số thư viện bạn có thể sử dụng. Vì các thành phần không trạng thái chỉ là các chức năng, về cơ bản, đây là vấn đề kinh điển của "ghi nhớ chức năng".

  • Tham chiếu không được hỗ trợ: https://github.com/facebook/react/issues/4936

Một thành phần mở rộng lớp PureComponent VS Một thành phần bình thường mở rộng lớp Thành phần:

React được sử dụng để có một PureRenderMixinbạn có thể đính kèm vào một lớp được xác định bằng React.createClasscú pháp. Mixin chỉ đơn giản là định nghĩa một phép shouldComponentUpdateso sánh nông giữa các đạo cụ tiếp theo và trạng thái tiếp theo để kiểm tra xem có gì thay đổi không. Nếu không có gì thay đổi, thì không cần phải thực hiện kết xuất lại.

Nếu bạn muốn sử dụng cú pháp ES6, bạn không thể sử dụng mixins. Vì vậy, để thuận tiện, React đã giới thiệu một PureComponentlớp mà bạn có thể kế thừa từ thay vì sử dụng Component. PureComponentchỉ thực hiện shouldComponentUpdatetheo cùng một cách của PureRendererMixin. Đây chủ yếu là một điều thuận tiện để bạn không phải tự mình thực hiện, vì so sánh nông cạn giữa trạng thái hiện tại / tiếp theo và đạo cụ có lẽ là kịch bản phổ biến nhất có thể mang lại cho bạn một số chiến thắng hiệu suất nhanh.

Thí dụ:

class UserAvatar extends Component {
    render() {
       return <div><img src={this.props.imageUrl} /> {{ this.props.username }} </div>
    }
} 

Như bạn có thể thấy đầu ra phụ thuộc vào props.imageUrlprops.username. Nếu trong một thành phần cha mẹ bạn kết xuất <UserAvatar username="fabio" imageUrl="http://foo.com/fabio.jpg" />với cùng một đạo cụ, React sẽ gọi rendermọi lúc, ngay cả khi đầu ra sẽ giống hệt nhau. Tuy nhiên, hãy nhớ rằng React thực hiện dom diffing, vì vậy DOM sẽ không thực sự được cập nhật. Tuy nhiên, thực hiện dom diffing có thể tốn kém, vì vậy trong kịch bản này sẽ là một sự lãng phí.

Nếu UserAvatarthành phần mở rộng PureComponentthay thế, một so sánh nông được thực hiện. Và bởi vì đạo cụ và nextProps giống nhau, rendersẽ không được gọi chút nào.

Lưu ý về định nghĩa "thuần túy" trong React:

Nói chung, "hàm thuần túy" là một hàm luôn luôn đánh giá cùng một kết quả với cùng một đầu vào. Đầu ra (đối với React, đó là những gì được renderphương thức trả về ) không phụ thuộc vào bất kỳ lịch sử / trạng thái nào và nó không có bất kỳ tác dụng phụ nào (các hoạt động thay đổi "thế giới" bên ngoài chức năng).

Trong React, các thành phần không trạng thái không nhất thiết là các thành phần thuần túy theo định nghĩa ở trên nếu bạn gọi "statless" là thành phần không bao giờ gọi this.setStatevà không sử dụng this.state.

Trong thực tế, trong một PureComponent, bạn vẫn có thể thực hiện các tác dụng phụ trong các phương pháp vòng đời. Ví dụ: bạn có thể gửi yêu cầu ajax bên trong componentDidMounthoặc bạn có thể thực hiện một số tính toán DOM để tự động điều chỉnh chiều cao của div bên trong render.

Định nghĩa "thành phần câm" có ý nghĩa "thực tế" hơn (ít nhất là theo cách hiểu của tôi): một thành phần câm "được nói" phải làm gì bởi một thành phần cha mẹ thông qua đạo cụ và không biết cách làm gì ngoài việc sử dụng đạo cụ gọi lại thay thế.

Ví dụ về một "thông minh" AvatarComponent:

class AvatarComponent extends Component {
    expandAvatar () {
        this.setState({ loading: true });
        sendAjaxRequest(...).then(() => {
            this.setState({ loading: false });
        });
    }        

    render () {
        <div onClick={this.expandAvatar}>
            <img src={this.props.username} />
        </div>
    }
}

Ví dụ về một "người câm" AvatarComponent:

class AvatarComponent extends Component {
    render () {
        <div onClick={this.props.onExpandAvatar}>
            {this.props.loading && <div className="spinner" />}
            <img src={this.props.username} />
        </div>
    }
}

Cuối cùng tôi sẽ nói rằng "ngu ngốc", "không quốc tịch" và "thuần túy" là những khái niệm khá khác nhau đôi khi có thể trùng lặp, nhưng không nhất thiết, phụ thuộc chủ yếu vào trường hợp sử dụng của bạn.


1
Tôi thực sự đánh giá cao câu trả lời của bạn và kiến ​​thức bạn đã chia sẻ. Nhưng câu hỏi thực sự của tôi là khi nào chúng ta nên chọn cái gì? . Đối với cùng một ví dụ bạn đã đề cập trong câu trả lời của mình, tôi nên định nghĩa nó như thế nào? Nó nên là thành phần không trạng thái chức năng (nếu vậy tại sao?), Hoặc mở rộng PureComponent (tại sao?) Hoặc mở rộng lớp Thành phần (một lần nữa tại sao?). Làm thế nào để bạn quyết định, làm thế nào để bạn chọn giữa ba dựa trên mục đích / kích thước / đạo cụ / hành vi của các thành phần của chúng tôi?
Yadhu Kiran

1
Không vấn đề gì. Đối với thành phần không trạng thái chức năng, có một danh sách ưu / nhược điểm mà tôi có thể xem xét để quyết định xem điều đó có phù hợp hay không. Điều đó có trả lời bạn điểm đầu tiên không? Tôi sẽ cố gắng giải quyết câu hỏi lựa chọn nhiều hơn một chút.
fabio.sussetto

2
Các thành phần chức năng luôn được kết xuất lại khi thành phần cha mẹ được cập nhật, ngay cả khi chúng không sử dụng props. Ví dụ .
AlexM

1
Đây là một trong những câu trả lời toàn diện nhất mà tôi đã đọc trong một thời gian khá dài. Công việc tuyệt vời Một nhận xét về câu đầu tiên: Khi mở rộng PureComponent, bạn không nên thực hiện shouldComponentUpdate(). Bạn sẽ thấy một cảnh báo nếu bạn thực sự làm điều này.
jjramos

1
Để tăng hiệu suất thực sự, bạn nên thử sử dụng PureComponentcho các thành phần DO có các thuộc tính đối tượng / mảng lồng nhau. Tất nhiên bạn phải nhận thức được những gì đang xảy ra. Nếu tôi hiểu chính xác, nếu bạn không trực tiếp thay đổi đạo cụ / trạng thái (mà React cố ngăn bạn thực hiện với các cảnh báo) hoặc thông qua một thư viện bên ngoài, thì bạn sẽ ổn khi sử dụng PureComponentthay vì Componentkhá nhiều ở mọi nơi ... ngoại trừ của các thành phần rất đơn giản, nơi nó thực sự có thể nhanh hơn KHÔNG sử dụng nó - xem news.ycombinator.com/item?id=14418576
Matt Browne

28

Tôi không phải là một thiên tài về phản ứng, nhưng theo hiểu biết của tôi, chúng ta có thể sử dụng từng thành phần trong các tình huống sau đây

  1. Thành phần không trạng thái - đây là thành phần không có vòng đời, vì vậy những thành phần đó nên được sử dụng để kết xuất thành phần lặp lại của thành phần cha mẹ, chẳng hạn như hiển thị danh sách văn bản chỉ hiển thị thông tin và không có bất kỳ hành động nào để thực hiện.

  2. Thành phần thuần túy - đây là những vật phẩm có vòng đời và chúng sẽ luôn trả lại kết quả tương tự khi một bộ đạo cụ cụ thể được đưa ra. Các thành phần này có thể được sử dụng khi hiển thị danh sách kết quả hoặc dữ liệu đối tượng cụ thể không có các phần tử con phức tạp và được sử dụng để thực hiện các hoạt động chỉ tác động đến chính nó. như một danh sách hiển thị thẻ người dùng hoặc danh sách thẻ sản phẩm (thông tin sản phẩm cơ bản) và chỉ người dùng hành động mới có thể thực hiện là nhấp để xem trang chi tiết hoặc thêm vào giỏ hàng.

  3. Các thành phần thông thường hoặc các thành phần phức tạp - Tôi đã sử dụng thuật ngữ thành phần phức tạp bởi vì chúng thường là các thành phần cấp độ trang và bao gồm rất nhiều thành phần con và vì mỗi đứa trẻ có thể hành xử theo cách riêng của nó để bạn không thể chắc chắn 100% rằng nó sẽ đưa ra kết quả tương tự trên trạng thái đã cho. Như tôi đã nói thường những thứ này nên được sử dụng như các thành phần container


1
Cách tiếp cận này có thể hiệu quả, nhưng bạn có thể bỏ lỡ những thành tích lớn. Sử dụng PureComponenttrong các thành phần và thành phần cấp gốc gần đầu phân cấp của bạn thường là nơi bạn sẽ thấy mức tăng hiệu suất lớn nhất. Tất nhiên, bạn cần phải tránh các đạo cụ đột biến và trạng thái trực tiếp để các thành phần thuần túy hoạt động chính xác, nhưng việc đột biến các đối tượng trực tiếp là một mô hình chống lại trong Dù sao đi nữa.
Matt Browne

5
  • React.Componentlà thành phần "bình thường" mặc định. Bạn khai báo chúng bằng classtừ khóa và extends React.Component. Hãy nghĩ về họ như một lớp học, với các phương pháp vòng đời, xử lý sự kiện và bất kỳ phương thức nào.

  • React.PureComponentlà một React.Componentmà dụng cụ shouldComponentUpdate()với một chức năng mà không một so sánh nông cạn của mình propsstate. Bạn phải sử dụng forceUpdate()nếu bạn biết thành phần này có các đạo cụ hoặc dữ liệu lồng nhau đã thay đổi và bạn muốn kết xuất lại. Vì vậy, chúng không tuyệt vời nếu bạn cần các thành phần để kết xuất lại khi mảng hoặc đối tượng bạn chuyển qua làm đạo cụ hoặc đặt trong trạng thái thay đổi.

  • Các thành phần chức năng là những thành phần không có chức năng vòng đời. Chúng được cho là không quốc tịch, nhưng chúng rất đẹp và sạch sẽ đến nỗi bây giờ chúng ta có móc (kể từ React 16.8) để bạn vẫn có thể có trạng thái. Vì vậy, tôi đoán họ chỉ là "thành phần sạch".

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.