React 高阶组件(HOC)
官方文檔:https://zh-hans.reactjs.org/docs/higher-order-components.html
組件是 React 中代碼復(fù)用的基本單元。但有時(shí)會(huì)發(fā)現(xiàn)某些模式并不合適傳統(tǒng)組件。
含義
HOC 是靈活使用 react 組件的一個(gè)技巧,本身不是一個(gè)組件,是一個(gè)參數(shù)為組件,返回值是一個(gè)組件的函數(shù)。
HOC 本身不是 react 中 API 的一部分,是 react 的組合特性而形成的設(shè)計(jì)模式。
作用
* 強(qiáng)化組件
高階組件返回的組件,可以劫持上一層傳過(guò)來(lái)的 props,然后混入新的 props,來(lái)增強(qiáng)組件的功能。
1.混入 props
通過(guò)承接上層的 props,在混入自己的 props,來(lái)強(qiáng)化組件
function classHOC(WrapComponent){return class Idex extends React.Component{state={name:'gs'}componentDidMount(){console.log('HOC')}render(){return <WrapComponent{ ...this.props }{...this.state }/>}} } function Index(props){const { name,...otherProps } = propsuseEffect(()=>{console.log( 'index' )},[])return <div>my name is { name }</div> }export default classHOC(Index)hooks 版本應(yīng)用
function functionHoc(WrapComponent){return function Index(props){const [ state , setState ] = useState({ name :'gs' })return <WrapComponent { ...props } { ...state } />} }2.state 控制更新
可以將 HOC 的 state 的配合起來(lái),控制業(yè)務(wù)組件的更新。類似用于處理來(lái)自 react-redux 中 state 更改,帶來(lái)的訂閱更新的作用。
function classHOC(WrapComponent){return class Idex extends React.Component{constructor(){super()this.state={name:'gs'}}const changeName=(name)=>{this.setState({ name })}render(){return<WrapComponent{ ...this.props }{ ...this.state }changeName={this.changeName}/>}} } function Index(props){const [ value ,setValue ] = useState(null)const { name ,changeName } = propsreturn <div><div> hello,world , my name is { name }</div>改變name <input onChange={ (e)=> setValue(e.target.value) } /><button onClick={ ()=> changeName(value) } >確定</button></div> }export default classHOC(Index)* 復(fù)用邏輯
高階組件可以對(duì) react 組件進(jìn)行加工處理。可以根據(jù)需求對(duì)原有的組件進(jìn)行加工,可以定制專屬的 HOC。
官網(wǎng)例子:
例如,假設(shè)有一個(gè) CommentList 組件,它訂閱外部數(shù)據(jù)源,用以渲染評(píng)論列表:
編寫了一個(gè)用于訂閱單個(gè)博客帖子的組件,該帖子遵循類似的模式:
class BlogPost extends React.Component {constructor(props) {super(props);this.handleChange = this.handleChange.bind(this);this.state = {blogPost: DataSource.getBlogPost(props.id)};}componentDidMount() {DataSource.addChangeListener(this.handleChange);}componentWillUnmount() {DataSource.removeChangeListener(this.handleChange);}handleChange() {this.setState({blogPost: DataSource.getBlogPost(this.props.id)});}render() {return <TextBlock text={this.state.blogPost} />;} }CommentList 和 BlogPost 不同 - 它們?cè)?DataSource 上調(diào)用不同的方法,且渲染不同的結(jié)果。但它們的大部分實(shí)現(xiàn)都是一樣的: 1.在掛載時(shí),向 DataSource 添加一個(gè)更改偵聽(tīng)器。 2.在偵聽(tīng)器內(nèi)部,當(dāng)數(shù)據(jù)源發(fā)生變化時(shí),調(diào)用 setState。 3.在卸載時(shí),刪除偵聽(tīng)器。
所以調(diào)用函數(shù) withSubscription 函數(shù)進(jìn)行處理:
withSubscription 函數(shù)邏輯處理:
// 此函數(shù)接收一個(gè)組件... function withSubscription(WrappedComponent, selectData) {// ...并返回另一個(gè)組件...return class extends React.Component {constructor(props) {super(props);this.handleChange = this.handleChange.bind(this);this.state = {data: selectData(DataSource, props)};}componentDidMount() {// ...負(fù)責(zé)訂閱相關(guān)的操作...DataSource.addChangeListener(this.handleChange);}componentWillUnmount() {DataSource.removeChangeListener(this.handleChange);}handleChange() {this.setState({data: selectData(DataSource, this.props)});}render() {// ... 并使用新數(shù)據(jù)渲染被包裝的組件!// 請(qǐng)注意,我們可能還會(huì)傳遞其他屬性return <WrappedComponentdata={this.state.data}{...this.props}/>;}}; }* 事件監(jiān)控
HOC 還可以對(duì)原有組件進(jìn)行監(jiān)控。比如對(duì)一些事件監(jiān)控,錯(cuò)誤監(jiān)控,事件監(jiān)聽(tīng)等一系列操作。
function ClickHoc (Component){return function Wrap(props){const dom = useRef(null)useEffect(()=>{const handleClick = () => console.log('發(fā)生點(diǎn)擊事件')dom.current.addEventListener('click',handleClick)return () => dom.current.removeEventListener('click',handleClick)},[])return <div ref={dom} ><Component {...props} /></div>} }@ClickHoc class Index extends React.Component{render(){return <div className='index' ><button>組件內(nèi)部點(diǎn)擊</button></div>} } export default ()=>{return <div className='box' ><Index /><button>組件外部點(diǎn)擊</button></div> }以上 🈯? 對(duì)組件內(nèi)的點(diǎn)擊事件做一個(gè)監(jiān)聽(tīng)效果
* 提升渲染性能
劫持渲染是 hoc 一個(gè)特性,在 wrapComponent 包裝組件中,可以對(duì)原來(lái)的組件,進(jìn)行條件渲染,節(jié)流渲染,懶加載等功能。
1.條件渲染
動(dòng)態(tài)掛載組件的 HOC
import {SyncOutlined} from '@ant-design/icons' function renderHOC(WrapComponent){return class Index extends React.Component{constructor(props){super(props)this.state={ visible:false }}const setVisible=()=>{this.setState({ visible:!this.state.visible })}render(){const { visible } = this.statereturn <div className="box" ><button onClick={ this.setVisible } > 掛載組件 </button>{ visible ? <WrapComponent { ...this.props } setVisible={ this.setVisible } />: <div ><SyncOutlined spin className="theicon" /></div> }</div>}} }class Index extends React.Component{render(){const { setVisible } = this.propsreturn <div className="box" ><a>點(diǎn)之后我就出現(xiàn)了</a><button onClick={() => setVisible()} > 卸載當(dāng)前組件 </button></div>} } export default renderHOC(Index)2.分片渲染
實(shí)現(xiàn)一個(gè)懶加載功能的 HOC
const renderQueue = [] let isFirstRender = falseconst tryRender = ()=>{const render = renderQueue.shift()if(!render) returnsetTimeout(()=>{render() /* 執(zhí)行下一段渲染 */},300) } /* HOC */ function renderHOC(WrapComponent){return function Index(props){const [ isRender , setRender ] = useState(false)useEffect(()=>{renderQueue.push(()=>{ /* 放入待渲染隊(duì)列中 */setRender(true)})if(!isFirstRender) {tryRender()isFirstRender = true}},[])return isRender ? <WrapComponent tryRender={tryRender} { ...props } /> : <div className='box' ><div className="icon" ><SyncOutlined spin /></div></div>} } /* 業(yè)務(wù)組件 */ class Index extends React.Component{componentDidMount(){const { name , tryRender} = this.props/* 上一部分渲染完畢,進(jìn)行下一部分渲染 */tryRender()console.log( name+'渲染')}render(){return <div><a>我就出現(xiàn)了</a></div>} } /* 高階組件包裹 */ const Item = renderHOC(Index)export default () => {return <React.Fragment><Item name="組件一" /><Item name="組件二" /><Item name="組件三" /></React.Fragment> }異步組件(懶加載)
配合 import 實(shí)現(xiàn)異步加載功能.
使用
const Index = AsyncRouter(()=>import('../pages/index'))3.節(jié)流渲染
可以實(shí)現(xiàn)對(duì)業(yè)務(wù)組件的渲染控制,減少渲染次數(shù),從而達(dá)到優(yōu)化性能的效果
function HOC (Component){return function renderWrapComponent(props){const { num } = propsconst RenderElement = useMemo(() => <Component {...props} /> ,[ num ])return RenderElement} } class Index extends React.Component{render(){console.log(`當(dāng)前組件是否渲染`,this.props)return <div>hello,world, my name is alien </div>} } const IndexHoc = HOC(Index)export default ()=> {const [ num ,setNumber ] = useState(0)const [ num1 ,setNumber1 ] = useState(0)const [ num2 ,setNumber2 ] = useState(0)return <div><IndexHoc num={ num } num1={num1} num2={ num2 } /><button onClick={() => setNumber(num + 1) } >num++</button><button onClick={() => setNumber1(num1 + 1) } >num1++</button><button onClick={() => setNumber2(num2 + 1) } >num2++</button></div> }意義
組件是把 props 渲染成 UI,而高階組件是將組件轉(zhuǎn)換成另一個(gè)組件。可達(dá)到強(qiáng)化或節(jié)省邏輯、解決原有組件的缺陷
使用
命名格式
以 with 開(kāi)頭,駝峰式命名,如 withSubscription。
注意
* 不要在 render 里使用
React 的 diff 算法(稱為協(xié)調(diào))使用組件標(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ù)。
render() {const EnhancedComponent = enhance(MyComponent);return <EnhancedComponent />; }每次調(diào)用 render 函數(shù)都會(huì)創(chuàng)建一個(gè)新的 EnhancedComponent,EnhancedComponent1 !== EnhancedComponent2. 這將導(dǎo)致子樹(shù)每次渲染都會(huì)進(jìn)行卸載,和重新掛載的操作!
重新渲染掛載組件可能會(huì)導(dǎo)致改組件及所有子組件的狀態(tài)丟失
* 務(wù)必復(fù)制靜態(tài)方法
當(dāng)將 HOC 應(yīng)用于組件時(shí),原始組件將使用容器組件進(jìn)行包裝。這意味著新組件沒(méi)有原始組件的任何靜態(tài)方法。
1.可以手動(dòng)繼承
可以手動(dòng)將原始組件的靜態(tài)方法復(fù)制到 hoc 組件上來(lái),但前提是必須準(zhǔn)確知道應(yīng)該拷貝哪些方法。
2.引入第三方庫(kù)
每個(gè)靜態(tài)方法都綁定會(huì)很累,尤其對(duì)于開(kāi)源的 hoc,對(duì)原生組件的靜態(tài)方法是未知的,我們可以使用 hoist-non-react-statics 自動(dòng)拷貝所有的靜態(tài)方法:
* Refs 不會(huì)被傳遞
雖然高階組件的約定是將所有 props 傳遞給被包裝組件,但這對(duì)于 refs 并不適用。那是因?yàn)?ref 實(shí)際上并不是一個(gè) prop, - 就像 key 一樣,它是由 React 專門處理的。如果將 ref 添加到 HOC 的返回組件中,則 ref 引用指向容器組件,而不是被包裝組件。
* 謹(jǐn)慎修改原型鏈
function HOC (Component){const proDidMount = Component.prototype.componentDidMountComponent.prototype.componentDidMount = function(){console.log('劫持生命周期:componentDidMount')proDidMount.call(this)}return Component }這樣做會(huì)產(chǎn)生一些不良后果。比如如果你再用另一個(gè)同樣會(huì)修改 componentDidMount 的 HOC 增強(qiáng)它,那么前面的 HOC 就會(huì)失效!同時(shí),這個(gè) HOC 也無(wú)法應(yīng)用于沒(méi)有生命周期的函數(shù)組件。
總結(jié)
以上是生活随笔為你收集整理的React 高阶组件(HOC)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 新一代VTL实现“新”的数据保护
- 下一篇: (CVE-2014-0160)OpenS