React 之 高阶组件的理解
1.基本概念
高階組件是參數(shù)為組件,返回值為新組件的函數(shù)。
?
2.舉例說明
① 裝飾工廠模式
組件是 react 中的基本單元,組件中通常有一些邏輯(非渲染)需要復(fù)用處理。這里我們可以用高階組件對組件內(nèi)部中的一些通用進行封裝。
未封裝時,相同的邏輯無法復(fù)用:
渲染評論列表
class CommentList extends React.Component {constructor(props) {super(props);this.handleChange = this.handleChange.bind(this);this.state = {// 假設(shè) "DataSource" 是個全局范圍內(nèi)的數(shù)據(jù)源變量comments: DataSource.getComments()};}componentDidMount() {// 訂閱更改DataSource.addChangeListener(this.handleChange);}componentWillUnmount() {// 清除訂閱DataSource.removeChangeListener(this.handleChange);}handleChange() {// 當(dāng)數(shù)據(jù)源更新時,更新組件狀態(tài)this.setState({comments: DataSource.getComments()});}render() {return (<div>{this.state.comments.map((comment) => (<Comment comment={comment} key={comment.id} />))}</div>);} }渲染博客列表
lass 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} />;} }借用高階組件,封裝公用邏輯:
const CommentListWithSubscription = withSubscription(CommentList,(DataSource) => DataSource.getComments() );const BlogPostWithSubscription = withSubscription(BlogPost,(DataSource, props) => DataSource.getBlogPost(props.id) );組件加工(加工:處理公用邏輯)工廠,接受舊組件,返回新組件:
// 此函數(shù)接收一個組件... 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() {// ...負(fù)責(zé)訂閱相關(guān)的操作...DataSource.addChangeListener(this.handleChange);}componentWillUnmount() {DataSource.removeChangeListener(this.handleChange);}handleChange() {this.setState({data: selectData(DataSource, this.props)});}render() {// ... 并使用新數(shù)據(jù)渲染被包裝的組件!// 請注意,我們可能還會傳遞其他屬性return <WrappedComponent data={this.state.data} {...this.props} />;}}; }
② 高階組件柯里化
高階組件是一個參數(shù)是組件返回值也是組件的函數(shù),那么我們借助函數(shù)柯里化,確保最終返回的函數(shù)是高階組件就可以了。
import React, { Component } from "react";const isEmpty = prop =>(prop && prop.hasOwnProperty("length") && prop.length === 0) ||(prop.constructor === Object && Object.keys(prop).length === 0);export default loadingProp => WrappedComponent => {const hocComponent = class extends Component {componentDidMount() {this.startTimer = Date.now();}componentWillUpdate(nextProps, nextState) {console.log(nextProps)if (!isEmpty(nextProps[loadingProp])) {this.endTimer = Date.now();}}render() {const myProps = {loadingTime: ((this.endTimer - this.startTimer) / 1000).toFixed(2)};return isEmpty(this.props[loadingProp]) ? (<div>loading...</div>) : (<WrappedComponent {...this.props} {...myProps} />);}};return hocComponent; };?
其中 ,而 loadingProp => ... 是一個返回值為高階組件的函數(shù),其返回結(jié)果 WrappedComponent => {...}? 是一個單參數(shù)(即被包裹的組件)的高階組件。這樣做有什么好處呢?為什么不寫一個高階組件并將參數(shù)與包裝組件一并傳遞過去?高階組件受限于返回值必須是組件,因此它無法柯里化。而在高階組件之上再構(gòu)建一個函數(shù)就能進行柯里化。同時,返回的結(jié)果是單參數(shù)(被包裹組件)的高階組件可以直接做 hoc 嵌套,即 一個 hoc 嵌套 另一個 hoc(因為傳入值、傳出值都是 組件)。此外的對于 hoc 嵌套調(diào)用,可以借助 compose 工具函數(shù) 進行扁平化處理。
許多第三方庫提供都提供了?compose?工具函數(shù),包括 lodash (比如?lodash.flowRight),?Redux?和?Ramda。
?
?③ 設(shè)定 hoc 的顯示名稱 displayName
為了方便調(diào)試,設(shè)定 hoc 的顯示名稱類似:?WithSubscription(CommentList)
function withSubscription(WrappedComponent) {class WithSubscription extends React.Component {/* ... */}WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;return WithSubscription; }function getDisplayName(WrappedComponent) {return WrappedComponent.displayName || WrappedComponent.name || 'Component'; }?
?3. 注意事項
?① 不要在 render 方法中使用 hoc
因為? hoc 會產(chǎn)生新的組件,而 redner 方法經(jīng)常被調(diào)用,所以會不斷產(chǎn)生新的組件(而在 react 的 diff 算法中會將之前舊的卸載而替換成新的,這不僅僅會對性能造成影響,同時重新掛載組件會導(dǎo)致該組件及其所有子組件的狀態(tài)丟失。
?
②務(wù)必復(fù)制靜態(tài)方法
手動一個個復(fù)制
function enhance(WrappedComponent) {class Enhance extends React.Component {/*...*/}// 必須準(zhǔn)確知道應(yīng)該拷貝哪些方法 :(Enhance.staticMethod = WrappedComponent.staticMethod;return Enhance; }使用?hoist-non-react-statics?自動拷貝所有非 React 靜態(tài)方法
import hoistNonReactStatic from 'hoist-non-react-statics'; function enhance(WrappedComponent) {class Enhance extends React.Component {/*...*/}hoistNonReactStatic(Enhance, WrappedComponent);return Enhance; }
③ Ref 需要轉(zhuǎn)發(fā)
雖然高階組件的約定是將所有 props 傳遞給被包裝組件,但這對于 refs 并不適用。那是因為?ref?實際上并不是一個 prop - 就像?key?一樣,它是由 React 專門處理的。如果將 ref 添加到 HOC 的返回組件中,則 ref 引用指向容器組件,而不是被包裝組件。
使用 hoc 包裹組件
class FancyButton extends React.Component {focus() {// ...}// ... }// 我們導(dǎo)出 LogProps,而不是 FancyButton。 // 雖然它也會渲染一個 FancyButton。 export default logProps(FancyButton);形式上導(dǎo)入的是原組件,實際上導(dǎo)入的是 hoc 包裹的原組件。這時如果直接傳 ref 到該組件,實際上? ref 并沒有傳遞到原組件中,而停留在 hoc 組件上。
import FancyButton from './FancyButton';const ref = React.createRef();// 我們導(dǎo)入的 FancyButton 組件是高階組件(HOC)LogProps。 // 盡管渲染結(jié)果將是一樣的, // 但我們的 ref 將指向 LogProps 而不是內(nèi)部的 FancyButton 組件! // 這意味著我們不能調(diào)用例如 ref.current.focus() 這樣的方法 <FancyButtonlabel="Click Me"handleClick={handleClick}ref={ref} />;這里我們需要對停留在 hoc 組件中的 ref 進行轉(zhuǎn)發(fā),使其傳遞到原組件中
return React.forwardRef((props, ref) => {return <LogProps {...props} forwardedRef={ref} />;});?
?
233
轉(zhuǎn)載于:https://www.cnblogs.com/lemos/p/11013369.html
總結(jié)
以上是生活随笔為你收集整理的React 之 高阶组件的理解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 最新版Kubernetes常用命令大全
- 下一篇: Qt坐标系以及自定义可移动控件