一、組件
(1) 函數(shù)組件
如果你想寫的組件只包含一個 render 方法,并且不包含 state,那么使用函數(shù)組件就會更簡單。我們不需要定義一個繼承于 React.Component 的類,我們可以定義一個函數(shù),這個函數(shù)接收 props 作為參數(shù),然后返回需要渲染的元素。
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
(2) React.Component
shouldComponentUpdate 僅檢查了 props.color 或 state.count 是否改變。如果這些值沒有改變,那么這個組件不會更新
class CounterButton extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
}
(3) PureComponent
如果你的組件更復(fù)雜一些,你可以使用類似“淺比較”的模式來檢查 props 和 state 中所有的字段,以此來決定是否組件需要更新。React 已經(jīng)提供了一位好幫手來幫你實(shí)現(xiàn)這種常見的模式 - 你只要繼承 React.PureComponent 就行了。
class CounterButton extends React.PureComponent {}
大部分情況下,你可以使用 React.PureComponent 來代替手寫 shouldComponentUpdate。但它只進(jìn)行淺比較 (例如:1 == 1或者ture==true,數(shù)組和對象引用是否相同),所以當(dāng) props 或者 state 某種程度是可變的話,淺比較會有遺漏,那你就不能使用它了。
不要在props和state中改變對象和數(shù)組,如果你在你的父組件中改變對象,你的PureComponent將不會更新。雖然值已經(jīng)被改變,但是子組件比較的是之前props的引用是否相同,所以不會檢測到不同。
因此,你可以通過使用es6的assign方法或者數(shù)組的擴(kuò)展運(yùn)算符或者使用第三方庫,強(qiáng)制返回一個新的對象。
當(dāng)數(shù)據(jù)結(jié)構(gòu)很復(fù)雜時(shí),情況會變得麻煩,存在性能問題。
(比較原始值和對象引用是低耗時(shí)操作。如果你有一列子對象并且其中一個子對象更新,對它們的props和state進(jìn)行檢查要比重新渲染每一個子節(jié)點(diǎn)要快的多。)
(4) 何時(shí)使用Component 或 PureComponent ?
<1> 當(dāng)組件是獨(dú)立的,組件在頁面中的個數(shù)為1或2的,組件有很多props、state,并且當(dāng)中還有些是數(shù)組和對象的,組件需要每次都渲染的,使用Component
<2> 當(dāng)組件經(jīng)常作為子組件,作為列表,組件在頁面中數(shù)量眾多,組件props, state屬性少,并且屬性中基本沒有數(shù)組和對象,組件不需要每次都渲染,只有變化了才渲染,使用PureComponent
憑主觀,我覺得
以下組件適合Component
Button
Input
以下組件適合PureComponent
Radio
Checkbox
Option
二、高階函數(shù)
HOC ( 高階組件higherOrderComponent ) 自身不是 React API 的一部分,它是一種基于 React 的組合特性而形成的設(shè)計(jì)模式。
組件是將 props 轉(zhuǎn)換為 UI,而高階組件是將組件轉(zhuǎn)換為另一個組件。(組件是 React 中代碼復(fù)用的基本單元。)
高階組件例如 Redux 的 connect 和 Relay 的 createFragmentContainer。
(1)HOC 不會修改傳入的組件,也不會使用繼承來復(fù)制其行為。
相反,HOC 通過將組件包裝在容器組件中來組成新組件。HOC 是純函數(shù),沒有副作用。
(2)HOC 應(yīng)該透傳與自身無關(guān)的 props
HOC 為組件添加特性。自身不應(yīng)該大幅改變約定。
HOC 應(yīng)該透傳與自身無關(guān)的 props,HOC 返回的組件與原組件應(yīng)保持類似的接口。
(3)約定:包裝顯示名稱以便輕松調(diào)試HOC
創(chuàng)建的容器組件會與任何其他組件一樣,會顯示在 React Developer Tools 中。為了方便調(diào)試,請選擇一個顯示名稱,以表明它是 HOC 的產(chǎn)物。
最常見的方式是用 HOC 包住被包裝組件的顯示名稱。比如高階組件名為 withSubscription,并且被包裝組件的顯示名稱為 CommentList,顯示名稱應(yīng)該為 WithSubscription(CommentList):
function withSubscription(WrappedComponent) {
class WithSubscription extends React.Component {/* ... */}
WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
return WithSubscription;
}
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
(4) 注意事項(xiàng):
<1> 不要在 render 方法中使用 HOC
render() {
// 每次調(diào)用 render 函數(shù)都會創(chuàng)建一個新的 EnhancedComponent
// EnhancedComponent1 !== EnhancedComponent2
const EnhancedComponent = enhance(MyComponent);
// 這將導(dǎo)致子樹每次渲染都會進(jìn)行卸載,和重新掛載的操作!
return <EnhancedComponent />;
}
<2>務(wù)必復(fù)制靜態(tài)方法
有時(shí)在 React 組件上定義靜態(tài)方法很有用。例如,Relay 容器暴露了一個靜態(tài)方法 getFragment 以方便組合 GraphQL 片段。
但是,當(dāng)你將 HOC 應(yīng)用于組件時(shí),原始組件將使用容器組件進(jìn)行包裝。這意味著新組件沒有原始組件的任何靜態(tài)方法。
// 定義靜態(tài)函數(shù)
WrappedComponent.staticMethod = function() {/*...*/}
// 現(xiàn)在使用 HOC
const EnhancedComponent = enhance(WrappedComponent);
// 增強(qiáng)組件沒有 staticMethod
typeof EnhancedComponent.staticMethod === 'undefined' // true
為了解決這個問題,你可以在返回之前把這些方法拷貝到容器組件上:
你可以使用 hoist-non-react-statics 自動拷貝所有非 React 靜態(tài)方法:
import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
hoistNonReactStatic(Enhance, WrappedComponent);
return Enhance;
}
除了導(dǎo)出組件,另一個可行的方案是再額外導(dǎo)出這個靜態(tài)方法。
// 使用這種方式代替...
MyComponent.someFunction = someFunction;
export default MyComponent;
// ...單獨(dú)導(dǎo)出該方法...
export { someFunction };
// ...并在要使用的組件中,import 它們
import MyComponent, { someFunction } from './MyComponent.js';
<3> Refs 不會被傳遞
雖然高階組件的約定是將所有 props 傳遞給被包裝組件,但這對于 refs 并不適用。那是因?yàn)?ref 實(shí)際上并不是一個 prop - 就像 key 一樣,它是由 React 專門處理的。如果將 ref 添加到 HOC 的返回組件中,則 ref 引用指向容器組件,而不是被包裝組件。
這個問題的解決方案是通過使用 React.forwardRef API(React 16.3 中引入)
三、React Redux 的 connect
React Redux 的 connect 函數(shù)是一個 返回高階組件的高階函數(shù)!
最常見的 HOC 簽名如下:
// React Redux 的 `connect` 函數(shù)
const ConnectedComment = connect(commentSelector, commentActions)(CommentList);
剛剛發(fā)生了什么?!如果你把它分開,就會更容易看出發(fā)生了什么。
// connect 是一個函數(shù),它的返回值為另外一個函數(shù)。
const enhance = connect(commentListSelector, commentListActions);
// 返回值為 HOC,它會返回已經(jīng)連接 Redux store 的組件
const ConnectedComment = enhance(CommentList);
這種形式可能看起來令人困惑或不必要,但它有一個有用的屬性。最大化可組合性。
像 connect 函數(shù)返回的單參數(shù) HOC 具有簽名 Component => Component。 輸出類型與輸入類型相同的函數(shù)很容易組合在一起。
// 而不是這樣...
const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent))
// ... 你可以編寫組合工具函數(shù)
// compose(f, g, h) 等同于 (...args) => f(g(h(...args)))
const enhance = compose(
// 這些都是單參數(shù)的 HOC
withRouter,
connect(commentSelector)
)
const EnhancedComponent = enhance(WrappedComponent)
//(同樣的屬性也允許 connect 和其他 HOC 承擔(dān)裝飾器的角色)
四、其他
(1)key
每當(dāng)一個列表重新渲染時(shí),React 會根據(jù)每一項(xiàng)列表元素的 key 來檢索上一次渲染時(shí)與每個 key 所匹配的列表項(xiàng)。如果 React 發(fā)現(xiàn)當(dāng)前的列表有一個之前不存在的 key,那么就會創(chuàng)建出一個新的組件。如果 React 發(fā)現(xiàn)和之前對比少了一個 key,那么就會銷毀之前對應(yīng)的組件。如果一個組件的 key 發(fā)生了變化,這個組件會被銷毀,然后使用新的 state 重新創(chuàng)建一份。
我們強(qiáng)烈推薦,每次只要你構(gòu)建動態(tài)列表的時(shí)候,都要指定一個合適的 key。
如果你沒有指定任何 key,React 會發(fā)出警告,并且會把數(shù)組的索引當(dāng)作默認(rèn)的 key。但是如果想要對列表進(jìn)行重新排序、新增、刪除操作時(shí),把數(shù)組索引作為 key 是有問題的。
顯式地使用 key={i} 來指定 key 確實(shí)會消除警告,但是仍然和數(shù)組索引存在同樣的問題,所以大多數(shù)情況下最好不要這么做。
組件的 key 值并不需要在全局都保證唯一,只需要在當(dāng)前的同一級元素之前保證唯一即可。