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)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步