react进阶系列:高阶组件详解(二)

高阶组件能够封装大众逻辑,给当前组件通报要领属性,增加生命周期钩子等。

案例:

一个项目中有的页面须要推断所处环境,假如在挪动端则一般显现页面,并向用户提醒当前页面所处的挪动端环境,假如不在挪动端则显现提醒让其在挪动端翻开。然则有的页面又不须要这个推断。

假如在每一个页面都写一段推断逻辑难免贫苦,因而能够借助高阶组件来处置惩罚这部份逻辑。

先建立一个高阶组件

// src/container/withEnvironment/index.jsx
import React from 'react';

const envs = {
    weixin: '微信',
    qq: 'QQ',
    baiduboxapp: '手机百度',
    weibo: '微博',
    other: '挪动端'
}

function withEnvironment(BasicComponent) {
    const ua = navigator.userAgent;
    const isMobile = 'ontouchstart' in document;
    let env = 'other';

    if (ua.match(/MicroMessenger/i)) {
        env = 'weixin';
    }
    if (ua.match(/weibo/i)) {
        env = 'weibo';
    }
    if (ua.match(/qq/i)) {
        env = 'qq';
    }
    if (ua.match(/baiduboxapp/i)) {
        env = 'baiduboxapp'
    }

    // 差别逻辑下返回差别的中心组件
    if (!isMobile) {
        return function () {
            return (
                <div>
                    <div>该页面只能在挪动端检察,请扫描下方二维码翻开。</div>
                    <div>假定这里有张二维码</div>
                </div>
            )    
        }
    }

    // 经由过程定义的中心组件将页面所处环境经由过程props通报给基本组件
    const C = props => (
        <BasicComponent {...props} env={env} envdesc={envs[env]} />
    )

    return C;
}


export default withEnvironment;

然后在基本组件中运用

// src/pages/Demo01/index.jsx
import React from 'react';
import withEnvironment from '../../container/withEnvironment';

function Demo01(props) {
    return (
        <div>你如今正在{props.envdesc}中接见该页面</div>
    )
}

export default withEnvironment(Demo01);

末了将基本组件衬着出来即可检察到效果。

// src/index.js
import React from 'react';
import { render } from 'react-dom';
import Demo01 from './pages/Demo01';

const root = document.querySelector('#root');
render(<Demo01 />, root);

在上面这个例子中,我们将环境推断的逻辑放在了高阶组件中处置惩罚,今后只需须要推断环境的页面只须要在基本组件中如许实行即可。

export default withEnvironment(Demo01);

除此之外,我们在现实开辟中还会碰到一个异常罕见的需求,那就是在进入一个页面时须要推断登录状况,登录状况与非登录状况的差别显现,登录状况以后角色的差别显现都能够经由过程高阶组件一致来处置惩罚这个逻辑,然后将登录状况,角色信息等通报给基本组件。

// 也许的处置惩罚逻辑
import React from 'react';
import $ from 'jquery';

// 假定已封装了一个叫做getCookie的要领猎取cookie
import { getCookie } from 'cookie';

function withRule(BasicComponent) {

    return class C extends React.Component {
        state = {
            islogin: false,
            rule: -1,
            loading: true,
            error: null
        }

        componentDidMount() {
            // 假如能直接在cookie中找到uid,申明已登录过并保留了相干信息
            if (getCookie('uid')) {
                this.setState({
                    islogin: true,
                    rule: getCookie('rule') || 0,
                    loading: false
                })
            } else {
                // 假如找不到uid,则尝试自动登录,先从kookie中查找是不是保留了登录账号与暗码
                const userinfo = getCookie('userinfo');
                if (userinfo) {
                    // 挪用登录接口
                    $.post('/api/login', {
                        username: userinfo.username,
                        password: userinfo.password
                    }).then(resp => {
                        this.setState({
                            islogin: true,
                            rule: resp.rule,
                            islogin: false
                        })
                    }).catch(err => this.setState({ error: err.message }))
                } else {
                    // 当没法自动登录时,你能够挑选在这里弹出登录框,或许直接显现未登录页面的款式等都能够
                }
            }
        }

        render() {
            const { islogin, rule, loading, error } = this.state;

            if (error) {
                return (
                    <div>登录接口要求失利!错误信息为:{error}</div>
                )
            }

            if (loading) {
                return (
                    <div>页面加载中, 请稍后...</div>
                )
            }

            return (
                <BasicComponent {...props} islogin={islogin} rule={rule} />
            )
        }
    }
}

export default withRule;

与第一个例子比拟,这个例子越发靠近现实运用而且逻辑也更越发庞杂。因而触及到了异步数据,因而最好的体式格局是在中心组件的componentDidMount中来处置惩罚逻辑。并在render中依据差别的状况决议差别的衬着效果。

我们须要依据现实状况合理的运用react建立组件的两种体式格局。这一点至关重要。上面两个例子个人认为照样比较典范的能代表大多数状况。

react-router中的高阶组件

我们在进修react的过程当中,会逐步的与高阶组件打交道,react-router 中的 withRouter应当算是会最早接触到的高阶组件。我们在运用的时刻就晓得,经由过程withRouter包装的组件,我们能够在props中接见到location, router等对象,这正是withRouter经由过程高阶组件的体式格局通报过来的。

import React, { Component } from 'react';
import { withRouter } from 'react-router';

class Home extends Component {
    componentDidMount() {
        const { router } = this.props;

        router.push('/');
    }
    render() {
        return (
            <div className="my-home">...</div>
        )
    }
}
export default withRouter(Home);

我们能够来看看在react-router v4withRouter的源码。

import React from 'react';
import PropTypes from 'prop-types';
import hoistStatics from 'hoist-non-react-statics';
import Route from './Route';

// 传入基本组件作为参数
const withRouter = (Component) => {

    // 建立中心组件
    const C = (props) => {
        const { wrappedComponentRef, ...remainingProps } = props;
        return (
            <Route render={routeComponentProps => (
                // wrappedComponentRef 用来处理高阶组件没法准确猎取到ref的题目
                <Component {...remainingProps} {...routeComponentProps} ref={wrappedComponentRef}/>
            )}/>
        )
    }

    C.displayName = `withRouter(${Component.displayName || Component.name})`;
    C.WrappedComponent = Component;
    C.propTypes = {
        wrappedComponentRef: PropTypes.func
    }

    // hoistStatics类似于Object.assign,用于处理基本组件由于高阶组件的包裹而丧失静态要领的题目
    return hoistStatics(C, Component);
}

export default withRouter;

假如关于高阶组件的例子你已熟知,那末withRouter的源码实在很轻易明白。它做所的事情就仅仅只是把routeComponentProps传入基本组件罢了。

别的还须要注意点是在该源码中,处理了两个由于高阶组件带来的题目,一个是经由高阶组件包裹的组件在运用时没法经由过程ref准确猎取到对应的值。二是基本组件的静态要领也会由于高阶组件的包裹会丧失。不过幸亏这段源码已给我们供应了对应的处理方案。因而假如我们在运用中须要处置惩罚这2点的话,根据这里的体式格局来做就能够了。

然则通常状况下,我们也很少会在自定义的组件中增加静态要领和运用ref。假如在开辟中确切碰到了必需运用它们,就肯定要注意高阶组件的这2个题目并仔细处理。

《react进阶系列:高阶组件详解(二)》

    原文作者:九死蚕传人bo
    原文地址: https://segmentfault.com/a/1190000009948787
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞