摘要:對傳給的進行操作。之所以被稱為是因為被繼承了,而不是繼承了。在這種方式中,它們的關系看上去被反轉了。在原則,這叫單一職責原則。組合的方式是可以保證組件具有充分的復用性,靈活度,遵守原則的其中一種實踐。
前言
最近在學習React的封裝,雖然日常的開發中也有用到HOC或者Render Props,但從繼承到組合,靜態構建到動態渲染,都是似懂非懂,索性花時間系統性的整理,如有錯誤,請輕噴~~
例子以下是React官方的一個例子,我會采用不同的封裝方法來嘗試代碼復用,例子地址。
組件在 React 是主要的代碼復用單元,但如何共享狀態或一個組件的行為封裝到其他需要相同狀態的組件中并不是很明了。
例如,下面的組件在 web 應用追蹤鼠標位置:
class MouseTracker extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; } handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); } render() { return (); } }Move the mouse around!
The current mouse position is ({this.state.x}, {this.state.y})
隨著鼠標在屏幕上移動,在一個
的組件上顯示它的 (x, y) 坐標。
現在的問題是:我們如何在另一個組件中重用行為?換句話說,若另一組件需要知道鼠標位置,我們能否封裝這一行為以讓能夠容易在組件間共享?
由于組件是 React 中最基礎的代碼重用單元,現在嘗試重構一部分代碼能夠在
// Thecomponent encapsulates the behavior we need... class Mouse extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; } handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( {/* ...but how do we render something other than a); } } class MouseTracker extends React.Component { render() { return (? */}
The current mouse position is ({this.state.x}, {this.state.y})
); } }Move the mouse around!
現在
例如,假設我們現在有一個在屏幕上跟隨鼠標渲染一張貓的圖片的 首先,你可能會像這樣,嘗試在 for a 這一方法對我們的具體用例來說能夠生效,但我們卻沒法實現真正的將行為封裝成可重用的方式的目標。現在,每次我們在不同的用例中想要使用鼠標的位置,我們就不得不創建一個新的針對那一用例渲染不同內容的組件 (如另一個關鍵的 React Mixin將通用共享的方法包裝成Mixins方法,然后注入各個組件實現,事實上已經是不被官方推薦使用了,但仍然可以學習一下,了解其為什么被遺棄,先從API看起。 The current mouse position is ({this.state.x}, {this.state.y}) 然而,為什么Mixin會被不推薦使用?歸納起來就是以下三點 1. Mixin引入了隱式依賴關系 如: 2. Mixin導致名稱沖突 如: 3. Mixin導致復雜的滾雪球 4. 擁抱ES6,ES6的class不支持Mixin 高階組件(HOC)是react中的高級技術,用來重用組件邏輯。但高階組件本身并不是React API。它只是一種模式,這種模式是由react自身的組合性質必然產生的,是React社區發展中產生的一種模式。 高階組件在社區中, 有兩種使用方式, 分別是:
Props Proxy: HOC 對傳給 WrappedComponent W 的 porps 進行操作。
Inheritance Inversion: HOC 繼承 WrappedComponent W。 依然是使用之前的例子, 先從比較普通使用的Props Proxy看起: The current mouse position is ({x}, {y}) 那么在Hoc的Props Proxy模式下, 我們可以做什么? 操作Props 通過 Refs 訪問組件實例 提取state 包裹 WrappedComponent 另外一種HOC模式則是Inheritance Inversion,不過該模式比較少見,一個最簡單的例子如下: 那么在我們的例子中它是這樣的: The current mouse position is ({x}, {y}) 同樣, 在II模式下,我們能做些什么呢? 渲染劫持 可以通過手動修改這個tree,來達到一些需求效果,不過這通常不會用到: 操作 state Props State 可能有人看到這里會有疑惑,為什么有Class而不去使用繼承返回來使用HOC, 這里推薦知乎的一個比較好的答案 D中A相關的功能交由D內部的A來負責,D中B相關的功能交由D內部的B來負責,D僅僅負責維護A,B,C的關系,另外也可以額外提供增加項,實現組件的增強。 繼承沒有什么不好,注意,React只是推薦,但沒限制。其實用繼承來擴展組件也沒問題,而且也存在這樣的場景。比如:有一個按鈕組件,僅僅是對Button進行一個包裝,我們且叫它Button,可是,按照產品需求,很多地方的按鈕都是帶著一個icon的,我們需要提供一個IconButton。這是時候,就可以通過繼承來擴展,同時組合另外一個獨立的組件,我們且叫它Icon,顯示icon的功能交給Icon組件來做,原來按鈕的功能繼續延續著。對于這種同類型組件的擴展,我認為用繼承的方式是沒關系的,靈活性,復用性還在。 繼承會帶來什么問題,以我的實踐經驗,過渡使用繼承,雖然給編碼帶來便利,但容易導致代碼失控,組件膨脹,降低組件的復用性。比如:有一個列表組件,叫它ListView吧,可以上下滾動顯示一個item集,突然有一天需求變了,PM說,我要這個ListView能像iOS那樣有個回彈效果。好,用繼承對這個ListView進行擴展,加入了回彈效果,任務closed。第二天PM找上門來了,希望所有上下滾動的地方都可以支持回彈效果,這時候就懵逼啦,怎么辦?把ListView中回彈效果的代碼copy一遍?這就和DRY原則相悖了不是,而且有可能受到其他地方代碼的影響,處理回彈效果略有不同,要是有一天PM希望對這個回彈效果做升級,那就有得改啦。應對這種場景,最好的辦法是啥?用組合,封裝一個帶回彈效果的Scroller,ListView看成是Scroller和item容器組件的組合,其他地方需要要用到滾動的,直接套一個Scroller,以后不管回彈效果怎么變,我只要維護這個Scroller就好了。當然,最理想的,把回彈效果也做成一個組件SpringBackEffect,從Scroller分離出來,這樣,需要用回彈效果的地方就加上SpringBackEffect組件就好了,這就是為什么組合優先于繼承的原因。 頁面簡單的時候,組合也好,繼承也罷,可維護就好,能夠快速的響應需求迭代就好,用什么方式實現到無所謂。但如果是一個大項目,頁面用到很多組件,或者是團隊多人共同維護的話,就要考慮協作中可能存在的矛盾,然后通過一定約束來閉坑。組合的方式是可以保證組件具有充分的復用性,靈活度,遵守DRY原則的其中一種實踐。 Mixin就像他的名字,他混入了組件中,我們很難去對一個混入了多個Mixin的組件進行管理,好比一個盒子,我們在盒子里面塞入了各種東西(功能),最后肯定是難以理清其中的脈絡。 貫穿傳遞不相關props屬性給被包裹的組件 最大化的組合性 包裝顯示名字以便于調試 不要在render方法內使用高階組件,因為每次高階組件返回的都是不同的組件,會造成不必要的渲染。 必須將靜態方法做拷貝。 當存在多個HOC時,你不知道Props是從哪里來的。 和Mixin一樣, 存在相同名稱的props,則存在覆蓋問題,而且react并不會報錯。 JSX層次中多了很多層次(即無用的空組件),不利于調試。 HOC屬于靜態構建,靜態構建即是重新生成一個組件,即返回的新組件,不會馬上渲染,即新組件中定義的生命周期函數只有新組件被渲染時才會執行。 可以看下最初的例子在render props中的應用: 不用擔心Props是從哪里來的, 它只能從父組件傳遞過來。 不用擔心props的命名問題。 render props是動態構建的。 這里簡單的說下動態構建,因為React官方推崇動態組合,然而HOC實際上是一個靜態構建,比如,在某個需求下,我們需要根據Mouse中某個字段來決定渲染Cat組件或者Dog組件,使用HOC會是如下: 可以看到,我們不得不提前靜態構建好Cat和Dog組件 假如我們用Render props: 很明顯,在動態構建的時候,我們具有更多的靈活性,我們可以更好的利用生命周期。 無法使用SCU做優化, 具體參考官方文檔。 拋開被遺棄的Mixin和尚未穩定的Hooks,目前社區的代碼復用方案主要還是HOC和Render Props,個人感覺,如果是多層組合或者需要動態渲染那就選擇Render Props,而如果是諸如在每個View都要執行的簡單操作,如埋點、title設置等或者是對性能要求比較高如大量表單可以采用HOC。 Function as Child Components Not HOCs class Cat extends React.Component {
render() {
const mouse = this.props.mouse
return (
);
}
}
class MouseWithCat extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
Move the mouse around!
React Mixin只能通過React.createClass()使用, 如下:var mixinDefaultProps = {}
var ExampleComponent = React.createClass({
mixins: [mixinDefaultProps],
render: function(){}
});
Mixin實現
// 封裝的Mixin
const mouseMixin = {
getInitialState() {
return {
x: 0,
y: 0
}
},
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
})
}
}
const Mouse = createReactClass({
mixins: [mouseMixin],
render() {
return (
Mixin的問題
你可能會寫一個有狀態的組件,然后你的同事可能會添加一個讀取這個狀態的mixin。在幾個月內,您可能需要將該狀態移至父組件,以便與兄弟組件共享。你會記得更新mixin來讀取道具嗎?如果現在其他組件也使用這個mixin呢?
你在該Mixin定義了getSomeName, 另外一個Mixin又定義了同樣的名稱getSomeName, 造成了沖突。
隨著時間和業務的增長, 你對Mixin的修改越來越多, 到最后會變成一個難以維護的Mixin。
高階組件的名稱是從高階函數來的, 如果了解過函數式編程, 就會知道高階函數就是一個入參是函數,返回也是函數的函數,那么高階組件顧名思義,就是一個入參是組件,返回也是組件的函數,如:const EnhancedComponent = higherOrderComponent(WrappedComponent);
HOC實現
其中 W (WrappedComponent) 指被包裹的 React.Component,E (EnhancedComponent) 指返回類型為 React.Component 的新的 HOC。
class Mouse extends React.Component {
render() {
const { x, y } = this.props.mouse
return (
如上面的MouseHoc, 假設在日常開發中,我們需要傳入一個props給Mouse或者Cat,那么我們可以在HOC里面對props進行增刪查改等操作,如下:const MouseHoc = (MouseComponent, props) => {
props.text = props.text + "---I can operate props"
return class extends React.Component {
......
render() {
return (
function refsHOC(WrappedComponent) {
return class RefsHOC extends React.Component {
proc(wrappedComponentInstance) {
wrappedComponentInstance.method()
}
render() {
const props = Object.assign({}, this.props, {ref: this.proc.bind(this)})
return
就是我們的例子。function iiHOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
return super.render()
}
}
}
你可以看到,返回的 HOC 類(Enhancer)繼承了 WrappedComponent。之所以被稱為 Inheritance Inversion 是因為 WrappedComponent 被 Enhancer 繼承了,而不是 WrappedComponent 繼承了 Enhancer。在這種方式中,它們的關系看上去被反轉(inverse)了。Inheritance Inversion 允許 HOC 通過 this 訪問到 WrappedComponent,意味著它可以訪問到 state、props、組件生命周期方法和 render 方法。
class Mouse extends React.Component {
render(props) {
const { x, y } = props.mouse
return (
因為render()返回的就是JSX編譯后的對象,如下: function iiHOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
const elementsTree = super.render()
let newProps = {};
if (elementsTree && elementsTree.type === "input") {
newProps = {value: "may the force be with you"}
}
const props = Object.assign({}, elementsTree.props, newProps)
const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.children)
return newElementsTree
}
}
}
HOC 可以讀取、編輯和刪除 WrappedComponent 實例的 state,如果你需要,你也可以給它添加更多的 state。記住,這會搞亂 WrappedComponent 的 state,導致你可能會破壞某些東西。要限制 HOC 讀取或添加 state,添加 state 時應該放在多帶帶的命名空間里,而不是和 WrappedComponent 的 state 混在一起。
export function IIHOCDEBUGGER(WrappedComponent) {
return class II extends WrappedComponent {
render() {
return (
為什么有Class而不去使用繼承返回來使用HOC
HOC Debugger Component
{JSON.stringify(this.props, null, 2)}
{JSON.stringify(this.state, null, 2)}
{super.render()}
OOP和FP并不矛盾,所以混著用沒毛病,很多基于FP思想的庫也需要OOP來搭建。
Mixin和HOC的對比
為什么React推崇HOC和組合的方式,我的理解是React希望組件是按照最小可用的思想來進行封裝的,理想的說,就是一個組件只做一件的事情,且把它做好,DRY。在OOP原則,這叫單一職責原則。如果要對組件增強,首先應該先思路這個增強的組件需要用到哪些功能,這些功能由哪些組件提供,然后把這些組件組合起來.
但是,用繼承的方式擴展前,要先思考,新組件是否與被繼承的組件是不是同一類型的,同一類職責的。如果是,可以繼承,如果不是,那么就用組合。怎么定義同一類呢,回到上面的Button的例子,所謂同一類,就是說,我直接用IconButton直接替換掉Button,不去改動其他代碼,頁面依然可以正常渲染,功能可以正常使用,就可以認為是同一類的,在OOP中,這叫做里氏替換原則。
HOC則像是一個裝飾器,他是在盒子的外面一層一層的裝飾,當我們想要抽取某一層或者增加某一層都非常容易。
高階組件應該貫穿傳遞與它專門關注無關的props屬性。render() {
// 過濾掉專用于這個階組件的props屬性,
// 不應該被貫穿傳遞
const { extraProp, ...passThroughProps } = this.props;
// 向被包裹的組件注入props屬性,這些一般都是狀態值或
// 實例方法
const injectedProp = someStateOrInstanceMethod;
// 向被包裹的組件傳遞props屬性
return (
// 不要這樣做……
const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent))
// ……你可以使用一個函數組合工具
// compose(f, g, h) 和 (...args) => f(g(h(...args)))是一樣的
const enhance = compose(
// 這些都是多帶帶一個參數的高階組件
withRouter,
connect(commentSelector)
)
const EnhancedComponent = enhance(WrappedComponent)
最常用的技術是包裹顯示名字給被包裹的組件。所以,如果你的高階組件名字是 withSubscription,且被包裹的組件的顯示名字是 CommentList,那么就是用 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";
}
HOC的警戒
Render Props從名知義,也是一種剝離重復使用的邏輯代碼,提升組件復用性的解決方案。在被復用的組件中,通過一個名為“render”(屬性名也可以不是render,只要值是一個函數即可)的屬性,該屬性是一個函數,這個函數接受一個對象并返回一個子組件,會將這個函數參數中的對象作為props傳入給新生成的組件。
Render Props應用
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
render props的優勢
Move the mouse around!
const EnhanceCat = MounseHoc(Cat)
const EnhanceDog = MounseHoc(Dog)
class MouseTracker extends React.Component {
render() {
return (
class MouseTracker extends React.Component {
render() {
return (
Move the mouse around!
React高階組件和render props的適用場景有區別嗎,還是更多的是個人偏好?
深入理解 React 高階組件
高階組件-React
精讀《我不再使用高階組件》
為什么 React 推崇 HOC 和組合的方式,而不是繼承的方式來擴展組件?
React 中的 Render Props
使用 Render props 吧!
渲染屬性(Render Props)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/102828.html
摘要:已經被廢除,具體缺陷可以參考二為了解決的缺陷,第二種解決方案是高階組件簡稱。我們定義了父組件,存在自身的,并且將自身的通過的方式傳遞給了子組件。返回一個標識該的變量,以及更新該的方法。 ??為了實現分離業務邏輯代碼,實現組件內部相關業務邏輯的復用,在React的迭代中針對類組件中的代碼復用依次發布了Mixin、HOC、Render props等幾個方案。此外,針對函數組件,在Reac...
摘要:已經被廢除,具體缺陷可以參考二為了解決的缺陷,第二種解決方案是高階組件簡稱。我們定義了父組件,存在自身的,并且將自身的通過的方式傳遞給了子組件。返回一個標識該的變量,以及更新該的方法。 ??為了實現分離業務邏輯代碼,實現組件內部相關業務邏輯的復用,在React的迭代中針對類組件中的代碼復用依次發布了Mixin、HOC、Render props等幾個方案。此外,針對函數組件,在Reac...
摘要:已經被廢除,具體缺陷可以參考二為了解決的缺陷,第二種解決方案是高階組件簡稱。我們定義了父組件,存在自身的,并且將自身的通過的方式傳遞給了子組件。返回一個標識該的變量,以及更新該的方法。 ??為了實現分離業務邏輯代碼,實現組件內部相關業務邏輯的復用,在React的迭代中針對類組件中的代碼復用依次發布了Mixin、HOC、Render props等幾個方案。此外,針對函數組件,在Reac...
摘要:這一周連續發表了兩篇關于的文章組件復用那些事兒實現按需加載輪子應用設計之道化妙用其中涉及到組件復用輪子設計相關話題,并配合相關場景實例進行了分析。 showImg(https://segmentfault.com/img/remote/1460000014482098); 這一周連續發表了兩篇關于 React 的文章: 組件復用那些事兒 - React 實現按需加載輪子 React ...
閱讀 3676·2021-09-22 15:34
閱讀 1186·2019-08-29 17:25
閱讀 3399·2019-08-29 11:18
閱讀 1371·2019-08-26 17:15
閱讀 1739·2019-08-23 17:19
閱讀 1228·2019-08-23 16:15
閱讀 718·2019-08-23 16:02
閱讀 1335·2019-08-23 15:19