Higher Order Components: A React Application Design Pattern 高階組件:react應(yīng)用設(shè)計模式

In this article we will discuss how to use Higher Order Components to keep your React applications tidy, well structured and easy to maintain. We’ll discuss how pure functions keep code clean and how these same principles can be applied to React components.
在這片文章里我們將討論任何使用高階組件保持你的react應(yīng)用整潔,結(jié)構(gòu)合理,易于維護(hù)。純函數(shù)是如何保持代碼整潔并且如何將這些原則應(yīng)用到react組件中。

Pure Functions
純函數(shù)

A function is considered pure if it adheres to the following properties:
如果一個函數(shù)遵循以下幾個規(guī)則那么他就是純函數(shù):

· All the data it deals with are declared as arguments
· 函數(shù)處理的數(shù)據(jù)都是作為參數(shù)傳遞給函數(shù)。

· It does not mutate data it was given or any other data (these are often referred to as side effects).
· 他不接受不確定的數(shù)據(jù)或者任何其他數(shù)據(jù)(他們通常是副作用的)。

· Given the same input, it will always return the same output.
· 同樣的輸入,函數(shù)將得到同樣的輸出。

For example, the add function below is pure:
舉個例子,下面的add函數(shù)就是個純函數(shù):

function add(x, y) {
? ? ? return x + y;
}

However, the function badAdd below is impure:
下面badadd就是不是純函數(shù):

var y = 2;
function badAdd(x) {
? ? return x + y;
}?

This function is not pure because it references data that it hasn’t directly been given. As a result, it’s possible to call this function with the same input and get different output:
這個函數(shù)之所以不是純函數(shù)是因?yàn)樗玫臄?shù)據(jù)不是直接給予的(不是通過參數(shù)傳遞,而是直接引用了外部全局變量。)。所以當(dāng)你輸入相同的數(shù)據(jù)時候可能得到不一樣的輸出。

var y = 2;
badAdd(3) // 5
y = 3;
badAdd(3) // 6

To read more about pure functions you can read “An introduction to reasonably pure programming” by Mark Brown.
要了解更多關(guān)于純函數(shù)的內(nèi)容,你可以閱讀一下Mark Brown寫的“An introduction to reasonably pure programming”。

Higher Order Functions
高階函數(shù)

A higher order function is a function that when called, returns another function. Often they also take a function as an argument, but this is not required for a function to be considered higher order.
高階函數(shù)的定義是,當(dāng)一個函數(shù)被調(diào)用他將輸出另一個函數(shù)。有時他們把函數(shù)作為參數(shù),但是這個不是高階函數(shù)的必要條件。

Let’s say we have our add function from above, and we want to write some code so that when we call it we log the result to the console before returning the result. We’re unable to edit the add function, so instead we can create a new function:
讓我們再來看看前面說過的 add 函數(shù),我們希望他在返回結(jié)果前,console一下結(jié)果。我們不能改變 add 函數(shù),所以我們要創(chuàng)建一個新函數(shù)來替代他:

function addAndLog(x, y) {
? ? var result = add(x, y);
? ? console.log('Result', result);
? ? return result;
}

We decide that logging results of functions is useful, and now we want to do the same with a subtract function. Rather than duplicate the above, we could write a higher order function that can take a function and return a new function that calls the given function and logs the result before then returning it:
我們顯示函數(shù)的記錄結(jié)果是有用的,現(xiàn)在我們要對減法函數(shù)做同樣的事情。而不是復(fù)制上述內(nèi)容,我們可以編寫一個更高階的函數(shù),它可以執(zhí)行一個函數(shù)接受函數(shù)作為參數(shù),被調(diào)用的函數(shù)會返回結(jié)果和顯示記錄,函數(shù)則會返回新的函數(shù)。

function logAndReturn(func) {
? ? return function() {
? ? ? ? var args = Array.prototype.slice.call(arguments);
? ? ? ? var result = func.apply(null, args);
? ? ? ? console.log('Result', result);
? ? ? ? return result;
? ? }
}

Now we can take this function and use it to add logging to add and subtract:
現(xiàn)在我們可以使用這個函數(shù)為 add subtract 函數(shù)添加顯示記錄:

var addAndLog = logAndReturn(add);
addAndLog(4, 4) // 8 is returned, ‘Result 8’ is logged
var subtractAndLog = logAndReturn(subtract);
subtractAndLog(4, 3) // 1 is returned, ‘Result 1’ is logged;

logAndReturn is a HOF because it takes a function as its argument and returns a new function that we can call. These are really useful for wrapping existing functions that you can’t change in behavior. For more information on this, check M. David Green’s article “Higher-Order Functions in JavaScript which goes into much more detail on the subject.
logAndReturn 就是一個高階函數(shù)因?yàn)樗邮芤粋€函數(shù)作為參數(shù),并且返回一個函數(shù)被我們調(diào)用。封裝且不改變現(xiàn)有函數(shù)的內(nèi)部實(shí)現(xiàn)是非常有用的。希望了解更多相關(guān)內(nèi)容可以去看看M. David Green寫的“Higher-Order Functions in JavaScript”。

Additionally you can check out this CodePen, which shows the above code in action.
另外你可以去CodePen,那里有上面說到的代碼。

Higher Order Components
高階組件

Moving into React land, we can use the same logic as above to take existing React components and give them some extra behaviours.
現(xiàn)在我們討論討論React,上面講到的邏輯我們同樣可以在React組件上使用,以賦予他們更多的行為。

In this section we’re going to use React Router, the de facto routing solution for React. If you’d like to get started with the library I highly recommend the React Router Tutorial on GitHub.
在這節(jié)我們將使用React Router,這個是React的路由解決方案。如果你開始使用庫,我強(qiáng)烈建議你使用GitHub上的React Router Tutorial。

React Router’s Link component React
路由鏈接組件

React Router provides a <Link> component that is used to link between pages in a React application. One of the properties that this <Link> component takes is activeClassName. When a <Link> has this property and it is currently active (the user is on a URL that the link points to), the component will be given this class, enabling the developer to style it.
React 路由提供 <Link> 用于鏈接React應(yīng)用里的頁面。<Link> 組件其中一個屬性是activeClassName。當(dāng) <Link> 組件處于活動狀態(tài)時使用了這個屬性(用戶經(jīng)過鏈接時候的意思),組件將使用這個樣類名,以便開發(fā)人員能夠?qū)ζ錁邮竭M(jìn)行美化。

This is a really useful feature, and in our hypothetical application we decide that we always want to use this property. However, after doing so we quickly discover that this is making all our <Link> components very verbose:
這是一個非常有用的功能,在我們假設(shè)的應(yīng)用程序中,我們決定我們總是使用這個屬性。然而,很快我們會發(fā)現(xiàn)這會使 <Link> 組件變得臃腫:

<Link to="/" activeClassName="active-link"> Home </Link>
<Link to="/about" activeClassName="active-link"> About</Link>
<Link to="/contact" activeClassName="active-link"> Contact</Link>

Notice that we are having to repeat the class name property every time. Not only does this make our components verbose, it also means that if we decide to change the class name we’ve got to do it in a lot of places.
注意我們每次都必須重復(fù)使用那個類的屬性名。這不僅使我們的組件臃腫,這也意味著如果我們決定改變類名,我們必須在很多地方進(jìn)行修改。

Instead, we can write a component that wraps the <Link> component:
替換,我們要寫一個組件去封裝 <Link> 組件:

var AppLink=React.createClass({
? ? render:function(){
? ? ? ? return(
? ? ? ? ? ? <Link to={this.props.to} activeClassName="active-link">
? ? ? ? ? ? ? ? {this.props.children}
? ? ? ? ? ? </Link>;
? ? ? ? );
? ? }
});

And now we can use this component, which tidies up our links:
現(xiàn)在我們可以使用這個組件來整合我們的鏈接:

<AppLink to="/">Home</AppLink>
<AppLink to="/about">About</AppLink>
<AppLink to="/contact">Contact</AppLink>

In the React ecosystem these components are known as higher order components because they take an existing component and manipulate it slightly without changing the existing component. You can also think of these as wrapper components, but you’ll find them commonly referred to as higher order components in React-based content.
在React生態(tài)系統(tǒng)中,這些組件被稱為高階組件,因?yàn)樗麄儾捎矛F(xiàn)有組件并對他們進(jìn)行封裝,但是并沒有更改現(xiàn)有組件。你也可以將他們視為封裝組件,但你會發(fā)現(xiàn)他們通常被稱為React-based內(nèi)容中的高階組件。

Functional, Stateless Components
函數(shù)式無狀態(tài)組件

React 0.14 introduced support for functional, stateless components. These are components that have the following characteristics:
React0.14開始提供函數(shù)式無狀態(tài)組件,函數(shù)式無狀態(tài)組件具有以下特點(diǎn):

They do not have any state.
他們沒有state

They do not use any React lifecycle methods (such as componentWillMount()).
他們無法使用React生命周期函數(shù)(比如 componentWillMount()

They only define the render method and nothing more.
他們只定義了 render 方法,沒有其他的了。

When a component adheres to the above, we can define it as a function, rather than using React.createClass (or class App extends React.Component if you’re using ES2015 classes). For example, the two expressions below both produce the same component:
當(dāng)一個組件遵守上述內(nèi)容時,我們可以將其定義為一個函數(shù),而不是使用React.createClass(如果你使用了 ES2015? classes 你也可以使用class 來擴(kuò)展你的React 組件)。下面的兩個表達(dá)式產(chǎn)生相同的組件:

var App = React.createClass({
? ? render: function() {
? ? ? ? return <p>My name is { this.props.name }</p>;
? ? }
});
var App = function(props) {
? ? return <p>My name is { props.name }</p>;
}

In the functional, stateless component instead of referring to this.props we’re instead passed props as an argument. You can read more about this on the React documentation.
在函數(shù)式無狀態(tài)組件中,不是引用this.props,而是將props用作參數(shù)。React 文檔有更多介紹。

Because higher order components often wrap an existing component you’ll often find you can define them as a functional component. For the rest of this article I’ll do that whenever possible.
因?yàn)楦唠A的組件通常會封裝一個現(xiàn)有的組件,所以您經(jīng)常會發(fā)現(xiàn)可以將它們定義為函數(shù)組件。在本文的其余部分,我將盡可能地這樣做。

Better Higher Order Components
更好的高階組件

The above component works, but we can do much better. The AppLink component that we created isn’t quite fit for purpose.
我們不只局限于組件可以正常運(yùn)行,我們希望做的更好。我們創(chuàng)建的 AppLink 組件就不太適合。

Accepting multiple properties
接受多個參數(shù)

The <AppLink>?component expects two properties:
<AppLink>?組件估計會有兩個參數(shù):
-- this.props.to which is the URL the link should take the user to
-- this.props.to 表示鏈接讓用戶訪問的URL
-- this.props.children which is the text shown to the user
-- this.props.children 表示顯示的文本

However, the <Link> component accepts many more properties, and there might be a time when you want to pass extra properties along with the two above which we nearly always want to pass. We haven’t made <AppLink>?very extensible by hard coding the exact properties we need.
當(dāng)然,<Link> 組件可以接受更多發(fā)屬性,有時我們只是想組件允許傳遞額外的屬性以及上述的兩個屬性。我們并沒有通過代碼屬性讓 <AppLink> 有很高的擴(kuò)展性。

The JSX spread
JSX 傳遞

JSX, the HTML-like syntax we use to define React elements, supports the spread operator for passing an object to a component as properties. For example, the code samples below achieve the same thing:
JSX,一種類似于HTML的語法用于定義React組件,支持?jǐn)U展操作符將對象作為屬性傳遞給組件。例如,下面的代碼示例實(shí)現(xiàn)了同樣的事情:

var props={a:1,b:2};
<Foo a={props.a }b={props.b} />

<Foo {...props} />

Using {...props} spreads each key in the object and passes it to Foo as an individual property
使用{... props}擴(kuò)展對象中的每個鍵,并將其作為單個屬性傳遞給Foo。

We can make use of this trick with <AppLink>?so we support any arbitrary property that <Link> supports. By doing this we also future proof ourselves; if <Link> adds any new properties in the future our wrapper component will already support them. While we’re at it, I’m also going to change? AppLink to be a functional component.
我們可以利用 <AppLink> 這個技巧,讓我們支持 <Link> 支持的任意屬性。通這樣做,我們也將來證明自己;如果 <Link> 在將來添加任何新的屬性,我們的包裝器組件將已經(jīng)支持它們。在我們這樣做的時候,我同時要將 AppLink 改成函數(shù)式組件。

var AppLink = function(props){
? ? return <Link {...props} activeClassName="active-link" />;
}

Now <Link>?will accept any properties and pass them through. Note that we also can use the self closing form instead of explicitly referencing {props.children} in-between the <Link> tags. React allows children to be passed as a regular prop or as child elements of a component between the opening and closing tag.
現(xiàn)在將接受任何屬性并傳遞它們。請注意,我們還可以使用筆和標(biāo)簽,而不是在標(biāo)簽之間傳遞{props.children}。React允許 children 作為常規(guī) prop 或作為在開始和結(jié)束標(biāo)簽之間的組件的子元素進(jìn)行傳遞。?

Property ordering in React
React中屬性的排序

Imagine that for one specific link on your page, you have to use a different activeClassName. You try passing it into <AppLink>, since we pass all properties through:
想象一下,對于頁面上的一個特定鏈接,你必須使用不同的 activeClassName。嘗試將其傳遞到 <AppLink>?中,我們通過以下方式傳遞所有屬性:

<AppLink to=“/special-link” activeClassName=“special-active” >
? ? Special Secret Link
</AppLink>

However, this doesn’t work. The reason is because of the ordering of properties when we render the <Link> component:
但是,這不行。渲染 <Link> 組件時屬性的順序:

return < Link {...props} activeClassName="active-link" />;

When you have the same property multiple times in a React component, the last declaration wins. This means that our last activeClassName=“active-link” declaration will always win, since it’s placed after {...this.props}. To fix this we can reorder the properties so we spread this.props last. This means that we set sensible defaults that we’d like to use, but the user can override them if they really need to:
當(dāng)您在React組件中多次具有相同的屬性時,最后一個會被使用。這就意味著放在 {...this.props} 后面的 activeClassName=“active-link” 會被使用。我們可以把 this.props 放在最后來解決這個問題。這意味著如果用戶真的需要我們可以設(shè)置一些想要合理的默認(rèn)值,但是這些默認(rèn)值是可以被覆蓋:

return <Link activeClassName="active-link" {...props} />;

By creating higher order components that wrap existing ones but with additional behavior, we keep our code base clean and defend against future changes by not repeating properties and keeping their values in just one place.
通過創(chuàng)建一個逛街組件可以使現(xiàn)有的組件得到額外的功能,我們盡量保持我們的代碼簡潔,并且通過不重復(fù)的屬性來應(yīng)對未來的變化,同時把他們的值保存在一個地方。

Higher Order Component Creators
高階組件創(chuàng)造者

Often you’ll have a number of components that you’ll need to wrap in the same behavior. This is very similar to earlier in this article when we wrapped add and subtract to add logging to them.
通常你需要給很多組件添加同樣的方法。這就和我們前面說到的給?add 函數(shù)和 subtract 函數(shù)添加顯示輸出一樣。

Let’s imagine in your application you have an object that contains information on the current user who is authenticated on the system. You need some of your React components to be able to access this information, but rather than blindly making it accessible for every component you want to be more strict about which components receive the information.
讓我們想象一下,在你的應(yīng)用程序中,你有一個對象,其中包含關(guān)于系統(tǒng)進(jìn)行身份驗(yàn)證的當(dāng)前用戶的信息。你需要你的 React 組件可以訪問這些信息,而不是盲目地使每個組件都可以訪問,你更希望嚴(yán)格地控制哪些組件接收信息。

The way to solve this is to create a function that we can call with a React component. The function will then return a new React component that will render the given component but with an extra property which will give it access to the user information.
解決的方法就是我們可以創(chuàng)建一個函數(shù)去調(diào)用一個 React 組件。函數(shù)會返回一個可以訪問用戶信息并且把原來組件進(jìn)行渲染的新 React 組件。

That sounds pretty complicated, but it’s made more straightforward with some code:
聽起來很復(fù)雜,但是通過一些代碼就可以很簡潔了:

function wrapWithUser(Component){
? ? // information that we don’t want everything to access
? ? // 收限制的用戶信息
? ? var secretUserInfo = {
? ? ? ? name:'Jack Franklin',
? ? ? ? favouriteColour:'blue'
? ? };

? ? // return a newly generated React component
? ? // using a functional, stateless component
? ? // 返回一個新的 React 組件
? ? // 使用一個無狀態(tài)函數(shù)式組件
? ? return function(props){
? ? ? ? // pass in the user variable as a property, along with
? ? ? ? // all the other props that we might be given
? ? ? ? // 用戶信息作為屬性進(jìn)行傳遞
? ? ? ? // 其他屬性我們可以自定義
? ? ? ? return <Component user={secretUserInfo} {...props} />
? ? }
}

The function takes a React component (which is easy to spot given React components have to have capital letters at the beginning) and returns a new function that will render the component it was given with an extra property of user, which is set to the secretUserInfo.
函數(shù)通過一個React組件(自定義的React組件必須有大寫字母開頭)并返回一個新的函數(shù),它將使用設(shè)置在 secretUserInfo 里的用戶的屬性來渲染那個組件。

Now let’s take a component,?<AppHeader>, which wants access to this information so it can display the logged in user:
現(xiàn)在我們有一個 <AppHeader> 組件需要這些信息,以便把登陸的用戶信息展示出來:

var AppHeader=function(props){
? ? if(props.user){
? ? ? ? return <p>Logged in as {props.user.name} </p>;
? ? } else {
? ? ? ? return <p>You need to login</p>;
? ? }
}

The final step is to connect this component up so it is given this.props.user. We can create a new component by passing this one into our wrapWithUser function.
最后一步是將此組件連接起來,我們可以通過?wrapWithUser?函數(shù)創(chuàng)建新的組件,以便把 this.props.user 傳遞給他。

var ConnectedAppHeader = wrapWithUser(AppHeader);

We now have a <ConnectedAppHeader> component that can be rendered, and will have access to the user object.
現(xiàn)在我們有了 <ConnectedAppHeader> 組件可以訪問 user 對象,并且渲染原來的組件。

I chose to call the component ConnectedAppHeader because I think of it as being connected with some extra piece of data that not every component is given access to.
我給這個組件起名叫 ConnectedAppHeader 因?yàn)樗麄鬟f了一些不希望所有組件都可以使用的額外數(shù)據(jù)。

This pattern is very common in React libraries, particularly in Redux, so being aware of how it works and the reasons it’s being used will help you as your application grows and you rely on other third party libraries that use this approach.
這種模式在 React 庫中非常常見,特別是在 Redux 中,知道你依賴的第三方庫是如何工作的,它被使用的原因?qū)椭汶S著你的應(yīng)用程序一起成長。

Conclusion
結(jié)論

This article has shown how, by applying principles of functional programming such as pure functions and higher order components to React, you can create a codebase that’s easier to maintain and work with on a daily basis.
這篇文章即將結(jié)束,通過應(yīng)用諸如純函數(shù)和更高階函數(shù)的函數(shù)式編程的原理應(yīng)用到React,您可以創(chuàng)建一個更容易維護(hù)和使用的代碼庫。

By creating higher order components you’re able to keep data defined in only one place, making refactoring easier. Higher order function creators enable you to keep most data private and only expose pieces of data to the components that really need it. By doing this you make it obvious which components are using which bits of data, and as your application grows you’ll find this beneficial.
通過創(chuàng)建更高階的組件,你只能將數(shù)據(jù)定義在一個位置,使重構(gòu)更容易。更高階的函數(shù)創(chuàng)建者使你能夠?qū)⒋蟛糠謹(jǐn)?shù)據(jù)保留為私有,并且僅將數(shù)據(jù)片段暴露給真正需要的組件。通過這樣做,你可以清楚地知道哪些組件正在使用哪些數(shù)據(jù),并且隨著應(yīng)用程序的發(fā)展,你會發(fā)現(xiàn)這是有益的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 剛到武漢的時候,老總帶我們?nèi)コ詿?。服?wù)員端上來七八套一次性餐具,讓我小小驚訝了下。貌似,我們來得地方挺正式的呀,...
    江烈閱讀 317評論 0 1
  • 掌握 UIImageView幀動畫的使用 UIImage的2種加載方式 重復(fù)代碼的封裝抽取 文檔注釋的寫法 UII...
    JonesCxy閱讀 1,668評論 0 0
  • 「需求」是我們幾乎天天打交道的重要概念,但需求到底是什么?似乎很少有人提起。我們常說:“用戶提了一個需求?!币渤3?..
    萬能的船長閱讀 2,088評論 0 5
  • 任何時候,在對的地方做對的事情才能保證把事情效益達(dá)到最大化。正如拍攝企業(yè)宣傳片制作的時候,就必須要選擇能與宣傳片文...
    anerjie閱讀 266評論 0 0

友情鏈接更多精彩內(nèi)容