三分鐘掌握 React 高階組件

掌握這個有用的模式,停止在 React Components 中重複邏輯! 😎

原文:React Higher Order Components in 3 minutes
作者:Jhey Tompkins
譯者:博軒

什麼是高階組件?

高階組件(HOC)是 React 中用於複用組件邏輯的一種高級技巧。HOC 自身不是 React API 的一部分,它是一種基於 React 的組合特性而形成的設計模式。

譯註:對,我又一次借鑑了官網 😂

它做了什麼?

他們接收一個組件並返回一個新的組件!

什麼時候去使用?

當你的組件之間出現重複的模式 / 邏輯的時候。

栗子:

  • 掛載,訂閱數據
  • UI 增加交互(也可以使用容器組件,或者 Render Props
  • 排序,過濾輸入的數據
譯註:第三個說法,我個人可能更加傾向於在傳入組件之前做處理,而不是使用高階組件

一個愚蠢的例子

我們有一個 Mouse 組件。

const Mouse = () => (
    <span className="mouse" role="img">🐭</span>
)

接下來,讓我們使用 GreenSock’s Draggable 模塊,來讓組件變的可以拖拽。

class Mouse extends Component {
    componentDidMount = () => new Draggable(this.ELEMENT)
    render = () => (
        <span className="mouse" ref={e => (this.ELEMENT = e)} role="img">🐭</span>
    )
}

圖片描述

我們加一隻 🐱

const Cat = () => (
    <span className="cat" role="img">🐱</span>
)

這個組件同樣需要變得可拖拽✋,接下來,讓我們使用高階組件(HOC)來試一下:

const withDrag = Wrapped => {
    class WithDrag extends Component {
        componentDidMount = () => new Draggable(this.ELEMENT)
        render = () => {
            return (
                <span className="draggable_wrapper" ref={e => (this.ELEMENT = e)}>
                    <Wrapped  {...this.props} />
                </span>
            )
        }
    }
    WithDrag.displayName = `WithDrag(${Wrapped.displayName || Wrapped.name})`
    return WithDrag;
}

我們的高階組件(HOC)可以通過 props 接受一組件,並返回一個新的組件。

許多高階組件會在傳遞組件的過程中,注入新的 props 。這通常是決定我們是否應該使用高階組件的因素之一。如果,我們不注入 props ,我們可以使用一個容器組件,或者 Render Props

對於我們的高階組件(HOC),我們也可以使用 Render Props 來達到相同的效果。你可能會覺得使用 HOC 來實現並不合適。但是,這個 “愚蠢的例子” 會讓你更加熟悉 HOC 。 這比注入數據的示例更加有趣!😉

讓我們將這個 HOC 應用到 CatMouse 組件上吧 👍

const Mouse = () => (
    <span className="mouse" role="img">🐭</span>
)
const Cat = () => (
    <span className="cat" role="img">🐱</span>
)
const DraggableMouse = withDrag(Mouse)
const DraggableCat = withDrag(Cat)

class App extends Component {
    render = () => (
        <Fragment>
            <DraggableMouse />
            <DraggableCat />
        </Fragment>
    )
}

圖片描述

接下來,讓我們在高階組件中增加 onDrag 回調函數,並在 props 中注入 xy 的位置屬性。

const withDrag = Wrapped => {
    class WithDrag extends Component {
        state = {
            x: undefined,
            y: undefined,
        }
        componentDidMount = () => new Draggable(this.ELEMENT, { onDrag: this.onDrag })
        onDrag = e => {
            const { x, y } = e.target.getBoundingClientRect();
            this.setState({ x: Math.floor(x), y: Math.floor(y) })
        }
        render = () => (
            <span className="draggable_wrapper" ref={e => (this.ELEMENT = e)}>
                <Wrapped  {...this.props} x={this.state.x} y={this.state.y} />
            </span>
        )
    }
    WithDrag.displayName = `WithDrag(${Wrapped.displayName || Wrapped.name})`
    return WithDrag;
}
const Mouse = () => (
    <span className="mouse" role="img">
        🐭
        {x !== undefined && 
            y !== undefined && (
                <span className="mouse__position"> {`(${x}, ${y})`} </span>
        )}
    </span>
)

現在 Mouse 組件會向用戶展示他的 XY 位置屬性 🤓

圖片描述

我們也可以給 HOC 傳遞 props。然後在傳遞的過程中過濾掉這些無用的屬性。舉個例子,傳遞一個 onDrag 回調函數。

const withDrag = Wrapped => {
    class WithDrag extends Component {
        componentDidMount = () => new Draggable(this.ELEMENT, { onDrag: this.props.onDrag })
        render = () => (
            const { onDrag, ...passed } = this.props;
            <span className="draggable__wrapper" ref={e => (this.ELEMENT = e)}>
                <Wrapped  {...passed} />
            </span>
        )
    }
    WithDrag.displayName = `WithDrag(${Wrapped.displayName || Wrapped.name})`
    return WithDrag;
}

class App extends Component {
    render = () => (
        <Fragment>
            <DraggableMouse
                onDrag={e => console.info(e.target.getBoundingClientRect())}
            />
        </Fragment>
    )
}

通過使用 HOC ,我們的組件仍然很簡單,複雜的邏輯都交給 HOC 來處理了。 我們的組件只關心傳遞給他們的內容。 我們可以在其他地方重複使用它們而且不會有可以被拖拽的屬性。這使得我們的應用更容易維護。

優秀的實踐 👍

  • 當出現重複的模式的時候,使用它們
  • 爲了方便調試,需要更新處理之後組件的 displayName
  • 傳遞與當前 HOC 無關的所有 props

糟糕的實踐 👎

  • 過度使用,其他模式可能會更加適合
  • 改變原始組件
  • render 方法中使用高階組件
譯註:永遠不要在 React render() 方法中定義 React 組件(甚至是無狀態組件)。React 在每次更新狀態的時候,都會廢棄舊的 html DOM 元素並將其替換爲全新的元素。比如在 render() 函數中定義一個輸入組件,元素被替換之後就會失去焦點,每次只能輸入一個字符。

注意 🙏

  • Refs 不會被傳遞
  • 務必複製靜態方法
  • 大部分 HOC 都可以和 render props 相互替換使用

這就是一篇關於高階組件的簡短介紹 ~

官方文檔:高階組件

本文已經聯繫原文作者,並授權翻譯,轉載請保留原文鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章