這是高階元件系列的第三部分。在第一個教程中,我們從零開始。我們學習了 ES6 語法、高階函數和高階組件的基礎知識。
高階元件模式對於建立抽像元件非常有用 - 您可以使用它們與現有元件共用資料(狀態和行為)。在本系列的第二部分中,我示範了使用此模式的程式碼的實際範例。這包括受保護的路由、建立可配置的通用容器、將載入指示器附加到元件等。
在本教程中,我們將了解您在編寫 HOC 時應該考慮的一些最佳實踐和注意事項。
React 之前有一個名為 Mixins 的東西,它與 React.createClass
方法配合得很好。 Mixins 允許開發人員在組件之間共用程式碼。然而,它們有一些缺點,這個想法最終被放棄了。 Mixin 沒有升級為支援 ES6 類,Dan Abramov 甚至寫了一篇深入的文章來解釋為什麼 Mixin 被認為是有害的。
高階組件作為 Mixins 的替代品出現,並且它們支援 ES6 類別。此外,HOC 不需要對 React API 做任何事情,並且是與 React 配合良好的通用模式。然而,HOC 也有缺陷。儘管高階組件的缺點在較小的項目中可能並不明顯,但您可以將多個高階組件連結到單一組件,如下所示。
const SomeNewComponent = withRouter(RequireAuth(LoaderDemo(GenericContainer(CustomForm(Form)))))
您不應該讓連結發展到您問自己這樣的問題:「這些道具從哪裡來?」本教學解決了高階組件模式的一些常見問題以及正確解決這些問題的解決方案。
與 HOC 相關的一些常見問題與 HOC 本身關係不大,而與它們的實現有關。
如您所知,HOC 非常適合程式碼抽象化和建立可重複使用程式碼。然而,當你堆疊了多個 HOC 時,如果某些東西看起來不合適或某些 props 沒有顯示,那麼調試起來就會很痛苦,因為 React DevTools 為你提供的有關可能出現問題的線索非常有限。
為了了解 HOC 的缺點,我創建了一個範例演示,其中嵌套了我們在上一教程中創建的一些 HOC。我們有四個高階函數包裝單一 ContactList 元件。如果程式碼沒有意義或您沒有遵循我之前的教程,這裡是其工作原理的簡短摘要。
withRouter
是一個 HOC,是 React-router 套件的一部分。它使您可以存取歷史物件的屬性,然後將它們作為道具傳遞。
#withAuth
尋找 authentication
屬性,如果驗證為 true,則呈現 WrappedComponent
。如果驗證為 false,則會將 '/login
' 推送到歷史物件。
withGenericContainer
除了 WrappedComponent
之外還接受一個物件作為輸入。 GenericContainer
進行 API 呼叫並將結果儲存在狀態中,然後將資料作為 props 傳送到包裝的元件。
withLoader
是一個附加載入指標的 HOC。指示器旋轉,直到所獲得的數據達到狀態。
class BestPracticesDemo extends Component { render() { return( <div className="contactApp"> <ExtendedContactList authenticated = {true} {...this.props} contacts ="this" /> </div> ) } } const ContactList = ({contacts}) => { return( <div> <ul> {contacts.map( (contact) => <li key={contact.email}> <img src={contact.photo} style="max-width:90%" height="100px" alt="探索在 React 中引入高階元件的最佳實踐" /> <div className="contactData"> <h4>{contact.name}</h4> <small>{contact.email}</small> <br/><small> {contact.phone}</small> </div> </li> )} </ul> </div> ) } const reqAPI = {reqUrl: 'https://demo1443058.mockable.io/users/', reqMethod:'GET', resName:'contacts'} const ExtendedContactList = withRouter( withAuth( withGenericContainer(reqAPI)( withLoader('contacts') (ContactList)))); export default BestPracticesDemo;
不要忘記在 HOC 中傳播道具
假設我們在組合層次結構的頂端有一個 authenticated = { this.state.authenticated }
屬性。我們知道這是一個重要的道具,並且應該將其延伸到演示組件。然而,想像一下中間 HOC,例如
,決定忽略它的所有 props。
//render method of withGenericContainer render() { return( <WrappedComponent /> ) }
//The right way render() { return( <WrappedComponent {...this.props} {...this.state} />) }
不要傳遞超出 HOC 範圍的不存在的 props
HOC 可能會引入 WrappedComponent 可能沒有任何用處的新屬性。在這種情況下,傳遞僅與組合元件相關的 props 是一個很好的做法。
高階元件可以透過兩種方式接受資料:作為函數的參數或作為組件的 prop。例如, authenticated = { this.state.authenticated }
是一個 prop 範例,而在
因为 withGenericContainer 是一个函数,所以您可以根据需要传入任意数量的参数。在上面的示例中,配置对象用于指定组件的数据依赖性。然而,增强组件和包装组件之间的契约是严格通过 props 进行的。
因此,我建议通过函数参数填充静态时间数据依赖项,并将动态数据作为 props 传递。经过身份验证的道具是动态的,因为用户可以通过身份验证,也可以不通过身份验证,具体取决于他们是否登录,但我们可以确定 reqAPI
对象的内容不会动态更改。
这是一个您应该不惜一切代价避免的示例。
var OriginalComponent = () => <p>Hello world.</p>; class App extends React.Component { render() { return React.createElement(enhanceComponent(OriginalComponent)); } };
除了性能问题之外,您还将在每次渲染时丢失 OriginalComponent
及其所有子组件的状态。要解决这个问题,请将 HOC 声明移到 render 方法之外,使其仅创建一次,以便渲染始终返回相同的EnhancedComponent。
var OriginalComponent = () => <p>Hello world.</p>; var EnhancedComponent = enhanceComponent(OriginalComponent); class App extends React.Component { render() { return React.createElement(EnhancedComponent); } };
改变 HOC 内的包装组件将导致无法在 HOC 外部使用包装组件。如果您的 HOC 返回 WrappedComponent,您几乎总是可以确定自己做错了。下面的例子演示了突变和组合之间的区别。
function logger(WrappedComponent) { WrappedComponent.prototype.componentWillReceiveProps = function(nextProps) { console.log('Current props: ', this.props); console.log('Next props: ', nextProps); }; // We're returning the WrappedComponent rather than composing //it return WrappedComponent; }
组合是 React 的基本特征之一。您可以在其渲染函数中将一个组件包装在另一个组件内,这就是所谓的组合。
function logger(WrappedComponent) { return class extends Component { componentWillReceiveProps(nextProps) { console.log('Current props: ', this.props); console.log('Next props: ', nextProps); } render() { // Wraps the input component in a container, without mutating it. Good! return <WrappedComponent {...this.props} />; } } }
此外,如果您改变 HOC 内的 WrappedComponent,然后使用另一个 HOC 包装增强组件,则第一个 HOC 所做的更改将被覆盖。为了避免这种情况,您应该坚持组合组件而不是改变它们。
当您有多个堆叠时,命名空间道具名称的重要性是显而易见的。组件可能会将 prop 名称推送到已被另一个高阶组件使用的 WrappedComponent 中。
import React, { Component } from 'react'; const withMouse = (WrappedComponent) => { return class withMouse extends Component { constructor(props) { super(props); this.state = { name: 'Mouse' } } render() { return( <WrappedComponent {...this.props} name={this.state.name} /> ); } } } const withCat = (WrappedComponent) => { return class withCat extends Component { render() { return( <WrappedComponent {...this.props} name= "Cat" /> ) } } } const NameComponent = ({name}) => { return( <div> {name} </div>) } const App =() => { const EnhancedComponent = withMouse(withCat(NameComponent)); return( <div> <EnhancedComponent /> </div>) } export default App;
withMouse
和 withCat
都在尝试推送自己的 name 版本。如果EnhancedComponent也必须共享一些同名的props怎么办?
<EnhancedComponent name="This is important" />
这不会给最终开发人员带来混乱和误导吗? React Devtools 不会报告任何名称冲突,您必须查看 HOC 实现细节才能了解出了什么问题。
这可以通过提供 HOC 属性名称的范围作为约定来解决。因此,您将拥有 withCat_name
和 withMouse_name
而不是通用的 prop 名称。
这里需要注意的另一件有趣的事情是,对属性进行排序在 React 中非常重要。当您多次拥有相同的属性并导致名称冲突时,最后一个声明将始终保留。在上面的例子中,Cat 获胜,因为它被放置在 { ...this.props }
之后。
如果您希望通过其他方式解决名称冲突,您可以重新排序属性并在最后传播 this.props
。这样,您就可以设置适合您的项目的合理默认值。
由 HOC 创建的组件在 React Devtools 中显示为普通组件。很难区分两者。您可以通过为高阶组件提供有意义的 displayName
来简化调试。在 React Devtools 上拥有这样的东西不是明智的吗?
<withMouse(withCat(NameComponent)) > ... </withMouse(withCat(NameComponent))>
那么 displayName
是什么?每个组件都有一个 displayName
属性,可用于调试目的。最流行的技术是包装 WrappedComponent
的显示名称。如果 withCat
是 HOC,并且 NameComponent
是 WrappedComponent
,则 displayName
将是 withCat(NameComponent)
.
const withMouse = (WrappedComponent) => { class withMouse extends Component { /* */ } withMouse.displayName = `withMouse(${getDisplayName(WrappedComponent)})`; return withMouse; } const withCat = (WrappedComponent) => { class withCat extends Component { /* */ } withCat.displayName = `withCat(${getDisplayName(WrappedComponent)})`; return withCat; } function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component'; }
尽管 Mixins 已经消失,但说高阶组件是唯一允许代码共享和抽象的模式是有误导性的。另一种替代模式已经出现,我听说有人说它比 HOC 更好。深入探讨这个概念超出了本教程的范围,但我将向您介绍渲染道具和一些基本示例,以演示它们为何有用。
渲染道具有许多不同的名称:
这是一个简单的示例,应该解释渲染道具的工作原理。
class Mouse extends Component { constructor() { super(); this.state = { name: "Nibbles" } } render() { return( <div> {this.props.children(this.state)} </div> ) } } class App extends Component { render() { return( <Mouse> {(mouse) => <div> The name of the mouse is {mouse.name} </div> } </Mouse> ) } }
如您所见,我们已经摆脱了高阶函数。我们有一个名为 Mouse
的常规组件。我们将渲染 this.props.children()
并将状态作为参数传递,而不是在其 render 方法中渲染包装的组件。所以我们给 Mouse
一个 render prop,而 render prop 决定应该渲染什么。
換句話說,Mouse
元件接受一個函數作為子屬性的值。當 Mouse
渲染時,它會傳回 Mouse
的狀態,而 render prop 函數可以隨意使用它。
我喜歡這種模式的幾點:
高階元件是可用於在 React 中建立健全、可重複使用元件的模式。如果您要使用 HOC,則應遵循一些基本規則。這是為了讓您以後不會後悔使用它們的決定。我在本教程中總結了大部分最佳實踐。
HOC 並不是當今唯一流行的模式。在本教學的最後,我向您介紹了另一種稱為渲染道具的模式,該模式在 React 開發人員中越來越受歡迎。
我不會判斷一種模式並說這個模式比另一個模式更好。隨著 React 的發展以及它周圍的生態系統的成熟,越來越多的模式將會出現。在我看來,您應該學習所有這些,並堅持選擇適合您的風格並且您感到舒適的一個。
這也標誌著高階元件教學系列的結束。我們已經從零開始,掌握了一種稱為 HOC 的先進技術。如果我錯過了任何內容或您有建議/想法,我很樂意聽到。您可以將它們發佈在評論中。
以上是探索在 React 中引入高階元件的最佳實踐的詳細內容。更多資訊請關注PHP中文網其他相關文章!