掌握這個有用的模式,停止在React Components
中重複邏輯! ?原文:React Higher Order Components in 3 minutes
作者:Jhey Tompkins
譯者:博軒
什麼是高階元件?
高階元件(HOC)是 React
中用於複用元件邏輯的一種高階技巧。HOC
自身不是 React API
的一部分,它是一種基於 React
的組合特性而形成的設計模式。
譯註:對,我又一次借鑑了官網 ?
它做了什麼?
他們接收一個元件並返回一個新的元件!
什麼時候去使用?
當你的元件之間出現重複的模式 / 邏輯的時候。
栗子:
- 掛載,訂閱資料
- 為
UI
增加互動(也可以使用容器元件,或者 Render Props) - 排序,過濾輸入的資料
譯註:第三個說法,我個人可能更加傾向於在傳入元件之前做處理,而不是使用高階元件
一個愚蠢的例子
我們有一個 Mouse
元件。
const Mouse = () => (
<span className="mouse" role="img">?</span>
)複製程式碼
接下來,讓我們使用 GreenSock’s Draggable 模組,來讓元件變的可以拖拽。
class Mouse extends Component {
componentDidMount = () => new Draggable(this.ELEMENT)
render = () => (
<span className="mouse" ref={e => (this.ELEMENT = e)} role="img">?</span>
)
}複製程式碼
我們加一隻貓 ?
const Cat = () => (
<span className="cat" role="img">?</span>
)複製程式碼
這個元件同樣需要變得可拖拽✋,接下來,讓我們使用高階元件(HOC)來試一下:
const withDrag = Wrapped => {
class WithDrag extends Component {
componentDidMount = () => new Draggable(this.ELEMENT)
render = () => {
return (
<span className="draggable_wrapper" ref={e => (this.ELEMENT = e)}>
<Wrapped {...this.props} />
</span>
)
}
}
WithDrag.displayName = `WithDrag(${Wrapped.displayName || Wrapped.name})`
return WithDrag;
}複製程式碼
我們的高階元件(HOC)可以通過 props
接受一元件,並返回一個新的元件。
許多高階元件會在傳遞元件的過程中,注入新的 props
。這通常是決定我們是否應該使用高階元件的因素之一。如果,我們不注入 props
,我們可以使用一個容器元件,或者 Render Props。
對於我們的高階元件(HOC),我們也可以使用 Render Props 來達到相同的效果。你可能會覺得使用 HOC
來實現並不合適。但是,這個 “愚蠢的例子” 會讓你更加熟悉 HOC
。 這比注入資料的示例更加有趣!?
讓我們將這個 HOC
應用到 Cat
和 Mouse
元件上吧 ?
const Mouse = () => (
<span className="mouse" role="img">?</span>
)
const Cat = () => (
<span className="cat" role="img">?</span>
)
const DraggableMouse = withDrag(Mouse)
const DraggableCat = withDrag(Cat)
class App extends Component {
render = () => (
<Fragment>
<DraggableMouse />
<DraggableCat />
</Fragment>
)
}複製程式碼
接下來,讓我們在高階元件中增加 onDrag
回撥函式,並在 props
中注入 x
和 y
的位置屬性。
const withDrag = Wrapped => {
class WithDrag extends Component {
state = {
x: undefined,
y: undefined,
}
componentDidMount = () => new Draggable(this.ELEMENT, { onDrag: this.onDrag })
onDrag = e => {
const { x, y } = e.target.getBoundingClientRect();
this.setState({ x: Math.floor(x), y: Math.floor(y) })
}
render = () => (
<span className="draggable_wrapper" ref={e => (this.ELEMENT = e)}>
<Wrapped {...this.props} x={this.state.x} y={this.state.y} />
</span>
)
}
WithDrag.displayName = `WithDrag(${Wrapped.displayName || Wrapped.name})`
return WithDrag;
}複製程式碼
const Mouse = () => (
<span className="mouse" role="img">
?
{x !== undefined &&
y !== undefined && (
<span className="mouse__position"> {`(${x}, ${y})`} </span>
)}
</span>
)複製程式碼
現在 Mouse
元件會向使用者展示他的 XY
位置屬性 ?
我們也可以給 HOC
傳遞 props
。然後在傳遞的過程中過濾掉這些無用的屬性。舉個例子,傳遞一個 onDrag
回撥函式。
const withDrag = Wrapped => {
class WithDrag extends Component {
componentDidMount = () => new Draggable(this.ELEMENT, { onDrag: this.props.onDrag })
render = () => {
const { onDrag, ...passed } = this.props;
return (
<span className="draggable__wrapper" ref={e => (this.ELEMENT = e)}>
<Wrapped {...passed} />
</span>
)
}
}
WithDrag.displayName = `WithDrag(${Wrapped.displayName || Wrapped.name})`
return WithDrag;
}
class App extends Component {
render = () => (
<Fragment>
<DraggableMouse
onDrag={e => console.info(e.target.getBoundingClientRect())}
/>
</Fragment>
)
}複製程式碼
通過使用 HOC
,我們的元件仍然很簡單,複雜的邏輯都交給 HOC
來處理了。 我們的元件只關心傳遞給他們的內容。 我們可以在其他地方重複使用它們而且不會有可以被拖拽的屬性。這使得我們的應用更容易維護。
優秀的實踐 ?
- 當出現重複的模式的時候,使用它們
- 為了方便除錯,需要更新處理之後元件的
displayName
- 傳遞與當前
HOC
無關的所有props
糟糕的實踐 ?
- 過度使用,其他模式可能會更加適合
- 改變原始元件
- 在
render
方法中使用高階元件
譯註:永遠不要在React render()
方法中定義React
元件(甚至是無狀態元件)。React
在每次更新狀態的時候,都會廢棄舊的html DOM
元素並將其替換為全新的元素。比如在render()
函式中定義一個輸入元件,元素被替換之後就會失去焦點,每次只能輸入一個字元。
注意 ?
Refs
不會被傳遞- 務必複製靜態方法
- 大部分
HOC
都可以和render props
相互替換使用
這就是一篇關於高階元件的簡短介紹 ~
示例連結
withDrag Mouse and Cat (HOC)示例連結
Drag Mouse and Cat (Render Props)示例連結
withDrag Mouse and Cat (X , Y) (HOC)示例連結
Drag Mouse and Cat (X , Y)(Render Props)示例連結
Drag Mouse and Cat (X , Y)(Hooks)示例連結
官方文件:高階元件本文已經聯絡原文作者,並授權翻譯,轉載請保留原文連結