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