React,1702H搭建架构,包裹redux、路由、api、拦截器、国际化

一、使用到的库

1.react-redux库,使用了两个api

Provider组件:

import { Provider } from 'react-redux';

ReactDOM.render(
  <Provider store={Store}>
    <BrowserRouter>
      <Routers />
    </BrowserRouter>
  </Provider>
  , document.getElementById('root'));

connect():

const mapStateToProps = (state) => {
  return {
    count: state.getIn(['task', 'count']) 
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    TASK_CHANGAE_STATE: (key, value) => {
      dispatch({ type: 'TASK_CHANGAE_STATE', key, value });
    }    
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Login))

参考链接:https://www.jianshu.com/p/81e9e9eaf8fa

2.redux-immutable库

combineReducers():

redux-immutable提供一个combineReducers()函数,将store中最外层的reducer中的state转化为immutable对象(这里涉及到reducer的拆分,拆分用到了与redux中同名的combineReducers()方法)。combineReducers() 将多个 reducer 合并成为一个。

import { combineReducers } from 'redux-immutable';
import { createStore, applyMiddleware, compose } from 'redux';
import reducers from './reducers';

const reducer = combineReducers({ ...reducers });
const store = createStore(reducer);

export default store;

import { reducer as task } from './task';
export default {
        task
};

参考链接:https://blog.csdn.net/weixin_39786582/article/details/82623353

3.redux库

createStore()。创建store,第一个参数是reducer。

createStore(reducer, [preloadedState], [enhancer])

https://redux.js.org/api/createstore

compose() 。字面意思:组成,构成(一个整体); 使用compose可以增强store。例如添加中间件。

https://redux.js.org/api/compose

Enhancers。字面意思:增强剂。

applyMiddleware()。应用中间件

https://redux.js.org/api/applymiddleware

redux-devtools-extension无法使用的解决办法:

https://github.com/zalmoxisus/redux-devtools-extension#usage

import { combineReducers } from 'redux-immutable';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducers from './reducers';

const composeEnhancers =
  typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
    : compose;

const enhancer = composeEnhancers(applyMiddleware(thunk));
const reducer = combineReducers({ ...reducers });
const store = createStore(reducer, enhancer);

export default store;

4.redux-thunk库

redux的中间件,使用后dispatch可以接收一个函数作为参数。使用前只能接收一个对象作为参数

https://www.npmjs.com/package/redux-thunk

5.immutable库

immutabble字面意思:不可改变的;永恒不变的

mutabble字面意思:可变的;会变的

官网:https://immutable-js.github.io/immutable-js/

用的的api:

formJS:把js对象转换成immutable对象

import { fromJS } from 'immutable';

const defaultState = fromJS({
  count: 0,
})

formJS() 、setIn() 、getIn()、toJS()

// 原来的写法
var foo = {a: {b: 1}};
var bar = foo;
bar.a.b = 2;
console.log(foo.a.b);  // 打印 2
console.log(foo === bar);  //  打印 true
 
// 使用 immutable.js 后
var foo = Immutable.fromJS({a: {b: 1}});
var bar = foo.setIn(['a', 'b'], 2);   // 使用 setIn 赋值
console.log(foo.getIn(['a', 'b']));  // 使用 getIn 取值,打印 1
console.log(foo === bar);  //  打印 false     
console.log(bar.getIn(['a', 'b']))  //2
console.log(bar.toJS())  //转变成js对象

reducer.js文件示例:

import actions from './actionTypes';
import { fromJS } from 'immutable';

const defaultState = fromJS({
  count: 0,
})

export default (state = defaultState, action) => {
  switch (action.type) {
    case actions.TASK_CHANGAE_STATE: {
      if (typeof action.value !== 'undefined') {
        return state.setIn(action.key, action.value);
      } else {
        return state
      }
    }       
    case actions.TASK_CHANGAE_STATE_FORMJS: {
      if (typeof action.value !== 'undefined') {
        return state.setIn(action.key, fromJS(action.value));
      } else {
        return state
      }
    }
    default:
      return state;
  }
};

actionTypes.js文件示例:

export default {
        TASK_CHANGAE_STATE: 'TASK_CHANGAE_STATE',
        TASK_CHANGAE_STATE_FORMJS: 'TASK_CHANGAE_STATE_FORMJS'
};

我写的小demo:https://blog.csdn.net/xutongbao/article/details/81331179

6.react-router-dom库

常用api有:

BrowserRouter:包裹根组件

Switch: 有<Switch>标签,则其中的<Route>在路径相同的情况下,只匹配第一个,这个可以避免重复匹配

Route:路由

Link :链接

withRouter:包裹组件,包裹后组件的props会增加三个属性: match, location, history

1)通过js跳转路由:this.props.history.push('/tasklist')

2)获取动态路由参数

let { match } = this.props

if (match.params.new === 'new') {

}

3)获取路径名:<div>{this.props.location.pathname}</div>

官方文档:https://reacttraining.com/react-router/web/api/BrowserRouter

index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import Routers from './router/index.js';
import { BrowserRouter } from 'react-router-dom';  //路由
import { Provider } from 'react-redux';
import Store from './store/index.js';
import 'antd/dist/antd.css';
import './index.css';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
  <Provider store={Store}>
    <BrowserRouter>
      <Routers />
    </BrowserRouter>
  </Provider>
  , document.getElementById('root'));

router/index.js:

import React from 'react';
import { Switch, Route } from 'react-router-dom';
import config from "./config.js"

//路由组件
class Routers extends React.Component {
  render() {
    let router = this.renderSingleRoute()
    return (
      <Switch>
        {router}
      </Switch>
    );
  }
}

Object.assign(Routers.prototype, {
  renderSingleRoute() {
    return config.routerSingle.map((item) => {
      return <Route key={item.path} exact={item.exact}  path={item.path} component={item.component} />
    });
  }  
})

export default Routers

config.js:

import React, {lazy, Suspense } from 'react';
import Login from '../pages/login/Login.js'
const List = lazy(() => import('../pages/list/List.js'))

const routerSingle = [
  {
    path: '/',
    exact: true,
    component: () => (
      <Suspense fallback={'loading...'}>
        <Login />
      </Suspense>
    )
  },
  {
    path: '/login',
    exact: true,
    component: () => (
      <Suspense fallback={'loading...'}>
        <Login />
      </Suspense>
    )
  },
  {
    path: '/list',
    exact: true,
    component: () => (
      <Suspense fallback={'loading...'}>
        <List />
      </Suspense>
    )
  }
];

export default {
  routerSingle
}

首先加上了Switch,其次加入了exact。加入Switch表示,只显示一个组件。加exact表示精确匹配/

嵌套路由,从广义上来说,分为两种情况:一种是每个路由到的组件都有共有的内容,这时把共有的内容抽离成一个组件,变化的内容也是一个组件,两种组件组合嵌套,形成一个新的组件。另一种是子路由,路由到的组件内部还有路由。

对于共有的内容,典型的代表就是网页的侧边栏,假设侧边栏在左边,我们点击其中的按钮时,右侧的内容会变化,但不管右侧的内容怎么变化,左侧的侧边栏始终存在。

嵌套路由的示例:

import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';

const Home = lazy(() => import('./Home'));
const Bar = lazy(() => import('./Bar'));
const FileUpload = lazy(() => import('./FileUpload'));
const Banner = lazy(() => import('./Banner'));

const App = () => (
  <Router>
    <Suspense fallback={<div>loading</div>}>
      <div>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/bar">Bar</Link></li>
          <li><Link to="/management/file_upload">后台管理</Link></li>
        </ul>
      </div>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/bar" component={Bar} />
        <Route>
          <div>
            <div>header</div>
            <ul>
              <li><Link to="/management/file_upload">文件上传</Link></li>
              <li><Link to="/management/banner">Banner</Link> </li>
            </ul>                   
            <Route exact path="/management/file_upload" component={FileUpload}></Route>
            <Route exact path="/management/banner" component={Banner}></Route>
          </div>
        </Route>
      </Switch>
    </Suspense>
  </Router>
)

export default App;
  renderManagement() {
    return (
      <Route>
        <div>
          <Header/>
          <div className="m-management">
            <div className="m-sidebar">
              {
                config.routerManagement.map((item) => {
                  return <NavLink key={item.path} to={item.path} className="m-management-link" activeClassName="active">{item.text}</NavLink>
                })
              }            
            </div>
            <div className="m-content-wrap">
              {
                config.routerManagement.map((item) => {
                  return <Route key={item.path} exact={item.exact} path={item.path} component={item.component}></Route>
                })
              }
            </div>
          </div>
        </div>
      </Route>
    )
  }

7.react库

代码分割用的两个api:lazy方法和Suspense组件

参考链接:https://blog.csdn.net/xutongbao/article/details/84822315

8.axios库

拦截器:

import axios from 'axios'

axios.interceptors.request.use(
    (config) => {
        config.headers['token'] = localStorage.getItem('token')
        return config
    },
    (error) => {
        return Promise.reject(error.response);
    }
)

axios.interceptors.response.use(
    (response) => {
        if (response.data.code === 200) {
            return Promise.resolve(response)
        } else if (response.data.code === 400) {
            alert(response.data.message)
        } else if (response.data.code === 403) {
            console.log('登陆过期')
            window.location.href = '/login'
        }
        return Promise.reject(response);
    },
    (error) => {
        return Promise.reject(error.response);
    }    
)

api.js:

import axios from 'axios';

axios.defaults.baseURL = 'http://localhost:8888'

export default async (config) => {
        try {
                const response = await axios(config);
                if (response) {
                        const responseJSON = response.data;
                        return responseJSON;                    
                } else {
                        return {}
                }
        } catch (err) {
                throw err;
        }
};

index.js:

import * as urls from './url';
import common from './api';

export default {
    login: (data) => common({url: urls.login, method: 'post', data}),
    captcha: () => common({url: urls.captcha, method: 'get'}),
    register: (data) => common({url: urls.register, method: 'post', data}),
    forgotPassword: (url) => common({url: urls.forgotPassword + url, method: 'get'}),
    resetPassword: (data) => common({url: urls.resetPassword, method: 'post', data}),
    getList: (url) => common({url: urls.getList + url, method: 'get'}),
    deleteItem: (data) => common({url: urls.deleteItem, method: 'post', data}),
    addItem:(data) => common({url: urls.addItem, method: 'post', data})
}

url.js:

export const login = '/login';
export const captcha = '/captcha'
export const register = '/register'
export const forgotPassword = '/forgot_password'
export const resetPassword = '/reset_password'
export const getList = '/getlist'
export const deleteItem = '/deleteItem'
export const addItem = '/addItem'

keyCode.js:

export const SUCCESS = 200;  // 成功
export const ERROR = 400;  // 基础错误码
export const PARAMS_ERR = 401;  // 请求参数错误
export const PERMISSION_ERR = 402;  // 无权限
export const AUTH_ERR = 403;  // TOKEN失效 无权限
export const DATA_ERR = 404;  // 无数据
export const NO_ACCESS = 427;  // 无数据
export const FORMA_ERR = 500; // 格式错误

9.antd库

在入口index.js处引入样式:

import 'antd/dist/antd.css';

在页面里引入需要使用的组件:

import { Button, Input } from 'antd';

input受控组件示例:

import React from 'react';
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { Button, Input } from 'antd';
import Api from '../../api/index.js'
import * as keyCode from '../../api/keyCode.js'

class Login extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      username: '',
      password: ''
    }
  }
  render() {
    let { count } = this.props
    let {
      username,
      password,
    } = this.state
    return (
      <div>
        登录{count}<button onClick={this.handleBtn.bind(this)}>按钮</button>
        <Input placeholder="请输入用户名" value={username} onChange={this.handleInput.bind(this, 'username')}></Input>
        <Input placeholder="请输入密码" value={password} onChange={this.handleInput.bind(this, 'password')}></Input>
        <Button onClick={this.handleLogin.bind(this)}>按钮</Button>
      </div>
    );
  }
}

Object.assign(Login.prototype, {
  handleBtn() {
    let {count} = this.props
    count = count + 1
    this.props.TASK_CHANGAE_STATE(['count'], count)
  },
  handleLogin() {
    let {username, password} = this.state
    console.log(username,password)
    let data = {
      username,
      password
    }
    this.props.history.push('/list')
    Api.login(data).then((res) => {
      
    })
  }
})

Object.assign(Login.prototype, {
  handleInput(field, e) {
    this.setState({
      [field]: e.target.value
    })
  }
})

const mapStateToProps = (state) => {
  return {
    count: state.getIn(['task', 'count']) 
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    TASK_CHANGAE_STATE: (key, value) => {
      dispatch({ type: 'TASK_CHANGAE_STATE', key, value });
    }    
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Login))

10.react-intl-universal

多语言、国际化

示例代码:

1)初始化:

  languageInit() {
    let currentLocale = localStorage.getItem('language') || 'zh-CN'
    intl.init({
      currentLocale: currentLocale,    
      locales: {
        [currentLocale]: require(`../../i18n/${currentLocale}`).default
      }
    }).then(() => {
    }) 
    if (!localStorage.getItem('language')) {
      localStorage.setItem('language', 'zh-CN')
      this.setState({
        language: 'zh-CN'
      })
    } else {
      this.setState({
        language: currentLocale
      })      
    } 
  }

2)语言文件zh-CN.js:

export default ({
  language: 'zh',
  login: {
    loginTitle: '后台管理系统',
    login: '登录',
    usernamePlaceholder: '请输入用户名',
    passwordPlaceholder: '请输入密码',
    catpchaPlaceholder: '请输入验证码',
    userRegister: '用户注册',
    forgotPassword: '忘记密码'
  }
})

3)在dom中使用:

        <div className="m-login-title">
          {intl.get('login.loginTitle')}
        </div>

4)切换语言:

  handleLanguage(language) {
    this.setState({
      language
    })
    localStorage.setItem('language', language);
    window.location.reload()     
  },

参考链接:https://www.npmjs.com/package/react-intl-universal

二、制作页面

1.登录页

1)动态渲染html:

          <span 
            className="m-captcha" 
            dangerouslySetInnerHTML={{__html: captchaSvg}}
            onClick={this.getCaptcha.bind(this)}
            >
          </span> 

2)受控组件:

<Input 
    placeholder={intl.get('login.passwordPlaceholder')} 
    type="password" 
    value={password} 
    onChange={this.handleInput.bind(this, 'password')}>
</Input>



//受控组件
Object.assign(Login.prototype, {
  handleInput(field, e) {
    this.setState({
      [field]: e.target.value
    })
  }
})

3)input自动获取焦点:

         <Input 
            placeholder={intl.get('login.usernamePlaceholder')} 
            value={username} 
            ref={(input) => this.userNameInput = input}
            onChange={this.handleInput.bind(this, 'username')}></Input>



this.userNameInput.focus()

4)点击enter触发事件:

          <Input 
            className="m-login-input-catpcha" 
            placeholder={intl.get('login.catpchaPlaceholder')} 
            value={captcha}
            ref={(input) => this.captchaInput = input}
            onKeyDown={this.handleEnter.bind(this)} 
            onChange={this.handleInput.bind(this, 'captcha')}></Input> 


 handleEnter(e) {
                if (e.keyCode === 13) {
                        this.handleLogin()
                        this.captchaInput.blur()
                }    
  }

5)

import React from 'react';
import { connect } from 'react-redux'
import { withRouter, Link } from 'react-router-dom'
import { Button, Input, Select } from 'antd';
import { jsEncrypt } from '../../utils/index.js'
import intl from 'react-intl-universal'
import Api from '../../api/index.js'
import * as keyCode from '../../api/keyCode.js'
import './index.css'

const { Option } = Select
class Login extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      username: '',
      password: '',
      captcha: '',
      captchaSvg: '',
      language: 'zh-CN'
    }
  }
  render() {
    let { count } = this.props
    let {
      username,
      password,
      captcha,
      captchaSvg,
      language,
    } = this.state
    return (
      <div className="m-login">
        <div className="m-language">
          <Select value={language} onChange={this.handleLanguage.bind(this)}>
            <Option value="zh-CN">简体中文</Option>
            <Option value="en-US">Englist</Option>
            <Option value="zh-TW">繁體中文</Option>
          </Select>
        </div>
        <div className="m-login-title">
          {intl.get('login.loginTitle')}
        </div>
        <div className="m-login-row">
          <Input 
            placeholder={intl.get('login.usernamePlaceholder')} 
            value={username} 
            ref={(input) => this.userNameInput = input}
            onChange={this.handleInput.bind(this, 'username')}></Input>
        </div>
        <div className="m-login-row">
          <Input placeholder={intl.get('login.passwordPlaceholder')} type="password" value={password} onChange={this.handleInput.bind(this, 'password')}></Input>
        </div>
        <div className="m-login-row">
          <Input 
            className="m-login-input-catpcha" 
            placeholder={intl.get('login.catpchaPlaceholder')} 
            value={captcha}
            ref={(input) => this.captchaInput = input}
            onKeyDown={this.handleEnter.bind(this)} 
            onChange={this.handleInput.bind(this, 'captcha')}></Input>
          <span 
            className="m-captcha" 
            dangerouslySetInnerHTML={{__html: captchaSvg}}
            onClick={this.getCaptcha.bind(this)}
            >
          </span>          
        </div>
        <div className="m-login-row">
          <Button onClick={this.handleLogin.bind(this)}>{intl.get('login.login')}</Button>
        </div>
        <div className="m-login-row">
          <Link to="/register" className="m-link">{intl.get('login.userRegister')}</Link>
          <Link to="/forgot_password" className="m-link">{intl.get('login.forgotPassword')}</Link>
        </div>
        <div>
          {intl.get('login.login')}{count}<button onClick={this.handleBtn.bind(this)}>按钮</button>
        </div>        
                        </div>
    );
  }
}

//生命周期
Object.assign(Login.prototype, {
  componentDidMount() {
    this.userNameInput.focus()
    this.getCaptcha()
    this.languageInit()
  },
  languageInit() {
    let currentLocale = localStorage.getItem('language') || 'zh-CN'
    intl.init({
      currentLocale: currentLocale,    
      locales: {
        [currentLocale]: require(`../../i18n/${currentLocale}`).default
      }
    }).then(() => {
    }) 
    if (!localStorage.getItem('language')) {
      localStorage.setItem('language', 'zh-CN')
      this.setState({
        language: 'zh-CN'
      })
    } else {
      this.setState({
        language: currentLocale
      })      
    } 
  }
})

//事件
Object.assign(Login.prototype, {
  handleBtn() {
    let {count} = this.props
    count = count + 1
    this.props.TASK_CHANGAE_STATE(['count'], count)
  },
  handleLogin() {
    let {username, password, captcha} = this.state
    console.log(username,password)
    let data = {
      username,
      password: jsEncrypt(password),
      captcha
    }
    
    Api.login(data).then((res) => {
      if (res.code === keyCode.SUCCESS) {
        console.log(res)
        localStorage.setItem('token', res.data.token)
        localStorage.setItem('username', res.data.username)
        //this.props.history.push('/list')
        this.props.history.push('/management/file_upload')
      } else {
        this.getCaptcha()
      }
    }).catch((e) => {
      this.getCaptcha()
    })
  },
  getCaptcha() {
    Api.captcha().then((res) => {
      console.log(res)
      if (res.code === keyCode.SUCCESS) {
        localStorage.setItem('token', res.data.captchaId)
        this.setState({
          captchaSvg: res.data.captcha
        })
      }
    })
  },
  handleLanguage(language) {
    this.setState({
      language
    })
    localStorage.setItem('language', language);
    window.location.reload()     
  },
  handleEnter(e) {
                if (e.keyCode === 13) {
                        this.handleLogin()
                        this.captchaInput.blur()
                }    
  }
})

//受控组件
Object.assign(Login.prototype, {
  handleInput(field, e) {
    this.setState({
      [field]: e.target.value
    })
  }
})

const mapStateToProps = (state) => {
  return {
    count: state.getIn(['task', 'count']) 
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    TASK_CHANGAE_STATE: (key, value) => {
      dispatch({ type: 'TASK_CHANGAE_STATE', key, value });
    }    
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Login))

注册页:

import React from 'react';
import { withRouter } from 'react-router-dom'
import { Button, Input, message } from 'antd';
import { jsEncrypt } from '../../utils/index.js'
import Api from '../../api/index.js'
import * as keyCode from '../../api/keyCode.js'
import './index.css'

class Register extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      username: '',
      password: '',
      email: '',
    }
  }
  render() {
    let { count } = this.props
    let {
      username,
      password,
      email,
    } = this.state
    return (
      <div className="m-login">
        <div>
          注册
        </div>
        <div className="m-login-row">
          <Input placeholder="请输入用户名" value={username} onChange={this.handleInput.bind(this, 'username')}></Input>
        </div>
        <div className="m-login-row">
          <Input placeholder="请输入密码" type="password" value={password} onChange={this.handleInput.bind(this, 'password')}></Input>
        </div>
        <div className="m-login-row">
          <Input placeholder="请输入邮箱" type="email" value={email} onChange={this.handleInput.bind(this, 'email')}></Input>
        </div>
        <div className="m-login-row">
          <Button onClick={this.handleRegister.bind(this)}>注册</Button>
        </div>
                        </div>
    );
  }
}

Object.assign(Register.prototype, {
  handleRegister() {
    let {username, password, email} = this.state
    console.log(username,password)
    let data = {
      username,
      password: jsEncrypt(password),
      email
    }
    
    Api.register(data).then((res) => {
      if (res.code === keyCode.SUCCESS) {
        console.log(res)
        message.info(res.message)
      }
    }).catch((e) => {
    })
  },
})

//受控组件
Object.assign(Register.prototype, {
  handleInput(field, e) {
    this.setState({
      [field]: e.target.value
    })
  }
})

export default withRouter(Register)