前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React 中的高阶组件及其应用场景

React 中的高阶组件及其应用场景

原创
作者头像
愤怒的小鸟
修改2021-01-21 10:53:21
1.3K0
修改2021-01-21 10:53:21
举报
文章被收录于专栏:web shareweb share

如果一个函数 接受一个或多个组件作为参数并且返回一个组件 就可称之为 高阶组件

一、高阶组件的特性 (主要有两种形式:属性代理反向继承)

1. 属性代理(Props Proxy)

最简单的属性代理实现:

代码语言:javascript
复制
// 无状态
function HigherOrderComponent(WrappedComponent) {
    return props => <WrappedComponent {...props} />;
}
// or
// 有状态
function HigherOrderComponent(WrappedComponent) {
    return class extends React.Component {
        render() {
            return <WrappedComponent {...this.props} />;
        }
    };
}

可以发现,属性代理其实就是 一个函数接受一个 WrappedComponent 组件作为参数传入,并返回一个继承了 React.Component 组件的类,且在该类的 render() 方法中返回被传入的 WrappedComponent 组件

那我们可以利用属性代理类型的高阶组件做一些什么呢?

因为属性代理类型的高阶组件返回的是一个标准的 React.Component 组件,所以在 React 标准组件中可以做什么,那在属性代理类型的高阶组件中就也可以做什么,比如:

  • 操作 props
  • 抽离 state
  • 通过 ref 访问到组件实例
  • 用其他元素包裹传入的组件 WrappedComponent

2. 反向继承(Inheritance Inversion)

最简单的反向继承实现:

代码语言:javascript
复制
function HigherOrderComponent(WrappedComponent) {
    return class extends WrappedComponent {
        render() {
            return super.render();
        }
    };
}

反向继承其实就是 一个函数接受一个 WrappedComponent 组件作为参数传入,并返回一个继承了该传入 WrappedComponent 组件的类,且在该类的 render() 方法中返回 super.render() 方法

会发现其属性代理和反向继承的实现有些类似的地方,都是返回一个继承了某个父类的子类,只不过属性代理中继承的是 React.Component,反向继承中继承的是传入的组件 WrappedComponent

反向继承可以用来做什么:

  • 操作 state
  • 渲染劫持(Render Highjacking)

渲染劫持

之所以称之为 渲染劫持 是因为高阶组件控制着 WrappedComponent 组件的渲染输出,通过渲染劫持我们可以:

  • 有条件地展示元素树(element tree
  • 操作由 render() 输出的 React 元素树
  • 在任何由 render() 输出的 React 元素中操作 props
  • 用其他元素包裹传入的组件 WrappedComponent (同 属性代理

条件渲染

通过 props.isLoading 这个条件来判断渲染哪个组件。

代码语言:javascript
复制
function withLoading(WrappedComponent) {
    return class extends WrappedComponent {
        render() {
            if(this.props.isLoading) {
                return <Loading />;
            } else {
                return super.render();
            }
        }
    };
}

修改由 render() 输出的 React 元素树

修改元素树:

代码语言:javascript
复制
function HigherOrderComponent(WrappedComponent) {
    return class extends WrappedComponent {
        render() {
            const tree = super.render();
            const newProps = {};
            if (tree && tree.type === 'input') {
                newProps.value = 'something here';
            }
            const props = {
                ...tree.props,
                ...newProps,
            };
            const newTree = React.cloneElement(tree, props, tree.props.children);
            return newTree;
        }
    };
}

二、高阶组件的应用场景

权限控制

利用高阶组件的 条件渲染 特性可以对页面进行权限控制,权限控制一般分为两个维度:页面级别页面元素级别,这里以页面级别来举一个栗子:

代码语言:javascript
复制
// HOC.js
function withAdminAuth(WrappedComponent) {
    return class extends React.Component {
        state = {
            isAdmin: false,
        }
        async componentWillMount() {
            const currentRole = await getCurrentUserRole();
            this.setState({
                isAdmin: currentRole === 'Admin',
            });
        }
        render() {
            if (this.state.isAdmin) {
                return <WrappedComponent {...this.props} />;
            } else {
                return (<div>您没有权限查看该页面,请联系管理员!</div>);
            }
        }
    };
}

然后是两个页面:

代码语言:javascript
复制
// pages/page-a.js
class PageA extends React.Component {
    constructor(props) {
        super(props);
        // something here...
    }
    componentWillMount() {
        // fetching data
    }
    render() {
        // render page with data
    }
}
export default withAdminAuth(PageA);

// pages/page-b.js
class PageB extends React.Component {
    constructor(props) {
        super(props);
        // something here...
    }
    componentWillMount() {
        // fetching data
    }
    render() {
        // render page with data
    }
}
export default withAdminAuth(PageB);

使用高阶组件对代码进行复用之后,可以非常方便的进行拓展,比如产品经理说,PageC 页面也要有 Admin 权限才能进入,我们只需要在 pages/page-c.js 中把返回的 PageC 嵌套一层 withAdminAuth 高阶组件就行,就像这样 withAdminAuth(PageC)。是不是非常完美!非常高效!!但是。。第二天产品经理又说,PageC 页面只要 VIP 权限就可以访问了。你三下五除二实现了一个高阶组件 withVIPAuth。第三天。。。

其实你还可以更高效的,就是在高阶组件之上再抽象一层,无需实现各种 withXXXAuth 高阶组件,因为这些高阶组件本身代码就是高度相似的,所以我们要做的就是实现一个 返回高阶组件的函数,把 变的部分(Admin、VIP) 抽离出来,保留 不变的部分,具体实现如下:

代码语言:javascript
复制
// HOC.js
const withAuth = role => WrappedComponent => {
    return class extends React.Component {
        state = {
            permission: false,
        }
        async componentWillMount() {
            const currentRole = await getCurrentUserRole();
            this.setState({
                permission: currentRole === role,
            });
        }
        render() {
            if (this.state.permission) {
                return <WrappedComponent {...this.props} />;
            } else {
                return (<div>您没有权限查看该页面,请联系管理员!</div>);
            }
        }
    };
}

可以发现经过对高阶组件再进行了一层抽象后,前面的 withAdminAuth 可以写成 withAuth('Admin') 了,如果此时需要 VIP 权限的话,只需在 withAuth 函数中传入 'VIP' 就可以了。

组件渲染性能追踪

借助父组件子组件生命周期规则捕获子组件的生命周期,可以方便的对某个组件的渲染时间进行记录:

代码语言:javascript
复制
class Home extends React.Component {
    render() {
        return (<h1>Hello World.</h1>);
    }
}
function withTiming(WrappedComponent) {
    return class extends WrappedComponent {
        constructor(props) {
            super(props);
            this.start = 0;
            this.end = 0;
        }
        componentWillMount() {
            super.componentWillMount && super.componentWillMount();
            this.start = Date.now();
        }
        componentDidMount() {
            super.componentDidMount && super.componentDidMount();
            this.end = Date.now();
            console.log(`${WrappedComponent.name} 组件渲染时间为 ${this.end - this.start} ms`);
        }
        render() {
            return super.render();
        }
    };
}

export default withTiming(Home);

withTiming 是利用 反向继承 实现的一个高阶组件,功能是计算被包裹组件(这里是 Home 组件)的渲染时间。

页面复用

假设我们有两个页面 pageApageB 分别渲染两个分类的电影列表,普通写法可能是这样:

代码语言:javascript
复制
// pages/page-a.js
class PageA extends React.Component {
    state = {
        movies: [],
    }
    // ...
    async componentWillMount() {
        const movies = await fetchMoviesByType('science-fiction');
        this.setState({
            movies,
        });
    }
    render() {
        return <MovieList movies={this.state.movies} />
    }
}
export default PageA;

// pages/page-b.js
class PageB extends React.Component {
    state = {
        movies: [],
    }
    // ...
    async componentWillMount() {
        const movies = await fetchMoviesByType('action');
        this.setState({
            movies,
        });
    }
    render() {
        return <MovieList movies={this.state.movies} />
    }
}
export default PageB;

页面少的时候可能没什么问题,但是假如随着业务的进展,需要上线的越来越多类型的电影,就会写很多的重复代码,所以我们需要重构一下:

代码语言:javascript
复制
const withFetching = fetching => WrappedComponent => {
    return class extends React.Component {
        state = {
            data: [],
        }
        async componentWillMount() {
            const data = await fetching();
            this.setState({
                data,
            });
        }
        render() {
            return <WrappedComponent data={this.state.data} {...this.props} />;
        }
    }
}

// pages/page-a.js
export default withFetching(fetching('science-fiction'))(MovieList);
// pages/page-b.js
export default withFetching(fetching('action'))(MovieList);
// pages/page-other.js
export default withFetching(fetching('some-other-type'))(MovieList);

会发现 withFetching 其实和前面的 withAuth 函数类似,把 变的部分(fetching(type)) 抽离到外部传入,从而实现页面的复用。

三、高阶组件存在的问题

  • 静态方法丢失
  • refs 属性不能透传
  • 反向继承不能保证完整的子组件树被解析

总结:React 中的 高阶组件 其实是一个非常简单的概念,但又非常实用。在实际的业务场景中合理的使用高阶组件,可以提高代码的复用性和灵活性

  • 高阶组件 不是组件 一个把某个组件转换成另一个组件的 函数
  • 高阶组件的主要作用是 代码复用
  • 高阶组件是 装饰器模式在 React 中的实现

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、高阶组件的特性 (主要有两种形式:属性代理和反向继承)
    • 1. 属性代理(Props Proxy)
      • 2. 反向继承(Inheritance Inversion)
        • 渲染劫持
        • 条件渲染
        • 修改由 render() 输出的 React 元素树
    • 二、高阶组件的应用场景
      • 权限控制
        • 组件渲染性能追踪
          • 页面复用
          • 三、高阶组件存在的问题
            • 总结:React 中的 高阶组件 其实是一个非常简单的概念,但又非常实用。在实际的业务场景中合理的使用高阶组件,可以提高代码的复用性和灵活性。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档