React高阶组件的使用
发布于 7 年前 作者 zhangmingkai4315 7208 次浏览 来自 分享

本片文章是关于react高阶组件的使用,发布在我个人的博客网站JSMEAN 欢迎大家访问,也欢迎大家留言提意见。

我们在使用redux的时候,一般会通过react-redux的connect来绑定react视图组件和redux的数据store. 调用connect的方式其实是将原来的普通的component转换为一个container,这就是高阶组件的一个典型的使用方式。

除了调用类似于connect的函数来产生高阶组件,我们有时候还需要自己去实现,比如针对认证页面的使用,用户如果需要登入系统之后才能访问资源,如何做到? 这里我们通过一个简单的例子来实现。

首先我们定义一个router来区分普通页面/home和需要授权资源页面/resources.以及一个简单的button用来切换用户状态(点击登陆和再次点击退出,不需要用户名和密码). 以下是router的设置,这里当我们切换视图的时候,是没有任何的保护的用户可以随意的切换。

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
    <Router history={browserHistory}>
      <Route path="/" component={App}>
        <Route path="/resources" component={Resources}/>
        <Route path="/home" component={Home}/>
      </Route>
    </Router>
  </Provider>
  , document.querySelector('.container'));

我们的几个标准组件如下,分别是用户包含所有组件的App组件,用户切换子页面的Header组件,和标准的home和resource组件。

// App.js
import React, { Component } from 'react';
import Header from './Header';
export default class App extends Component {
  render() {
    return (
      <div>
        <Header/>
        {this.props.children}
      </div>
    );
  }
}
-----------------------
// Home.js
import React, {Component} from 'react';
class Home extends Component {
    render () {
        return (
            <div>
                <h3>Home Url</h3>
            </div>
        )
    }
}
export default Home
-----------------------
// Resources.js
import React, {Component} from 'react';
class Resources extends Component {
    render () {
        return (
            <div>
                <h3>Protected Resource</h3>
            </div>
        )
    }
}
export default Resources

由于App组件在router中作为顶层的组件,所以我们的子组件通过{this.props.children}传递进来。当用户切换不同的url时候,传递不同的组件到App组件中。Header是一个react容器,通过react-redux来订阅store的数据变化,并传递action到容器中。使用Link来切换url,这里我们通过一个button来切换用户状态,而这个button每次点击的时候都会调用redux的actions(我们稍后实现)产生一个action后进入reducer来修改root state(或者其中一部分)。

mapStateToProps用来将redux store中的一部分状态数据传递到组件中,mapDispatchToProps将actions传递到当前组件的props中。

// Header.js
import React, { Component } from 'react';
import { Link } from 'react-router';
import * as authAction from '../actions/authActions';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
class Header extends Component {
    constructor(props,context){
        super(props);
        this.authButton= this.authButton.bind(this);
    }
    authButton(){
        console.log(this.props)
        if(this.props.auth){
            return (<button onClick={this.props.actions.authChange}>Logout</button>);
        }else{
            return (<button onClick={this.props.actions.authChange}>Sign in</button>);
        }
    }
    render(){
        return (
            <nav className="navbar navbar-light">
                <ul className="nav navbar-nav">
                    <li className="nav-item">
                        <Link to = "/home" >Home</Link>
                    </li>
                    <li className="nav-item">
                        <Link to = "/resources" >Resources</Link>
                    </li>
                    <li className="nav-item">
                        {this.authButton()}
                    </li>
                </ul>
            </nav>
        )
    }
}
function mapStateToProps(state){
    return {
        auth:state.auth
    };
}
function mapDispatchToProps (disptach){
  return {
    actions:bindActionCreators(authAction,disptach)
  };
}

export default connect(mapStateToProps,mapDispatchToProps)(Header);

ok, 上面的如果都可以理解的话,接下来我们就来看一下action 和 reducer的实现,由于我们只有一个行为,并不包含任何的payload所以实现的authChange只是发送一个包含type的对象,传递到我们reducers中进行判定,由于不管有多少reducers都会在此刻被调用,用于检查,所以其中的type 一定不要重复。

combineReducers将多个reducer绑定到一起,产生一个最终的reducer,其中auth就是最终落入state中的键,他的值将通过对应reducer来改变。

// authActions.js
import { CHANG_AUTH } from './actionTypes';

export function authChange(){
    return {
        type:CHANG_AUTH
    };
}
-----------------------
// authReducers.js
import {CHANG_AUTH} from '../actions/actionTypes';
export default (state=false,action)=>{
    if(action.type===CHANG_AUTH){
        return !state;
    }else{
        return state;
    }
}
-----------------------
// rootReducers.js
import { combineReducers } from 'redux';
import authReducer from './authReducer';
const rootReducer = combineReducers({
  auth: authReducer
});

export default rootReducer;

为了完成资源的认证访问,我们需要设计一个通用的方式处理,对于已经认证的用户,直接render受保护的组件,而非认证的用户则跳转到合适的位置,我们的最终版代码如下。 首先它是一个函数,这个函数的参数是一个普通的组件,我们在这个函数中,定义了一个基本的react组件, 并在挂载组件之前进行判定,如果是授权用户继续操作,而如果是非授权用户则通过context对象来完成url history的跳转() , componentWillUpdate则在任何状态发生变化的时候,进行操作,比如用户当前属于受保护的页面下,当他退出的时候,应该将其跳转,这时候组件会收到nextProps状态,而componentWillMount则是在组件挂载的时候才进行操作。

import React ,{ Component } from 'react';
import {connect} from 'react-redux';
export default (ComposedComponent)=>{

    class Authenticate extends Component {
        // ===> 等价于直接定义Authenticate.contextTypes的声明方式
        static contextTypes = {
            router:React.PropTypes.object
        }
        componentWillMount(){
            if(!this.props.auth){
                this.context.router.push('/')
            }
        }
        componentWillUpdate(nextProps){
            if(!nextProps.auth){
                this.context.router.push('/')
            }
        }
        render(){
            return <ComposedComponent {...this.props}/>
        }
    }
    function mapStateToProps(state){
        return {
            auth:state.auth
        }
    }
    return connect(mapStateToProps)(Authenticate);
}

最终我们可以通过这种认证的方式来管理用户的url访问,简单的替换原来route中的代码即可。

 <Route path="/resources" component={requireAuth(Resources)}/>

高阶组件的使用方式,是编写react应用的重要部分,特别是我们需要认证授权的时候,希望通过这个简单的例子来帮助理解如何编写高阶组件。

1 回复

云山雾罩

回到顶部