高階組件
本質
本質是函數,將組件作為接收參數,返回一個新的組件。HOC本身不是React API,是一種基於React組合的特而形成的設計模式。
解決的問題(作用)
- 一句話概括:功能的復用,減少代碼冗余
- 進一步解釋:在實際情況中,多個組件可能會做某些相同的事情,有着相同的功能,存在大量的代碼冗余。我們可以將這部分功能拆分出來,每個組件盡量只保留自己獨有的作用,通過HOC生成我們最終需要的組件。
實現方法:
無論哪種方法,都是在HOC函數內定義新的組件,在新的組件內做一些公共的功能和事情
- 屬性代理
- 反向繼承
屬性代理
這是最常規的寫法,原理等同於ES7裝飾器、Python裝飾器。函數傳入的參數,除了原組件,還可以定義其他的參數,通過這些參數來區別每個實際組件。比如,公共的功能是獲取數據。獲取數據這件事情是相同的,但獲取的內容不同。如何決定最后生成的組件獲取各自指定的內容呢?通過函數傳參。
// 示例用localstorage代替如網絡請求等的異步操作 localStorage.setItem("comment","asuiklfhs"); localStorage.setItem("read","123"); class Comment extends React.Component { render() { return ( <div> {this.props.data} </div> ) } } class Read extends React.Component { render() { return ( <div> {this.props.data} </div> ) } } const HOCTest = (key) => { return (WrappedComponent) => { return class NewComponent extends React.Component{ constructor(props) { super(props); this.state = { data: '' }; } componentDidMount() { const data = localStorage.getItem(key); this.setState({ data }); } render() { console.log(this.myRef); return ( <div ref={(e)=>{this.myRef = e;}}> <WrappedComponent {...this.props} data={this.state.data} /> </div> ); } } } }; const CommentNew = HOCTest("comment")(Comment); // HOC生成新組件 const ReadNew = HOCTest("read")(Read); class Root extends React.Component { render() { return ( <div className="App"> Hello World <CommentNew/> <ReadNew/> </div> ); } }
在這里,React第三方的組件庫通常使用 函數柯里化 的寫法。對於使用者來說,改變了調用函數時的傳參方式,更加容易理解。即:原本調用函數wrapper(Component, params1, params2) 柯里化后調用wrapper(params1,params2)(Component)
屬性代理常見的作用:
- 操作props
- 通過Refs訪問組件實例
- 提取state
- 組合更多的html元素
反向繼承
顧名思義,就是將返回的組件繼承了原組件。它允許生成的組件通過this獲取原組件,意味着可以獲取到state,props,生命周期鈎子,以及render
localStorage.setItem("comment","asuiklfhs"); localStorage.setItem("read","123"); class Comment extends React.Component { constructor(props){ super(props); this.state = {id:"comment"} } componentDidMount() { console.log("Comment DidMount"); } render() { return ( <div> {this.props.data} </div> ) } } class Read extends React.Component { constructor(props){ super(props); this.state = {id:"read"} } render() { return ( <div> {this.props.data} </div> ) } } const HOCTest = (key) => { return (WrappedComponent) => { return class NewComponent extends WrappedComponent{ componentDidMount() { console.log("HOC DidMount"); const data = localStorage.getItem(key); this.setState({ ...this.state, data }); } render() { return ( <WrappedComponent data={this.state.data}/> ); } } } }; const CommentNew = HOCTest("comment")(Comment); const ReadNew = HOCTest("read")(Read); class Root extends React.Component { render() { return ( <div className="App"> Hello World <CommentNew/> <ReadNew/> </div> ); } }
作用:
- 渲染劫持。由於新組件可以控制原組件的render方法,可以做各種控制渲染的操作。
- 操作state
注意點:
- 不要在函數內修改原組件
- 使用反向繼承方式時,會丟失原本的顯示名
- 不要在render函數中使用HOC
高階組件的缺點:
- 難以溯源。如果原始組件A通過好幾個HOC的構造,最終生成了組件B,不知道哪個屬性來自於哪個HOC,需要翻看每個HOC才知道各自做了什么事情,使用了什么屬性。
- props屬性名的沖突。某個屬性可能被多個HOC重復使用。
- 靜態構建。新的組件是在頁面構建之前生成,先有組件,后生成頁面。
Render Props
作用
- 功能的復用,與HOC類似。
- 組件間數據的單向傳遞。
什么是Render Props?
是一個用於告知組件要渲染什么內容的函數屬性。該函數返回一個組件,是渲染出來的內容。
class Cat extends React.Component { render() { const mouse = this.props.mouse; return ( <p>位置:x:{ mouse.x } y: { mouse.y }</p> ); } } 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 ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> {/* Instead of providing a static representation of what <Mouse> renders, use the `render` prop to dynamically determine what to render. */} {this.props.render(this.state)} </div> ); } } class MouseTracker extends React.Component { render() { return ( <div> <h1>移動鼠標!</h1> <Mouse render={mouse => ( <Cat mouse={mouse} /> )}/> </div> ); } }
通過以上Demo可以看到,Mouse組件通過render屬性(屬性名也可以是別的名字),指定了渲染哪個子組件,並且子組件可以接收參數值,進而實現內部邏輯。
再進行分析,發現Mouse組件提供可變數據源,是一個基礎數據的提供者,最關鍵的代碼是:
{this.props.render(this.state)}
通過render屬性,將數據傳遞給另外一個組件。至於這個數據拿來干什么,怎么去渲染,就不是它管的事情了。
因此,整個頁面很多地方可能需要用到鼠標坐標數據,以上的例子就可以實現功能的復用。
相對高階組件的優點:
- 不用擔心props的命名沖突的問題
- 可以溯源,子組件的props一定來自父組件。
- 是動態構建的,頁面在渲染后,可以動態地決定渲染哪個組件。
- 所有能用HOC完成的事情,Render Props都可以做,且更加靈活。
- 除了功能復用,還可以用作兩個組件的單向數據傳遞。