本片文章是关于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应用的重要部分,特别是我们需要认证授权的时候,希望通过这个简单的例子来帮助理解如何编写高阶组件。