React中組件復(fù)用的方式總結(jié)
React組件復(fù)用的方式
現(xiàn)前端的工程化越發(fā)重要,雖然使用Ctrl+C
與Ctrl+V
同樣能夠完成需求,但是一旦面臨修改那就是一項(xiàng)龐大的任務(wù),于是減少代碼的拷貝,增加封裝復(fù)用能力,實(shí)現(xiàn)可維護(hù)、可復(fù)用的代碼就變得尤為重要,在React
中組件是代碼復(fù)用的主要單元,基于組合的組件復(fù)用機(jī)制相當(dāng)優(yōu)雅,而對(duì)于更細(xì)粒度的邏輯(狀態(tài)邏輯、行為邏輯等),復(fù)用起來(lái)卻不那么容易,很難把狀態(tài)邏輯拆出來(lái)作為一個(gè)可復(fù)用的函數(shù)或組件,實(shí)際上在Hooks
出現(xiàn)之前,都缺少一種簡(jiǎn)單直接的組件行為擴(kuò)展方式,對(duì)于Mixin
、HOC
、Render Props
都算是在既有(組件機(jī)制的)游戲規(guī)則下探索出來(lái)的上層模式,一直沒(méi)有從根源上很好地解決組件間邏輯復(fù)用的問(wèn)題,直到Hooks
登上舞臺(tái),下面我們就來(lái)介紹一下Mixin
、HOC
、Render Props
、Hooks
四種組件間復(fù)用的方式。
Mixin
當(dāng)然React
很久之前就不再建議使用Mixin
作為復(fù)用的解決方案,但是現(xiàn)在依舊能通過(guò)create-react-class
提供對(duì)Mixin
的支持,此外注意在以ES6
的class
方式聲明組件時(shí)是不支持Mixin
的。Mixins
允許多個(gè)React
組件之間共享代碼,它們非常類似于Python
中的mixins
或PHP
中的traits
,Mixin
方案的出現(xiàn)源自一種OOP
直覺(jué),只在早期提供了React.createClass() API
(React v15.5.0
正式廢棄,移至create-react-class
)來(lái)定義組件,自然而然地,(類)繼承就成了一種直覺(jué)性的嘗試,而在JavaScript
基于原型的擴(kuò)展模式下,類似于繼承的Mixin
方案就成了一個(gè)不錯(cuò)的解決方案,Mixin
主要用來(lái)解決生命周期邏輯和狀態(tài)邏輯的復(fù)用問(wèn)題,允許從外部擴(kuò)展組件生命周期,在Flux
等模式中尤為重要,但是在不斷實(shí)踐中也出現(xiàn)了很多缺陷:
- 組件與
Mixin
之間存在隱式依賴(Mixin
經(jīng)常依賴組件的特定方法,但在定義組件時(shí)并不知道這種依賴關(guān)系)。 - 多個(gè)
Mixin
之間可能產(chǎn)生沖突(比如定義了相同的state
字段)。 Mixin
傾向于增加更多狀態(tài),這降低了應(yīng)用的可預(yù)測(cè)性,導(dǎo)致復(fù)雜度劇增。- 隱式依賴導(dǎo)致依賴關(guān)系不透明,維護(hù)成本和理解成本迅速攀升。
- 難以快速理解組件行為,需要全盤(pán)了解所有依賴
Mixin
的擴(kuò)展行為,及其之間的相互影響 - 組件自身的方法和
state
字段不敢輕易刪改,因?yàn)殡y以確定有沒(méi)有Mixin
依賴它。 Mixin
也難以維護(hù),因?yàn)?code>Mixin邏輯最后會(huì)被打平合并到一起,很難搞清楚一個(gè)Mixin
的輸入輸出。
毫無(wú)疑問(wèn),這些問(wèn)題是致命的,所以,Reactv0.13.0
放棄了Mixin
靜態(tài)橫切(類似于繼承的復(fù)用),轉(zhuǎn)而走向HOC
高階組件(類似于組合的復(fù)用)。
示例
上古版本示例,一個(gè)通用的場(chǎng)景是:
一個(gè)組件需要定期更新,用setInterval()
做很容易,但當(dāng)不需要它的時(shí)候取消定時(shí)器來(lái)節(jié)省內(nèi)存是非常重要的,React
提供生命周期方法來(lái)告知組件創(chuàng)建或銷(xiāo)毀的時(shí)間,下面的Mixin
,使用setInterval()
并保證在組件銷(xiāo)毀時(shí)清理定時(shí)器。
var SetIntervalMixin = { componentWillMount: function() { this.intervals = []; }, setInterval: function() { this.intervals.push(setInterval.apply(null, arguments)); }, componentWillUnmount: function() { this.intervals.forEach(clearInterval); } }; var TickTock = React.createClass({ mixins: [SetIntervalMixin], // 引用 mixin getInitialState: function() { return {seconds: 0}; }, componentDidMount: function() { this.setInterval(this.tick, 1000); // 調(diào)用 mixin 的方法 }, tick: function() { this.setState({seconds: this.state.seconds + 1}); }, render: function() { return ( <p> React has been running for {this.state.seconds} seconds. </p> ); } }); ReactDOM.render( <TickTock />, document.getElementById("example") );
HOC
Mixin
之后,HOC
高階組件擔(dān)起重任,成為組件間邏輯復(fù)用的推薦方案,高階組件從名字上就透漏出高級(jí)的氣息,實(shí)際上這個(gè)概念應(yīng)該是源自于JavaScript
的高階函數(shù),高階函數(shù)就是接受函數(shù)作為輸入或者輸出的函數(shù),可以想到柯里化就是一種高階函數(shù),同樣在React
文檔上也給出了高階組件的定義,高階組件是接收組件并返回新組件的函數(shù)。具體的意思就是:
高階組件可以看作React
對(duì)裝飾模式的一種實(shí)現(xiàn),高階組件就是一個(gè)函數(shù),且該函數(shù)接受一個(gè)組件作為參數(shù),并返回一個(gè)新的組件,他會(huì)返回一個(gè)增強(qiáng)的React
組件,高階組件可以讓我們的代碼更具有復(fù)用性,邏輯性與抽象性,可以對(duì)render
方法進(jìn)行劫持,也可以控制props
與state
等。
對(duì)比Mixin
與HOC
,Mixin
是一種混入的模式,在實(shí)際使用中Mixin
的作用還是非常強(qiáng)大的,能夠使得我們?cè)诙鄠€(gè)組件中共用相同的方法,但同樣也會(huì)給組件不斷增加新的方法和屬性,組件本身不僅可以感知,甚至需要做相關(guān)的處理(例如命名沖突、狀態(tài)維護(hù)等),一旦混入的模塊變多時(shí),整個(gè)組件就變的難以維護(hù),Mixin
可能會(huì)引入不可見(jiàn)的屬性,例如在渲染組件中使用Mixin
方法,給組件帶來(lái)了不可見(jiàn)的屬性props
和狀態(tài)state
,并且Mixin
可能會(huì)相互依賴,相互耦合,不利于代碼維護(hù),此外不同的Mixin
中的方法可能會(huì)相互沖突。之前React
官方建議使用Mixin
用于解決橫切關(guān)注點(diǎn)相關(guān)的問(wèn)題,但由于使用Mixin
可能會(huì)產(chǎn)生更多麻煩,所以官方現(xiàn)在推薦使用HOC
。高階組件HOC
屬于函數(shù)式編程functional programming
思想,對(duì)于被包裹的組件時(shí)不會(huì)感知到高階組件的存在,而高階組件返回的組件會(huì)在原來(lái)的組件之上具有功能增強(qiáng)的效果,基于此React
官方推薦使用高階組件。
HOC
雖然沒(méi)有那么多致命問(wèn)題,但也存在一些小缺陷:
- 擴(kuò)展性限制
: HOC
并不能完全替代Mixin
,一些場(chǎng)景下,Mixin
可以而HOC
做不到,比如PureRenderMixin
,因?yàn)?code>HOC無(wú)法從外部訪問(wèn)子組件的State
,同時(shí)通過(guò)shouldComponentUpdate
濾掉不必要的更新,因此,React
在支持ES6Class
之后提供了React.PureComponent
來(lái)解決這個(gè)問(wèn)題。 Ref
傳遞問(wèn)題: Ref
被隔斷,Ref
的傳遞問(wèn)題在層層包裝下相當(dāng)惱人,函數(shù)Ref
能夠緩解一部分(讓HOC
得以獲知節(jié)點(diǎn)創(chuàng)建與銷(xiāo)毀),以致于后來(lái)有了React.forwardRef API
。WrapperHell: HOC
泛濫,出現(xiàn)WrapperHell
(沒(méi)有包一層解決不了的問(wèn)題,如果有,那就包兩層),多層抽象同樣增加了復(fù)雜度和理解成本,這是最關(guān)鍵的缺陷,而HOC
模式下沒(méi)有很好的解決辦法。
示例
具體而言,高階組件是參數(shù)為組件,返回值為新組件的函數(shù),組件是將props
轉(zhuǎn)換為UI
,而高階組件是將組件轉(zhuǎn)換為另一個(gè)組件。HOC
在React
的第三方庫(kù)中很常見(jiàn),例如Redux
的connect
和Relay
的createFragmentContainer
。
// 高階組件定義 const higherOrderComponent = (WrappedComponent) => { return class EnhancedComponent extends React.Component { // ... render() { return <WrappedComponent {...this.props} />; } }; } // 普通組件定義 class WrappedComponent extends React.Component{ render(){ //.... } } // 返回被高階組件包裝過(guò)的增強(qiáng)組件 const EnhancedComponent = higherOrderComponent(WrappedComponent);
在這里要注意,不要試圖以任何方式在HOC
中修改組件原型,而應(yīng)該使用組合的方式,通過(guò)將組件包裝在容器組件中實(shí)現(xiàn)功能。通常情況下,實(shí)現(xiàn)高階組件的方式有以下兩種:
- 屬性代理
Props Proxy
。 - 反向繼承
Inheritance Inversion
。
屬性代理
例如我們可以為傳入的組件增加一個(gè)存儲(chǔ)中的id
屬性值,通過(guò)高階組件我們就可以為這個(gè)組件新增一個(gè)props
,當(dāng)然我們也可以對(duì)在JSX
中的WrappedComponent
組件中props
進(jìn)行操作,注意不是操作傳入的WrappedComponent
類,我們不應(yīng)該直接修改傳入的組件,而可以在組合的過(guò)程中對(duì)其操作。
const HOC = (WrappedComponent, store) => { return class EnhancedComponent extends React.Component { render() { const newProps = { id: store.id } return <WrappedComponent {...this.props} {...newProps} />; } } }
我們也可以利用高階組件將新組件的狀態(tài)裝入到被包裝組件中,例如我們可以使用高階組件將非受控組件轉(zhuǎn)化為受控組件。
class WrappedComponent extends React.Component { render() { return <input name="name" />; } } const HOC = (WrappedComponent) => { return class EnhancedComponent extends React.Component { constructor(props) { super(props); this.state = { name: "" }; } render() { const newProps = { value: this.state.name, onChange: e => this.setState({name: e.target.value}), } return <WrappedComponent {...this.props} {...newProps} />; } } }
或者我們的目的是將其使用其他組件包裹起來(lái)用以達(dá)成布局或者是樣式的目的。
const HOC = (WrappedComponent) => { return class EnhancedComponent extends React.Component { render() { return ( <div class="layout"> <WrappedComponent {...this.props} /> </div> ); } } }
反向繼承
反向繼承是指返回的組件去繼承之前的組件,在反向繼承中我們可以做非常多的操作,修改state
、props
甚至是翻轉(zhuǎn)Element Tree
,反向繼承有一個(gè)重要的點(diǎn),反向繼承不能保證完整的子組件樹(shù)被解析,也就是說(shuō)解析的元素樹(shù)中包含了組件(函數(shù)類型或者Class
類型),就不能再操作組件的子組件了。
當(dāng)我們使用反向繼承實(shí)現(xiàn)高階組件的時(shí)候可以通過(guò)渲染劫持來(lái)控制渲染,具體是指我們可以有意識(shí)地控制WrappedComponent
的渲染過(guò)程,從而控制渲染控制的結(jié)果,例如我們可以根據(jù)部分參數(shù)去決定是否渲染組件。
const HOC = (WrappedComponent) => { return class EnhancedComponent extends WrappedComponent { render() { return this.props.isRender && super.render(); } } }
甚至我們可以通過(guò)重寫(xiě)的方式劫持原組件的生命周期。
const HOC = (WrappedComponent) => { return class EnhancedComponent extends WrappedComponent { componentDidMount(){ // ... } render() { return super.render(); } } }
由于實(shí)際上是繼承關(guān)系,我們可以去讀取組件的props
和state
,如果有必要的話,甚至可以修改增加、修改和刪除props
和state
,當(dāng)然前提是修改帶來(lái)的風(fēng)險(xiǎn)需要你自己來(lái)控制。在一些情況下,我們可能需要為高階屬性傳入一些參數(shù),那我們就可以通過(guò)柯里化的形式傳入?yún)?shù),配合高階組件可以完成對(duì)組件的類似于閉包的操作。
const HOCFactoryFactory = (params) => { // 此處操作params return (WrappedComponent) => { return class EnhancedComponent extends WrappedComponent { render() { return params.isRender && this.props.isRender && super.render(); } } } }
注意
不要改變?cè)冀M件
不要試圖在HOC
中修改組件原型,或以其他方式改變它。
function logProps(InputComponent) { InputComponent.prototype.componentDidUpdate = function(prevProps) { console.log("Current props: ", this.props); console.log("Previous props: ", prevProps); }; // 返回原始的 input 組件,其已經(jīng)被修改。 return InputComponent; } // 每次調(diào)用 logProps 時(shí),增強(qiáng)組件都會(huì)有 log 輸出。 const EnhancedComponent = logProps(InputComponent);
這樣做會(huì)產(chǎn)生一些不良后果,其一是輸入組件再也無(wú)法像HOC
增強(qiáng)之前那樣使用了,更嚴(yán)重的是,如果你再用另一個(gè)同樣會(huì)修改componentDidUpdate
的HOC
增強(qiáng)它,那么前面的HOC
就會(huì)失效,同時(shí)這個(gè)HOC
也無(wú)法應(yīng)用于沒(méi)有生命周期的函數(shù)組件。
修改傳入組件的HOC
是一種糟糕的抽象方式,調(diào)用者必須知道他們是如何實(shí)現(xiàn)的,以避免與其他HOC
發(fā)生沖突。HOC
不應(yīng)該修改傳入組件,而應(yīng)該使用組合的方式,通過(guò)將組件包裝在容器組件中實(shí)現(xiàn)功能。
function logProps(WrappedComponent) { return class extends React.Component { componentDidUpdate(prevProps) { console.log("Current props: ", this.props); console.log("Previous props: ", prevProps); } render() { // 將 input 組件包裝在容器中,而不對(duì)其進(jìn)行修改,Nice! return <WrappedComponent {...this.props} />; } } }
過(guò)濾props
HOC
為組件添加特性,自身不應(yīng)該大幅改變約定,HOC
返回的組件與原組件應(yīng)保持類似的接口。HOC
應(yīng)該透?jìng)髋c自身無(wú)關(guān)的props
,大多數(shù)HOC
都應(yīng)該包含一個(gè)類似于下面的render
方法。
render() { // 過(guò)濾掉額外的 props,且不要進(jìn)行透?jìng)? const { extraProp, ...passThroughProps } = this.props; // 將 props 注入到被包裝的組件中。 // 通常為 state 的值或者實(shí)例方法。 const injectedProp = someStateOrInstanceMethod; // 將 props 傳遞給被包裝組件 return ( <WrappedComponent injectedProp={injectedProp} {...passThroughProps} /> ); }
最大化可組合性
并不是所有的HOC
都一樣,有時(shí)候它僅接受一個(gè)參數(shù),也就是被包裹的組件。
const NavbarWithRouter = withRouter(Navbar);
HOC
通??梢越邮斩鄠€(gè)參數(shù),比如在Relay
中HOC
額外接收了一個(gè)配置對(duì)象用于指定組件的數(shù)據(jù)依賴。
const CommentWithRelay = Relay.createContainer(Comment, config);
最常見(jiàn)的HOC
簽名如下,connect
是一個(gè)返回高階組件的高階函數(shù)。
// React Redux 的 `connect` 函數(shù) const ConnectedComment = connect(commentSelector, commentActions)(CommentList); // connect 是一個(gè)函數(shù),它的返回值為另外一個(gè)函數(shù)。 const enhance = connect(commentListSelector, commentListActions); // 返回值為 HOC,它會(huì)返回已經(jīng)連接 Redux store 的組件 const ConnectedComment = enhance(CommentList);
這種形式可能看起來(lái)令人困惑或不必要,但它有一個(gè)有用的屬性,像connect
函數(shù)返回的單參數(shù)HOC
具有簽名Component => Component
,輸出類型與輸入類型相同的函數(shù)很容易組合在一起。同樣的屬性也允許connect
和其他HOC
承擔(dān)裝飾器的角色。此外許多第三方庫(kù)都提供了compose
工具函數(shù),包括lodash
、Redux
和Ramda
。
const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent)) // 你可以編寫(xiě)組合工具函數(shù) // compose(f, g, h) 等同于 (...args) => f(g(h(...args))) const enhance = compose( // 這些都是單參數(shù)的 HOC withRouter, connect(commentSelector) ) const EnhancedComponent = enhance(WrappedComponent)
不要在render方法中使用HOC
React
的diff
算法使用組件標(biāo)識(shí)來(lái)確定它是應(yīng)該更新現(xiàn)有子樹(shù)還是將其丟棄并掛載新子樹(shù),如果從render
返回的組件與前一個(gè)渲染中的組件相同===
,則React
通過(guò)將子樹(shù)與新子樹(shù)進(jìn)行區(qū)分來(lái)遞歸更新子樹(shù),如果它們不相等,則完全卸載前一個(gè)子樹(shù)。
通常在使用的時(shí)候不需要考慮這點(diǎn),但對(duì)HOC
來(lái)說(shuō)這一點(diǎn)很重要,因?yàn)檫@代表著你不應(yīng)在組件的render
方法中對(duì)一個(gè)組件應(yīng)用HOC
。
render() { // 每次調(diào)用 render 函數(shù)都會(huì)創(chuàng)建一個(gè)新的 EnhancedComponent // EnhancedComponent1 !== EnhancedComponent2 const EnhancedComponent = enhance(MyComponent); // 這將導(dǎo)致子樹(shù)每次渲染都會(huì)進(jìn)行卸載,和重新掛載的操作! return <EnhancedComponent />; }
這不僅僅是性能問(wèn)題,重新掛載組件會(huì)導(dǎo)致該組件及其所有子組件的狀態(tài)丟失,如果在組件之外創(chuàng)建HOC
,這樣一來(lái)組件只會(huì)創(chuàng)建一次。因此每次render
時(shí)都會(huì)是同一個(gè)組件,一般來(lái)說(shuō),這跟你的預(yù)期表現(xiàn)是一致的。在極少數(shù)情況下,你需要?jiǎng)討B(tài)調(diào)用HOC
,你可以在組件的生命周期方法或其構(gòu)造函數(shù)中進(jìn)行調(diào)用。
務(wù)必復(fù)制靜態(tài)方法
有時(shí)在React
組件上定義靜態(tài)方法很有用,例如Relay
容器暴露了一個(gè)靜態(tài)方法getFragment
以方便組合GraphQL
片段。但是當(dāng)你將HOC
應(yīng)用于組件時(shí),原始組件將使用容器組件進(jìn)行包裝,這意味著新組件沒(méi)有原始組件的任何靜態(tài)方法。
// 定義靜態(tài)函數(shù) WrappedComponent.staticMethod = function() {/*...*/} // 現(xiàn)在使用 HOC const EnhancedComponent = enhance(WrappedComponent); // 增強(qiáng)組件沒(méi)有 staticMethod typeof EnhancedComponent.staticMethod === "undefined" // true
為了解決這個(gè)問(wèn)題,你可以在返回之前把這些方法拷貝到容器組件上。
function enhance(WrappedComponent) { class Enhance extends React.Component {/*...*/} // 必須準(zhǔn)確知道應(yīng)該拷貝哪些方法 :( Enhance.staticMethod = WrappedComponent.staticMethod; return Enhance; }
但要這樣做,你需要知道哪些方法應(yīng)該被拷貝,你可以使用hoist-non-react-statics
依賴自動(dòng)拷貝所有非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)出組件,另一個(gè)可行的方案是再額外導(dǎo)出這個(gè)靜態(tài)方法。
// 使用這種方式代替... MyComponent.someFunction = someFunction; export default MyComponent; // ...單獨(dú)導(dǎo)出該方法... export { someFunction }; // ...并在要使用的組件中,import 它們 import MyComponent, { someFunction } from "./MyComponent.js";
Refs不會(huì)被傳遞
雖然高階組件的約定是將所有props
傳遞給被包裝組件,但這對(duì)于refs
并不適用,那是因?yàn)?code>ref實(shí)際上并不是一個(gè)prop
,就像key
一樣,它是由React
專門(mén)處理的。如果將ref
添加到HOC
的返回組件中,則ref
引用指向容器組件,而不是被包裝組件,這個(gè)問(wèn)題可以通過(guò)React.forwardRef
這個(gè)API
明確地將refs
轉(zhuǎn)發(fā)到內(nèi)部的組件。。
function logProps(Component) { class LogProps extends React.Component { componentDidUpdate(prevProps) { console.log('old props:', prevProps); console.log('new props:', this.props); } render() { const {forwardedRef, ...rest} = this.props; // 將自定義的 prop 屬性 “forwardedRef” 定義為 ref return <Component ref={forwardedRef} {...rest} />; } } // 注意 React.forwardRef 回調(diào)的第二個(gè)參數(shù) “ref”。 // 我們可以將其作為常規(guī) prop 屬性傳遞給 LogProps,例如 “forwardedRef” // 然后它就可以被掛載到被 LogProps 包裹的子組件上。 return React.forwardRef((props, ref) => { return <LogProps {...props} forwardedRef={ref} />; }); }
Render Props
與HOC
一樣,Render Props
也是一直以來(lái)都存在的元老級(jí)模式,render props
指在一種React
組件之間使用一個(gè)值為函數(shù)的props
共享代碼的簡(jiǎn)單技術(shù),具有render props
的組件接收一個(gè)函數(shù),該函數(shù)返回一個(gè)React
元素并調(diào)用它而不是實(shí)現(xiàn)一個(gè)自己的渲染邏輯,render props
是一個(gè)用于告知組件需要渲染什么內(nèi)容的函數(shù)props
,也是組件邏輯復(fù)用的一種實(shí)現(xiàn)方式,簡(jiǎn)單來(lái)說(shuō)就是在被復(fù)用的組件中,通過(guò)一個(gè)名為render
(屬性名也可以不是render
,只要值是一個(gè)函數(shù)即可)的prop
屬性,該屬性是一個(gè)函數(shù),這個(gè)函數(shù)接受一個(gè)對(duì)象并返回一個(gè)子組件,會(huì)將這個(gè)函數(shù)參數(shù)中的對(duì)象作為props
傳入給新生成的組件,而在使用調(diào)用者組件這里,只需要決定這個(gè)組件在哪里渲染以及該以何種邏輯渲染并傳入相關(guān)對(duì)象即可。
對(duì)比HOC
與Render Props
,技術(shù)上,二者都基于組件組合機(jī)制,Render Props
擁有與HOC
一樣的擴(kuò)展能力,稱之為Render Props
,并不是說(shuō)只能用來(lái)復(fù)用渲染邏輯,而是表示在這種模式下,組件是通過(guò)render
()組合起來(lái)的,類似于HOC
模式下通過(guò)Wrapper
的render
()建立組合關(guān)系形式上,二者非常相像,同樣都會(huì)產(chǎn)生一層Wrapper
,而實(shí)際上Render Props
與HOC
甚至能夠相互轉(zhuǎn)換。
同樣,Render Props
也會(huì)存在一些問(wèn)題:
- 數(shù)據(jù)流向更直觀了,子孫組件可以很明確地看到數(shù)據(jù)來(lái)源,但本質(zhì)上
Render Props
是基于閉包實(shí)現(xiàn)的,大量地用于組件的復(fù)用將不可避免地引入了callback hell
問(wèn)題。 - 丟失了組件的上下文,因此沒(méi)有
this.props
屬性,不能像HOC
那樣訪問(wèn)this.props.children
。
示例
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>React</title> </head> <body> <div id="root"></div> </body> <script src="https://unpkg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> class MouseTracker extends React.Component { constructor(props) { super(props); this.state = { x: 0, y: 0, } } handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div onMouseMove={this.handleMouseMove}> {this.props.render(this.state)} {/* Render Props */} </div> ) } } class MouseLocation extends React.Component { render() { return ( <> <h1>請(qǐng)?jiān)诖颂幰苿?dòng)鼠標(biāo)</h1> <p>當(dāng)前鼠標(biāo)的位置是: x:{this.props.mouse.x} y:{this.props.mouse.y}</p> </> ) } } ReactDOM.render( <MouseTracker render={mouse => <MouseLocation mouse={mouse} />}></MouseTracker>, document.getElementById("root") ); </script> </html>
Hooks
代碼復(fù)用的解決方案層出不窮,但是整體來(lái)說(shuō)代碼復(fù)用還是很復(fù)雜的,這其中很大一部分原因在于細(xì)粒度代碼復(fù)用不應(yīng)該與組件復(fù)用捆綁在一起,HOC
、Render Props
等基于組件組合的方案,相當(dāng)于先把要復(fù)用的邏輯包裝成組件,再利用組件復(fù)用機(jī)制實(shí)現(xiàn)邏輯復(fù)用,自然就受限于組件復(fù)用,因而出現(xiàn)擴(kuò)展能力受限、Ref
隔斷、Wrapper Hell
等問(wèn)題,那么我們就需要有一種簡(jiǎn)單直接的代碼復(fù)用方式,函數(shù),將可復(fù)用邏輯抽離成函數(shù)應(yīng)該是最直接、成本最低的代碼復(fù)用方式,但對(duì)于狀態(tài)邏輯,仍然需要通過(guò)一些抽象模式(如Observable
)才能實(shí)現(xiàn)復(fù)用,這正是Hooks
的思路,將函數(shù)作為最小的代碼復(fù)用單元,同時(shí)內(nèi)置一些模式以簡(jiǎn)化狀態(tài)邏輯的復(fù)用。比起上面提到的其它方案,Hooks
讓組件內(nèi)邏輯復(fù)用不再與組件復(fù)用捆綁在一起,是真正在從下層去嘗試解決(組件間)細(xì)粒度邏輯的復(fù)用問(wèn)題此外,這種聲明式邏輯復(fù)用方案將組件間的顯式數(shù)據(jù)流與組合思想進(jìn)一步延伸到了組件內(nèi)。
當(dāng)然Hooks
也并非完美,只是就目前而言,其缺點(diǎn)如下:
- 額外的學(xué)習(xí)成本,主要在于
Functional Component
與Class Component
之間的比較上。 - 寫(xiě)法上有限制(不能出現(xiàn)在條件、循環(huán)中),并且寫(xiě)法限制增加了重構(gòu)成本。
- 破壞了
PureComponent
、React.memo
淺比較的性能優(yōu)化效果,為了取最新的props
和state
,每次render
()都要重新創(chuàng)建事件處函數(shù)。 - 在閉包場(chǎng)景可能會(huì)引用到舊的
state
、props
值。 - 內(nèi)部實(shí)現(xiàn)上不直觀,依賴一份可變的全局狀態(tài),不再那么
pure
。 React.memo
并不能完全替代shouldComponentUpdate
(因?yàn)槟貌坏?code>state change,只針對(duì)props change
)。useState API
設(shè)計(jì)上不太完美。
示例
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>React</title> </head> <body> <div id="root"></div> </body> <script src="https://unpkg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> const {useState, useEffect} = React; function useMouseLocation(location){ return ( <> <h1>請(qǐng)?jiān)诖颂幰苿?dòng)鼠標(biāo)</h1> <p>當(dāng)前鼠標(biāo)的位置是: x:{location.x} y:{location.y}</p> </> ); } function MouseTracker(props){ const [x, setX] = useState(0); const [y, setY] = useState(0); function handleMouseMove(event){ setX(event.clientX); setY(event.clientY); } return ( <div onMouseMove={handleMouseMove}> {useMouseLocation({x, y})} </div> ) } ReactDOM.render( <MouseTracker/>, document.getElementById("root") ); </script> </html>
到此這篇關(guān)于React中組件復(fù)用的方式總結(jié)的文章就介紹到這了,更多相關(guān)React組件復(fù)用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文詳解手動(dòng)實(shí)現(xiàn)Recoil狀態(tài)管理基本原理
這篇文章主要為大家介紹了一文詳解手動(dòng)實(shí)現(xiàn)Recoil狀態(tài)管理基本原理實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05React組件設(shè)計(jì)過(guò)程之仿抖音訂單組件
這篇文章主要介紹了React組件設(shè)計(jì)過(guò)程之仿抖音訂單組件的實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07react組件實(shí)例屬性props實(shí)例詳解
這篇文章主要介紹了react組件實(shí)例屬性props,本文結(jié)合實(shí)例代碼給大家簡(jiǎn)單介紹了props使用方法,代碼簡(jiǎn)單易懂,需要的朋友可以參考下2023-01-0130行代碼實(shí)現(xiàn)React雙向綁定hook的示例代碼
本文主要介紹了30行代碼實(shí)現(xiàn)React雙向綁定hook的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04React 項(xiàng)目中動(dòng)態(tài)設(shè)置環(huán)境變量
本文主要介紹了React 項(xiàng)目中動(dòng)態(tài)設(shè)置環(huán)境變量,本文將介紹兩種常用的方法,使用 dotenv 庫(kù)和通過(guò)命令行參數(shù)傳遞環(huán)境變量,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04詳解React中setState回調(diào)函數(shù)
這篇文章主要介紹了詳解React中setState回調(diào)函數(shù),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06使用react+redux實(shí)現(xiàn)彈出框案例
這篇文章主要為大家詳細(xì)介紹了使用react+redux實(shí)現(xiàn)彈出框案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08