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應用整潔,結構合理,易于維護。純函數是如何保持代碼整潔并且如何將這些原則應用到react組件中。
Pure Functions
純函數
A function is considered pure if it adheres to the following properties:
如果一個函數遵循以下幾個規則那么他就是純函數:
· All the data it deals with are declared as arguments
· 函數處理的數據都是作為參數傳遞給函數。
· It does not mutate data it was given or any other data (these are often referred to as side effects).
· 他不接受不確定的數據或者任何其他數據(他們通常是副作用的)。
· Given the same input, it will always return the same output.
· 同樣的輸入,函數將得到同樣的輸出。
For example, the add function below is pure:
舉個例子,下面的add函數就是個純函數:
function add(x, y) {
? ? ? return x + y;
}
However, the function badAdd below is impure:
下面badadd就是不是純函數:
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:
這個函數之所以不是純函數是因為他所引用的數據不是直接給予的(不是通過參數傳遞,而是直接引用了外部全局變量。)。所以當你輸入相同的數據時候可能得到不一樣的輸出。
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.
要了解更多關于純函數的內容,你可以閱讀一下Mark Brown寫的“An introduction to reasonably pure programming”。
Higher Order Functions
高階函數
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.
高階函數的定義是,當一個函數被調用他將輸出另一個函數。有時他們把函數作為參數,但是這個不是高階函數的必要條件。
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 函數,我們希望他在返回結果前,console一下結果。我們不能改變 add 函數,所以我們要創建一個新函數來替代他:
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:
我們顯示函數的記錄結果是有用的,現在我們要對減法函數做同樣的事情。而不是復制上述內容,我們可以編寫一個更高階的函數,它可以執行一個函數接受函數作為參數,被調用的函數會返回結果和顯示記錄,函數則會返回新的函數。
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:
現在我們可以使用這個函數為 add 和 subtract 函數添加顯示記錄:
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 就是一個高階函數因為他接受一個函數作為參數,并且返回一個函數被我們調用。封裝且不改變現有函數的內部實現是非常有用的。希望了解更多相關內容可以去看看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.
現在我們討論討論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.
在這節我們將使用React Router,這個是React的路由解決方案。如果你開始使用庫,我強烈建議你使用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應用里的頁面。<Link> 組件其中一個屬性是activeClassName。當 <Link> 組件處于活動狀態時使用了這個屬性(用戶經過鏈接時候的意思),組件將使用這個樣類名,以便開發人員能夠對其樣式進行美化。
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:
這是一個非常有用的功能,在我們假設的應用程序中,我們決定我們總是使用這個屬性。然而,很快我們會發現這會使 <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.
注意我們每次都必須重復使用那個類的屬性名。這不僅使我們的組件臃腫,這也意味著如果我們決定改變類名,我們必須在很多地方進行修改。
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:
現在我們可以使用這個組件來整合我們的鏈接:
<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生態系統中,這些組件被稱為高階組件,因為他們采用現有組件并對他們進行封裝,但是并沒有更改現有組件。你也可以將他們視為封裝組件,但你會發現他們通常被稱為React-based內容中的高階組件。
Functional, Stateless Components
函數式無狀態組件
React 0.14 introduced support for functional, stateless components. These are components that have the following characteristics:
React0.14開始提供函數式無狀態組件,函數式無狀態組件具有以下特點:
They do not have any state.
他們沒有state
They do not use any React lifecycle methods (such as componentWillMount()).
他們無法使用React生命周期函數(比如 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:
當一個組件遵守上述內容時,我們可以將其定義為一個函數,而不是使用React.createClass(如果你使用了 ES2015? classes 你也可以使用class 來擴展你的React 組件)。下面的兩個表達式產生相同的組件:
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.
在函數式無狀態組件中,不是引用this.props,而是將props用作參數。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.
因為更高階的組件通常會封裝一個現有的組件,所以您經常會發現可以將它們定義為函數組件。在本文的其余部分,我將盡可能地這樣做。
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.
我們不只局限于組件可以正常運行,我們希望做的更好。我們創建的 AppLink 組件就不太適合。
Accepting multiple properties
接受多個參數
The <AppLink>?component expects two properties:
<AppLink>?組件估計會有兩個參數:
-- 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.
當然,<Link> 組件可以接受更多發屬性,有時我們只是想組件允許傳遞額外的屬性以及上述的兩個屬性。我們并沒有通過代碼屬性讓 <AppLink> 有很高的擴展性。
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組件,支持擴展操作符將對象作為屬性傳遞給組件。例如,下面的代碼示例實現了同樣的事情:
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}擴展對象中的每個鍵,并將其作為單個屬性傳遞給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> 在將來添加任何新的屬性,我們的包裝器組件將已經支持它們。在我們這樣做的時候,我同時要將 AppLink 改成函數式組件。
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.
現在將接受任何屬性并傳遞它們。請注意,我們還可以使用筆和標簽,而不是在標簽之間傳遞{props.children}。React允許 children 作為常規 prop 或作為在開始和結束標簽之間的組件的子元素進行傳遞。?
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:
當您在React組件中多次具有相同的屬性時,最后一個會被使用。這就意味著放在 {...this.props} 后面的 activeClassName=“active-link” 會被使用。我們可以把 this.props 放在最后來解決這個問題。這意味著如果用戶真的需要我們可以設置一些想要合理的默認值,但是這些默認值是可以被覆蓋:
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.
通過創建一個逛街組件可以使現有的組件得到額外的功能,我們盡量保持我們的代碼簡潔,并且通過不重復的屬性來應對未來的變化,同時把他們的值保存在一個地方。
Higher Order Component Creators
高階組件創造者
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 函數和 subtract 函數添加顯示輸出一樣。
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.
讓我們想象一下,在你的應用程序中,你有一個對象,其中包含關于系統進行身份驗證的當前用戶的信息。你需要你的 React 組件可以訪問這些信息,而不是盲目地使每個組件都可以訪問,你更希望嚴格地控制哪些組件接收信息。
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.
解決的方法就是我們可以創建一個函數去調用一個 React 組件。函數會返回一個可以訪問用戶信息并且把原來組件進行渲染的新 React 組件。
That sounds pretty complicated, but it’s made more straightforward with some code:
聽起來很復雜,但是通過一些代碼就可以很簡潔了:
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 組件
? ? // 使用一個無狀態函數式組件
? ? return function(props){
? ? ? ? // pass in the user variable as a property, along with
? ? ? ? // all the other props that we might be given
? ? ? ? // 用戶信息作為屬性進行傳遞
? ? ? ? // 其他屬性我們可以自定義
? ? ? ? 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.
函數通過一個React組件(自定義的React組件必須有大寫字母開頭)并返回一個新的函數,它將使用設置在 secretUserInfo 里的用戶的屬性來渲染那個組件。
Now let’s take a component,?<AppHeader>, which wants access to this information so it can display the logged in user:
現在我們有一個 <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?函數創建新的組件,以便把 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.
現在我們有了 <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 因為他傳遞了一些不希望所有組件都可以使用的額外數據。
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 中,知道你依賴的第三方庫是如何工作的,它被使用的原因將幫助你隨著你的應用程序一起成長。
Conclusion
結論
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.
這篇文章即將結束,通過應用諸如純函數和更高階函數的函數式編程的原理應用到React,您可以創建一個更容易維護和使用的代碼庫。
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.
通過創建更高階的組件,你只能將數據定義在一個位置,使重構更容易。更高階的函數創建者使你能夠將大部分數據保留為私有,并且僅將數據片段暴露給真正需要的組件。通過這樣做,你可以清楚地知道哪些組件正在使用哪些數據,并且隨著應用程序的發展,你會發現這是有益的。