[译] 理解 React 中的高阶组件

栏目: 服务器 · 发布时间: 4年前

内容简介:一篇关于 React 高阶组件的完整指南在我的上一篇文章中,我们讨论了 React 中的类型检查(type-checking);我们了解了如何在 React 组件中指定 props 的类型,尽管是在用 JS 编写代码。在这篇文章中,继续研究 React,我们将学习 React 中的 HOC。

一篇关于 React 高阶组件的完整指南

在我的上一篇文章中,我们讨论了 React 中的类型检查(type-checking);我们了解了如何在 React 组件中指定 props 的类型,尽管是在用 JS 编写代码。

在这篇文章中,继续研究 React,我们将学习 React 中的 HOC。

高阶组件(HOC)是什么?

HOC 是 React 中的一种进阶用法,函数(组件)接收组件作为参数并返回一个新的组件。

function composeComponent(Component) {
    return class extends React.Component {
        render() {
            return <Component />
        }
    }

}
复制代码

在这里,函数 composeComponent 接收了一个 Component 变量作为参数并返回一个 ES6 class 定义的组件。返回的 class 组件中使用了参数中的 Component 变量。 Component 参数会是一个 React 组件,它将被返回的 class 组件调用。

例如:

class CatComponent extends React.Component {
    render() {
        return <div>Cat Component</div>
    }
}
复制代码

我们有一个 CatComponent 组件,渲染结果如下:

Cat Component
复制代码

我们可以将 CatComponet 作为参数传递给 composeComponent 函数得到另一个组件:

const composedCatComponent = composeComponent(CatComponent)
复制代码

composedCatComponent 组件也能够被渲染:

<composedCatComponent />
复制代码

渲染结果如下:

Cat Component
复制代码

这和 JS 中的高阶函数是类似的。

高阶函数

高阶函数是 JS 中的一种模式,函数接收一个函数作为参数并返回另一个函数作为结果。因为 JS 本身的语法特性使得这是可行的。这意味着以下类型的数据:

  • objects
  • arrays
  • strings
  • numbers
  • boolean
  • functions

都可以作为参数传递给函数,也可以从函数中返回。

function mul(x) {
    return (y) => {
        return x * y
    }
}
const mulTwo = mul(2)

mulTwo(2) // 4
mulTwo(3) // 9
mulTwo(4) // 8
mulTwo(5) // 10
复制代码

mul 函数返回了一个函数,该函数在闭包中捕获变量 x 的值。现在,返回的函数可以使用这个 xmul 现在就是一个高阶函数,因为它返回了一个函数。这意味着我们可以调用它通过传递不同的参数来构造其它更具体的函数。

我们可以用它来创建一个函数,返回参数的 3 倍:

function mul(x) {
    return (y) => {
        return x * y
    }
}
const triple = mul(3)

triple(2) // 6
triple(3) // 9
triple(4) // 12
triple(5) // 15
复制代码

**那高阶函数和高阶组件有什么好处呢?**当我们发现自己一遍又一遍地重复相同的逻辑时。我们需要找到一种方法把相同的逻辑封装在一起,然后从那里调用它。高阶函数就提供了一个我们可以用来实现它的模式。

从上面的例子中,如果在我们的程序中需要多次乘以 3,我们就可以创建一个函数,返回另一个乘以参数 3 倍的函数,所以每当我们需要进行 3 倍乘法 时,我们可以简单地调用通过传递参数 3 获得的 triple 函数。

使用高阶组件(HOC)

所以,在 React 中使用高阶组件又有什么好处呢?

同样,我们在 React 项目的编程过程中,也可能会发现自己一次又一次地重复相同的逻辑。

例如,我们有一个应用程序是用来查看和编辑文档的。我们希望对应用程序的用户进行身份验证,这样只有经过身份验证的用户才能访问主页、编辑文档、查看文档或删除文档。我们的路由是这样设计的:

<Route path="/" component={App}>
    <Route path="/dashboard" component={Documents}/>
    <Route path="document/:id/view" component={ViewDocument} />
    <Route path="documents/:id/delete" component={DelDocument} />
    <Route path="documents/:id/edit" component={EditDocument}/>
</Route>
复制代码

我们必须在 Documents 组件中进行验证,这样只有通过验证的用户才能访问它。如下:

class Doucments extends React.Component {
    componentwillMount() {
        if(!this.props.isAuth){
            this.context.router.push("/")
        }
    }
    componentWillUpdate(nextProps) {
        if(!nextProps.isAuth) {
            this.context.router.push("/")            
        }
    }
    render() {
        return <div>Documents Paegs!!!</div>
    }
}

function mapstateToProps(state) {
    isAuth: state.auth
}
export default connect(mapStateToProps)(Documents)
复制代码

state.auth 保存了用户的验证状态。如果用户没有通过验证,它的值是 false ,如果通过验证,它将是 trueconnect 函数将 state.auth 映射到组件 props 对象的 isAuth 。然后,当组件将要挂载到 DOM 上时, componentWillMount 被触发,因此我们检查 propsisAuth 是否为真。如果为真,组件会继续渲染;否则,则该方法会将路由切换到 “/” 路径,从而使得我们的浏览器在渲染 Documents 组件时被重定向到了首页,进而有效地阻止了未通过验证的用户对它的访问。

当组件在初始渲染之后再次渲染时,我们只在 componentWillUpdate 中执行相同的操作,以检查用户是否仍然具有授权,如果没有,则同样重定向到首页。

然后,我们可以在 ViewDocument 组件中做同样的处理:

class ViewDoucment extends React.Component {
    componentwillMount() {
        if(!this.props.isAuth){
            this.context.router.push("/")
        }
    }
    componentWillUpdate(nextProps) {
        if(!nextProps.isAuth) {
            this.context.router.push("/")            
        }
    }
    render() {
        return <div>View Document Page!!!</div>
    }
}

function mapstateToProps(state) {
    isAuth: state.auth
}
export default connect(mapStateToProps)(ViewDocument)
复制代码

EditDocument 组件中:

class EditDocument extends React.Component {
    componentwillMount() {
        if(!this.props.isAuth){
            this.context.router.push("/")
        }
    }
    componentWillUpdate(nextProps) {
        if(!nextProps.isAuth) {
            this.context.router.push("/")            
        }
    }
    render() {
        return <div>Edit Document Page!!!</div>
    }
}

function mapstateToProps(state) {
    isAuth: state.auth
}
export default connect(mapStateToProps)(EditDocument)
复制代码

DelDocument 组件中:

class DelDocument extends React.Component {
    componentwillMount() {
        if(!this.props.isAuth){
            this.context.router.push("/")
        }
    }
    componentWillUpdate(nextProps) {
        if(!nextProps.isAuth) {
            this.context.router.push("/")            
        }
    }
    render() {
        return <div>Delete Document Page!!!</div>
    }
}

function mapstateToProps(state) {
    isAuth: state.auth
}
export default connect(mapStateToProps)(DelDocument)
复制代码

不同的页面具有不同的功能,但它们的大部分实现逻辑是相同的。

在每个组件中的操作:

  • 通过 react-redux 连接到 store 的 state。
  • state.auth 映射到组件的 props.isAuth 属性。
  • componentWillMount 中检查用户是否授权。
  • componentWillUpdate 中检查用户是否授权。

假设我们的项目扩展了更多其他的组件,我们发现我们在每个组件中都实现了上述的操作。这肯定会很无聊的。

我们需要找到只在一个地方实现逻辑的方式。最好的办法就是使用高阶组件(HOC)。

为此,我们将所有的逻辑封装到一个函数中,该函数将返回一个组件:

function requireAuthentication(composedComponent) {
    class Authentication extends React.Component {
        componentwillMount() {
            if(!this.props.isAuth){
                this.context.router.push("/")
            }
        }
        componentWillUpdate(nextProps) {
            if(!nextProps.isAuth) {
                this.context.router.push("/")            
            }
        }
        render() {
            <ComposedComponent />
        }
    }
    function mapstateToProps(state) {
        isAuth: state.auth
    }
    return connect(mapStateToProps)(Authentication)
}
复制代码

可以看到,我们将所有相同的逻辑都封装到了 Authentication 组件中。 requireAuthentication 函数将把 Authentication 组件连接到 store 并返回它。然后, Authentication 组件将渲染通过 composedCompoennt 参数传入的组件。

我们的路由现在这样的:

<Route path="/" component={App}>
    <Route path="/dashboard" component={requireAuthentication(Documents)}/>
    <Route path="document/:id/view" component={requireAuthentication(ViewDocument)} />
    <Route path="documents/:id/delete" component={requireAuthentication(DelDocument)} />
    <Route path="documents/:id/edit" component={requireAuthentication(EditDocument)}/>
</Route>
复制代码

因此,无论我们的应用程序将来会有多少条路由,我们都不用考虑向组件添加身份验证的逻辑,我们只需调用 requireAuthentication 函数,并将组件作为参数传递给它。

使用高阶组件(HOC)会有很多的好处。当你发现你在重复相同的逻辑时,你需要把相同的逻辑封装到一起,并使用高阶组件(HOC)。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java编程思想

Java编程思想

[美] Bruce Eckel / 陈昊鹏、饶若楠 / 机械工业出版社 / 2005-9 / 95.00元

本书赢得了全球程序员的广泛赞誉,即使是最晦涩的概念,在Bruce Eckel的文字亲和力和小而直接的编程示例面前也会化解于无形。从Java的基础语法到最高级特性(深入的面向对象概念、多线程、自动项目构建、单元测试和调试等),本书都能逐步指导你轻松掌握。 从本书获得的各项大奖以及来自世界各地的读者评论中,不难看出这是一本经典之作。本书的作者拥有多年教学经验,对C、C++以及Java语言都有独到......一起来看看 《Java编程思想》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具