后台框架搭建、登录跳转路由转入后台
私有化组件、存储token安全机制进入后台
设置私有化的链接
reacttraining.com/react-route… 官网API
react中的配置
import React from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
// 引用组件
import Login from './views/login/Index';
import Index from './views/index/Index';
// 私有组件方法
import PrivateRouter from "./components/privateRouter/Index";
// store
import Store from "@/stroe/Index";
// Provider
import { Provider } from "react-redux";
class App extends React.Component {
constructor(props){
super(props);
this.state = {};
}
render(){
return (
<Provider store={Store}>
<BrowserRouter>
<Switch>
<Route exact component={Login} path="/" />
<PrivateRouter component={Index} path="/index" />
</Switch>
</BrowserRouter>
</Provider>
)
}
}
export default App;
设置获取token的方法
import cookies from "react-cookies";
const token = "adminToken";
const user = "username";
// 存储token
export function setToken(value) {
cookies.save(token, value);
}
// 获取值
export function getToken() {
return cookies.load(token);
}
// 存储用户名
export function setUsername(value) {
cookies.save(user, value);
}
// 获取值 设置值的时候 不需要return 但是获取值的时候 是需要return的 才能获取到值
export function getUsername() {
return cookies.load(user);
}
存储token(在login的页面登录的时候)
Login(requestData).then(response => { // resolves
this.setState({
loading: false
})
const data = response.data.data
// actions
this.props.actions.setToken(data.token);
this.props.actions.setUsername(data.username);
// 存储token
setToken(data.token);
setUsername(data.username);
// 路由跳转
this.props.history.push('/index');
}).catch(error => { // reject
this.setState({
loading: false
})
})
import React from "react";
import { Route, Redirect } from "react-router-dom";
// 方法
import { getToken } from "../../utils/cookies";
const PrivateRouter = ({ component: Component, ...rest }) => {
return (
<Route {...rest} render={routeProps => (
getToken() ? <Component {...routeProps} /> : <Redirect to="/" />
)} />
);
}
export default PrivateRouter;
功能: 页面刷新的之后,侧边栏依然保持高亮
第一步: 给,menu添加openKeys,selectedKeys
<Menu
onOpenChange={this.openMenu}
onClick={this.selectMenu}
theme="dark"
mode="inline"
selectedKeys={selectedKeys}
openKeys={openKeys}
style={{ height: '100%', borderRight: 0 }} />
第二步:定义openKeys,selectedKeys
this.state = {
selectedKeys: [],
openKeys: []
};
第三步:页面刷新之后,在dom节点全部渲染完成之后,在生命周期获取路由路径
// 生命周期,在这里多了一层接口请求,并过滤路由
componentDidMount(){
const pathname = this.props.location.pathname;
const menuKey = pathname.split("/").slice(0, 3).join('/');
const menuHigh = {
selectedKeys: pathname,
openKeys: menuKey
}
this.selectMenuHigh(menuHigh);
}
注意点:this.props 在页面中直接使用是获取不到值得
1 import { withRouter } from "react-router-dom"; // 上文有解释
2 export default withRouter(AsideMenu);
第四步:改变state中得值
/** 页面刷新 菜单高光 */
selectMenuHigh = ({selectedKeys, openKeys}) => {
this.setState({
selectedKeys: [selectedKeys],
openKeys: [openKeys]
})
}
第五步:给侧边栏添加点击事件
<Menu
onOpenChange={this.openMenu}
onClick={this.selectMenu}
/** 选择菜单 */
selectMenu = ({ item, key, keyPath, domEvent }) => {
const menuHigh = {
selectedKeys: key,
openKeys: keyPath[keyPath.length - 1]// 数组的长度减1,即是数组的最后一项
}
this.selectMenuHigh(menuHigh);
}
// 展开菜单
openMenu = (openKeys) => {
this.setState({
openKeys: [openKeys[openKeys.length - 1]]
})
}
功能:收起展开侧边栏
实现步骤:
// 在index.js中页面中 包括侧边栏-头部-内容区
// 设置一个控制收起展开得是参数
this.state = {
collapsed: false
};
// 将collapsed 传给头部组件以及侧边栏组件
// 因为头部中要点击图标进行切换
// 在父组件中设置函数 改变collapsed得值
// 父组件中
// 页面刷新之后保留收起展开得值得状态
componentDidMount() {
const collapsed = JSON.parse(sessionStorage.getItem("collapsed"));
this.setState({
collapsed
});
}
<Header className = "layout-header" >
<LayoutHeader toggle = {this.toggleCollapsed} collapsed = {this.state.collapsed} />
</Header >
<Sider width = "250px" collapsed = {this.state.collapsed} >
< LayoutAside /> </Sider>
toggleCollapsed = () => {
const collapsed = !this.state.collapsed;
this.setState({
collapsed
});
sessionStorage.setItem("collapsed", collapsed);
}
// 子组件中 通过props接收默认父组件中得传过来得zhi
constructor(props){
super(props);
this.state = {
collapsed: props.collapsed
};
}
// 生命周期,监听父级 props 的值变化
componentWillReceiveProps({ collapsed }){
this.setState({
collapsed
})
}
// 通过collapsed得值来判断 以及设置宽度与大小
render(){
const { collapsed } = this.state;
return (
<div className={collapsed ? "collapsed-close" : ""}>
<h1 className="logo"><span>LOGO</span></h1>
<div className="header-wrap">
<span className="collapsed-icon" onClick={this.toggleMenu}><MenuFoldOutlined /></span>
</div>
</div>
)
}
// 图标点击事件
onClick={this.toggleMenu}
toggleMenu = () => {
// 调用父组件中得方法
this.props.toggle();
}
.collapsed-close {
.logo {
width: 80px;
span { width: 100%; }
}
.header-wrap {
margin-left: 65px;
}
}
自动化工程生成组件
第一步:
通过上下文 拿到跟路由对应得文件以及组件
// 建立上下文件关系
const files = require.context("../../views/", true, /\.js$/); // 第一个参数:目录,第二参数:是否查找子级目录,第三参数:指定查找到文件
// 声明组件对象
const components = [];
// 循环文件
files.keys().map((key) => {
// 过滤 index、login
if (key.includes("./index/") || key.includes("./login/")) {
return false;
}
// 分割字符串
const splitFilesName = key.split(".");
const jsonObj = {};
// path
const path = `/index${splitFilesName[1].toLowerCase()}`;
// component 组件本身
const component = files(key).default;
// 写入对象
jsonObj.path = path;
jsonObj.component = component;
components.push(jsonObj);
return key;
})
export default components;
第二步:封装好的组件
import React from 'react';
import { Switch } from 'react-router-dom';
// 私有组件方法
import PrivateRouter from "../privateRouter/Index";
/** 自动化工程 */
import Components from "./components";
class ContainerMain extends React.Component {
constructor(props){
super(props);
this.state = {};
}
render(){
return (
<Switch>
{
Components.map(item => {
return <PrivateRouter exact key={item.path} path={item.path} component={item.component} />
})
}
</Switch>
)
}
}
export default ContainerMain;
第三步: 在搭建框架得页面中使用,跟侧边栏中得路由联动
import React, {
Component
} from "react";
// layout组件
import LayoutAside from "./components/aside";
import LayoutHeader from "./components/header";
import ContainerMain from "../../components/containerMain/Index";
// css
import "./layout.scss";
// antd
import {
Layout
} from 'antd';
const {
Sider,
Header,
Content
} = Layout;
class Index extends Component {
constructor(props) {
super(props);
this.state = {
collapsed: false
};
}
componentDidMount() {
const collapsed = JSON.parse(sessionStorage.getItem("collapsed"));
this.setState({
collapsed
});
}
toggleCollapsed = () => {
const collapsed = !this.state.collapsed;
this.setState({
collapsed
});
sessionStorage.setItem("collapsed", collapsed);
}
render() {
return (
<Layout className = "layout-wrap" >
<Header className = "layout-header" >
<LayoutHeader toggle = {
this.toggleCollapsed
}
collapsed = {
this.state.collapsed
} />
</Header >
<Layout>
<Sider width = "250px"
collapsed = {
this.state.collapsed
}><LayoutAside />
</Sider>
<Content className = "layout-main" >
<ContainerMain />
</Content>
</Layout>
</Layout>
)
}
}
export default Index;
功能点:使用es6,扩展
功能点:token得数据保存
import cookies from "react-cookies";
const token = "adminToken";
const user = "username";
// 存储token
export function setToken(value) {
cookies.save(token, value);
}
// 获取值 设置值的时候 不需要return 但是获取值的时候 是需要return的 才能获取到值
export function getToken() {
return cookies.load(token);
}
// 存储用户名
export function setUsername(value) {
cookies.save(user, value);
}
// 获取值 设置值的时候 不需要return 但是获取值的时候 是需要return的 才能获取到值
export function getUsername() {
return cookies.load(user);
}
import axios from "axios";
// antd
import { message } from "antd";
// cookies
import { getToken, getUsername } from "./cookies";
//第一步,创建实例
const service = axios.create({
baseURL: process.env.REACT_APP_API,
timeout: 5000,
});
// 第二步,请求拦截(请求头)
service.interceptors.request.use(function (config) {
// 在发送请求之前做些什么 Token, Username
config.headers["Token"] = getToken();
config.headers["Username"] = getUsername();
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 第三步,响应拦截(响应头)
service.interceptors.response.use(function (response) {
// http状态为200
// 对响应数据做点什么
const data = response.data;
if(data.resCode !== 0) {
// resCode不成功
message.info(data.message);
// 全局的错误拦截提示
// 可以针对某些 resCode 值,进行业务逻辑处理
if(data.resCode === 1023) {
alert(11111)
}
return Promise.reject(response)
} else {
// resCode成功
return response;
}
}, function (error) {
// http 状态不为200
// const data = error.request;
// 对响应错误做点什么
return Promise.reject(error.request);
});
export default service;
react路由传参(3种方式)
params传参(刷新页面后参数不消失,参数会在地址栏显示)
路由页面:
<Route path=‘/link/:id’ component={Demo}></Route>
//注意要配置 /:id 路由跳转并传递参数。
链接方式:
<Link to={‘/link/’+‘xxx’}>首页</Link>
或 <Link to={{pathname:'/link/'+‘xxx'}}>首页</Link>
js方式:
this.props.history.push(‘/ link /’+‘xxx’)
或 this.props.history.push({pathname:'/ link /'+‘xxx'})
获取参数:
this.props.match.params.id
//注意这里是match而非history
query传参(刷新页面后参数消失)
路由页面:
<Route path='/demo' component={Demo}></Route>
//无需配置 路由跳转并传递参数
链接方式:
<Link to={{pathname:'/link ',query:{ id:22, name:'dahuang '}}}>XX</Link>
js方式:
this.props.history.push({pathname:'/demo',query:{id:22,name:'dahuang'}})
获取参数:
this.props.location.query.name
最优的传参方法:
state传参( 刷新页面后参数不消失,state传的参数是加密的)
路由页面:
<Route path='/ link ' component={Demo}></Route>
//无需配置 路由跳转并传递参数
链接方式:
<Link to={{pathname:'/link ',state:{id:12,name:'dahuang'}}}>XX</Link>
js方式:
this.props.history.push({pathname:'/demo',state:{id:12,name:'dahuang'}})
获取参数:
this.props.location.state.name
防止连续点击、交互优化、表格数据加载
{
title: "禁启用",
dataIndex: "status",
key: "status",
render: (status, rowData) => {
return <Switch onChange={() => this.onHandlerSwitch(rowData)} loading={rowData.id === this.state.id} checkedChildren="启用" unCheckedChildren="禁用" defaultChecked={status === "1" ? true : false} />
}
},
开关的做法:
state中定义flag
// 第一种做法,用组件本身异步
this.setState({id: data.id})
// 第二种做法,自己做的开关
// this.setState({flag: true})
Status(requestData).then(response => {
message.info(response.data.message);
this.setState({id: ""})
// this.setState({flag: false})
}).catch(error => {
this.setState({id: ""})
// this.setState({flag: false})
})
ui组件的二次封装
数据类型检测
import PropTypes from 'prop-types';
propTypes 提供一系列验证器,可用于确保组件接收到的数据类型是有效的。在本例中, 我们使用了 PropTypes.string。当传入的 prop 值类型不正确时,JavaScript 控制台将会显示警告。出于性能方面的考虑,propTypes 仅在开发模式下进行检查。
MyComponent.propTypes = {
// 你可以将属性声明为 JS 原生类型,默认情况下
// 这些属性都是可选的。
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// 任何可被渲染的元素(包括数字、字符串、元素或数组)
// (或 Fragment) 也包含这些类型。
optionalNode: PropTypes.node,
// 一个 React 元素。
optionalElement: PropTypes.element,
// 一个 React 元素类型(即,MyComponent)。
optionalElementType: PropTypes.elementType,
// 你也可以声明 prop 为类的实例,这里使用
// JS 的 instanceof 操作符。
optionalMessage: PropTypes.instanceOf(Message),
// 你可以让你的 prop 只能是特定的值,指定它为
// 枚举类型。
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
};
// 一个对象可以是几种类型中的任意一个类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 可以指定一个数组由某一类型的元素组成
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 可以指定一个对象由某一类型的值组成
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 可以指定一个对象由特定的类型值组成
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// An object with warnings on extra properties
optionalObjectWithStrictShape: PropTypes.exact({
name: PropTypes.string,
quantity: PropTypes.number
}),
// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
// 这个 prop 没有被提供时,会打印警告信息。
requiredFunc: PropTypes.func.isRequired,
// 任意类型的数据
requiredAny: PropTypes.any.isRequired,
// 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
// 请不要使用 `console.warn` 或抛出异常,因为这在 `onOfType` 中不会起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
// 它应该在验证失败时返回一个 Error 对象。
// 验证器将验证数组或对象中的每个值。验证器的前两个参数
// 第一个是数组或对象本身
// 第二个是他们当前的键。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
默认 Prop 值
// 指定 props 的默认值:
MyComponent.defaultProps = {
name: 'Stranger'
};
{/*
在父组件获取子组件的实例
1、在子组件调用父组件方法,并把子组件实例传回给父组件,(已经存储了子组件的实例)
2、通过实例调用子组件的方法
*/}
<TableComponent onRef={this.getChildRef} batchButton={true} config={this.state.tableConfig} />
// 获取子组件实例
getChildRef = (ref) => {
// 1、在子组件调用父组件方法,并把子组件实例传回给父组件,(已经存储了子组件的实例)
// 2、通过实例调用子组件的方法
this.tableComponent = ref; // 存储子组件
}
// 调用
/** 删除 */
delete = (id) => {
// 2、通过实例调用子组件的方法
this.tableComponent.onHandlerDelete(id)
}
封装组件的table的过程1
import React, { Component, Fragment } from "react";
// propTypes
import PropTypes from 'prop-types';
// antd
import{ Table, Row, Col, Button, Pagination } from "antd";
class TableBasis extends Component {
render(){
const { columns, dataSource, total, changePageCurrent, changePageSize, batchButton, handlerDelete, rowSelection, rowkey } = this.props;
return (
<Fragment>
<Table pagination={false} rowKey={rowkey} rowSelection={rowSelection} columns={columns} dataSource={dataSource} bordered />
<div className="spacing-30"></div>
<Row>
<Col span={8}>
{ batchButton && <Button onClick={handlerDelete}>批量删除</Button> }
</Col>
<Col span={16}>
<Pagination
onChange={changePageCurrent}
onShowSizeChange={changePageSize}
className="pull-right"
total={total}
showSizeChanger
showQuickJumper
showTotal={total => `Total ${total} items`}
/>
</Col>
</Row>
</Fragment>
)
}
}
// 校验数据类型
TableBasis.propTypes = {
columns: PropTypes.array,
dataSource: PropTypes.array,
total: PropTypes.number,
changePageCurrent: PropTypes.func,
changePageSize: PropTypes.func,
batchButton: PropTypes.bool,
rowSelection: PropTypes.object,
rowkey: PropTypes.string
}
// 默认
TableBasis.defaultProps = {
column: [],
dataSource: [],
total: 0,
batchButton: true,
rowkey: "id"
}
export default TableBasis;
封装组件的table的过程2
import React, { Component, Fragment } from "react";
// propTypes
import PropTypes from 'prop-types';
// antd
import { message, Modal } from "antd";
// api
import { TableList, TableDelete } from "@api/common";
// url
import requestUrl from "@api/requestUrl";
// Table basis component
import TableBasis from "./Table";
import FormSearch from "../formSearch/Index";
class TableComponent extends Component {
constructor(props){
super(props);
this.state = {
// 请求参数
pageNumber: 1,
pageSize: 10,
searchData: {},
// 数据
data: [],
// 加载提示
loadingTable: false,
// 页码
total: 0,
// 复选框
checkboxValue: [],
// 确认弹窗
modalVisible: false,
modalconfirmLoading: false
}
}
componentDidMount(){
this.loadDada();
// 返回子组件实例
this.props.onRef(this); // 子组件调用父组件方法,并把子组件实例传回给父组件
}
/** 获取列表数据 */
loadDada = () => {
const { pageNumber, pageSize, searchData } = this.state;
const requestData = {
url: requestUrl[this.props.config.url],
data: {
pageNumber: pageNumber,
pageSize: pageSize
}
}
// 筛选项的参数拼接
if(Object.keys(searchData).length !== 0) {
for(let key in searchData) {
requestData.data[key] = searchData[key]
}
}
// 请求接口
TableList(requestData).then(response => {
const responseData = response.data.data; // 数据
if(responseData.data) { // 返回一个 null
this.setState({
data: responseData.data,
total: responseData.total
})
}
this.setState({loadingTable: false})
}).catch(error => {
this.setState({loadingTable: false})
})
}
search = (searchData) => {
this.setState({
pageNumber: 1,
pageSize: 10,
searchData
}, () => {
this.loadDada();
})
}
/** 删除 */
onHandlerDelete(id){
this.setState({ modalVisible: true })
if(id) { this.setState({ checkboxValue: [id] }); }
}
/** 复选框 */
onCheckebox = (checkboxValue) => {
this.setState({checkboxValue})
}
/** 当前页码 */
onChangeCurrnePage = (value) => {
this.setState({
pageNumber: value
}, () => {
this.loadDada();
})
}
/** 下拉页码 */
onChangeSizePage = (value, page) => {
// this.setState({}, () => {})
//异步回调函数的写法,在更改完值 马上需要值做处理的时候
this.setState({
pageNumber: 1,
pageSize: page
}, () => {
this.loadDada();
})
}
/** 确认弹窗 */
modalThen = () => {
// 判断是否已选择删除的数据
if(this.state.checkboxValue.length === 0) {
message.info("请选择需要删除的数据");
return false;
}
this.setState({ confirmLoading: true })
const id = this.state.checkboxValue.join();
const requestData = {
url: requestUrl[`${this.props.config.url}Delete`],
data: {
id
}
}
TableDelete(requestData).then(response => {
message.info(response.data.message);
this.setState({
modalVisible: false,
id: "",
modalconfirmLoading: false,
selectedRowKeys: []
})
// 重新加载数据
this.loadDada();
})
}
render(){
const { thead, checkbox, rowkey, formItem } = this.props.config;
const rowSelection = {
onChange: this.onCheckebox
}
return (
<Fragment>
{/* 筛选 */}
<FormSearch formItem={formItem} search={this.search} />
{/* table UI 组件 */}
<div className="table-wrap">
<TableBasis
columns={thead}
dataSource={this.state.data}
total={this.state.total}
changePageCurrent={this.onChangeCurrnePage}
changePageSize={this.onChangeSizePage}
handlerDelete={() => this.onHandlerDelete()}
rowSelection={checkbox ? rowSelection : null}
rowkey={rowkey}
/>
</div>
{/* 确认弹窗 */}
<Modal
title="提示"
visible={this.state.modalVisible}
onOk={this.modalThen}
onCancel={() => { this.setState({ modalVisible: false})}}
okText="确认"
cancelText="取消"
confirmLoading={this.state.modalconfirmLoading}
>
<p className="text-center">确定删除此信息?<strong className="color-red">删除后将无法恢复。</strong></p>
</Modal>
</Fragment>
)
}
}
// 校验数据类型
TableComponent.propTypes = {
config: PropTypes.object
}
// 默认
TableComponent.defaultProps = {
batchButton: false
}
export default TableComponent;
封装组件--from的组件
import React, { Component, Fragment } from "react";
// antd
import { message } from "antd";
// API
import { Add, Detailed, Edit } from "@/api/department";
// 组件
import FormCom from "@c/form/Index";
class DepartmentAdd extends Component {
constructor(props){
super(props);
this.state = {
loading: false,
id: "",
formConfig: {
url: "departmentAdd",
initValue: {
number: 0,
status: true
},
setFieldValue: {}
},
formLayout: {
labelCol: { span: 2 },
wrapperCol: { span: 20 }
},
formItem: [
{
type: "Input",
label: "部门名称",
name: "name",
required: true,
style: { width: "200px" },
placeholder: "请输入部门名称"
},
{
type: "InputNumber",
label: "人员数量",
name: "number",
required: true,
min: 0,
max: 100,
style: { width: "200px" },
placeholder: "请输入人员数量"
},
{
type: "Radio",
label: "禁启用",
name: "status",
required: true,
options: [
{ label: "禁用", value: false },
{ label: "启用", value: true },
]
},
{
type: "Input",
label: "描述",
name: "content",
required: true,
placeholder: "请输入描述内容"
}
]
};
}
componentWillMount(){
if(this.props.location.state) {
this.setState({
id: this.props.location.state.id
})
}
}
componentDidMount(){
this.getDetailed();
}
getDetailed = () => {
if(!this.props.location.state) { return false }
Detailed({id: this.state.id}).then(response => {
this.setState({
formConfig: {
...this.state.formConfig,
setFieldValue: response.data.data
}
})
// this.refs.form.setFieldsValue(response.data.data);
})
}
/** 编辑信息 */
onHandlerEdit = (value) => {
const requestData = value;
requestData.id = this.state.id;
Edit(requestData).then(response => {
const data = response.data;
message.info(data.message)
this.setState({
loading: false
})
}).catch(error => {
this.setState({
loading: false
})
})
}
/** 添加信息 */
onHandlerAdd = (value) => {
const requestData = value;
Add(requestData).then(response => {
const data = response.data;
message.info(data.message)
this.setState({
loading: false
})
}).catch(error => {
this.setState({
loading: false
})
})
}
/** 提交表单 */
onHandlerSubmit = (value) => {
this.state.id ? this.onHandlerEdit(value) : this.onHandlerAdd(value);
}
render(){
return (
<Fragment>
<FormCom formItem={this.state.formItem} formLayout={this.state.formLayout} formConfig={this.state.formConfig} submit={this.onHandlerSubmit} />
</Fragment>
)
}
}
export default DepartmentAdd;
封装的底层
import React, { Component } from "react";
// propTypes
import PropTypes from 'prop-types';
// API
import { requestData } from "@api/common";
// url
import requestUrl from "@api/requestUrl";
// components
import SelectComponent from "../select/Index";
// antd
import { Form, Input, Button, Select, InputNumber, Radio, message } from "antd";
const { Option } = Select;
class FormCom extends Component {
constructor(props){
super(props);
this.state = {
loading: false,
mesPreix: {
"Input": "请输入",
"Radio": "请选择",
"Select": "请选择"
}
}
}
componentWillReceiveProps({ formConfig }){
this.refs.form.setFieldsValue(formConfig.setFieldValue)
}
// 校验规则
rules = (item) => {
// state
const { mesPreix } = this.state;
let rules = [];
// 是否必填
if(item.required) {
let message = item.message || `${mesPreix[item.type]}${item.label}`; // 请输入xxxxx,请选择xxxxxx
rules.push({ required: true, message })
}
if(item.rules && item.rules.length > 0) {
rules = rules.concat(item.rules);
}
return rules;
}
// selcctComponent 校验方法
validatorSelect = (rule, value) => {
if(!value || !value[rule.field]) {
return Promise.reject("选项不能为空");
}
return Promise.resolve();
}
// input
inputElem = (item) => {
const rules = this.rules(item);
return (
<Form.Item label={item.label} name={item.name} key={item.name} rules={rules}>
<Input style={item.style} placeholder={item.placeholder}/>
</Form.Item>
)
}
// inputNumber
inputNumberElem = (item) => {
const rules = this.rules(item);
return (
<Form.Item label={item.label} name={item.name} key={item.name} rules={rules}>
<InputNumber min={item.min} max={item.max} />
</Form.Item>
)
}
// select
selectElem = (item) => {
const rules = this.rules(item);
return (
<Form.Item label={item.label} name={item.name} key={item.name} rules={rules}>
<Select style={item.style} placeholder={item.placeholder}>
{
item.options && item.options.map(elem => {
return <Option value={elem.value} key={elem.value}>{elem.label}</Option>
})
}
</Select>
</Form.Item>
)
}
// SelectComponent
SelectComponent = (item) => {
const rules = this.rules(item);
return (
<Form.Item label={item.label} name={item.name} key={item.name} rules={[...rules, {validator: this.validatorSelect}]}>
<SelectComponent url={item.url} propsKey={item.propsKey} name={item.name} />
</Form.Item>
)
}
// radio
radioElem = (item) => {
const rules = this.rules(item);
return (
<Form.Item label={item.label} name={item.name} key={item.name} rules={rules}>
<Radio.Group>
{
item.options && item.options.map(elem => {
return <Radio value={elem.value} key={elem.value}>{elem.label}</Radio>
})
}
</Radio.Group>
</Form.Item>
)
}
// 初始化
initFormItem = () => {
const { formItem } = this.props;
// 检测是否存在 formItem
if(!formItem || (formItem && formItem.length === 0)) { return false; }
// 循环处理
const formList = []
formItem.map(item => {
if(item.type === "Input") { formList.push(this.inputElem(item)); }
if(item.type === "Select") { formList.push(this.selectElem(item)); }
if(item.type === "SelectComponent") { formList.push(this.SelectComponent(item)); }
if(item.type === "InputNumber") { formList.push(this.inputNumberElem(item)); }
if (item.type === "Radio") { formList.push(this.radioElem(item)); }
return item.type;
})
return formList;
}
onSubmit = (value) => { // 添加、修改
// 传入的 submit
if(this.props.submit) {
this.props.submit(value);
return false;
}
// 数据格式化
const formatFormKey = this.props.formConfig.formatFormKey;
if(formatFormKey && value[formatFormKey]) {
const dataKey = value[formatFormKey]; // 临时存储指定 key 数据
delete value.parentId // 删除指定的 key
value = Object.assign(value, dataKey) // 浅拷贝并合JSON对象
}
// 请求参数
const data = {
url: requestUrl[this.props.formConfig.url],
data: value
}
this.setState({ loading: true })
requestData(data).then(response => {
const responseData = response.data;
// 提示
message.info(responseData.message)
// 取消按钮加载
this.setState({ loading: false })
}).catch(error => {
// 取消按钮加载
this.setState({ loading: false })
})
}
render(){
return (
<Form ref="form" onFinish={this.onSubmit} initialValues={this.props.formConfig.initValue} {...this.props.formLayout}>
{ this.initFormItem() }
<Form.Item>
<Button loading={this.state.loading} type="primary" htmlType="submit">确定</Button>
</Form.Item>
</Form>
)
}
}
// 校验数据类型
FormCom.propTypes = {
formConfig: PropTypes.object
}
// 默认
FormCom.defaultProps = {
formConfig: {}
}
export default FormCom;
redux篇 除了和react一起用之外 不是跟react一定要绑定的
Redux 的 4大核心:
Store
State
Action
Reducer
Store 类似于数据存储仓库,存储 State 应用的所有状态数据。
state是只读的
Action 是事件,应用某个模块请求动作或操作,通过分发 Action 事件(带type属性的对象)执行 Reducer 来修改Store里的state,Action可以携带数据对象,就是告知 Reducer 要更新 Store 的 state。
Reducer 是在收到分发的 Action 事件后, 经过reducer处理后,会返回新的状态数据。Reducer接收两个参数:原始的state和Action,返回一个新的state。
单向数据流
中文文档: https://www.redux.org.cn/
依赖 npm install --save redux
创建 store
import { createStore } from "redux";
const store = createStore();
store.subscribe(() =>
console.log(store.getState())
);
store.dispatch(addInfo(‘禁用', 1));
store.dispatch(addInfo(‘启用', 2));
combineReducers
Reducer
export default function(state=initialState, action) {
switch (action.type) {
case ‘ADD_INFO': {
return {
...state,
info: [...state.info, action.payload]
}
}
default:
return state;
}
}
Active
export function addInfo(label, value) {
return {
type: ‘ADD_INFO’,
payload: { label, value }
}
}
1. Redux 核心
1.1 Redux 介绍
JavaScript 状态容器,提供可预测化的状态管理
1.2 Redux 核心概念及流程
Store: 存储状态的容器,JavaScript 对象
View: 视图,HTML页面
Actions: 对象,描述对状态进行怎样的操作
Reducers: 函数,操作状态并返回新的状态
1.3 Redux 使用: 计数器案例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redux</title>
</head>
<body>
<button id="minus">-</button>
<span id="count">0</span>
<button id="plus">+</button>
<script src="./redux.min.js"></script>
<script>
// 3. 存储默认状态
const initialState = {
count: 0
}
// 2. 创建 reducer 函数
function reducer(state = initialState, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
// 1. 创建 store 对象
const store = Redux.createStore(reducer);
// 4. 定义 action
const increment = { type: 'increment' }
const decrement = { type: 'decrement' }
// 5. 获取按钮 给按钮添加点击事件
document.getElementById('minus')
.addEventListener('click', function () {
// 6. 获取dispatch 触发 action
store.dispatch(decrement)
})
document.getElementById('plus')
.addEventListener('click', function () {
// 6. 获取dispatch 触发 action
store.dispatch(increment)
})
// 获取store {dispatch: ƒ, subscribe: ƒ, getState: ƒ, replaceReducer: ƒ, Symbol(observable): ƒ}
console.log(store)
// 获取 store 中存储的状态 state
console.log(store.getState());
// 订阅数据的变化
store.subscribe(() => {
console.log(store.getState())
document.getElementById('count').innerHTML = store.getState().count
});
</script>
</body>
</html>
1.4 Redux 核心API
// 创建 store 对象
const store = Redux.createStore(reducer);
// 2. 创建 reducer 函数
function reducer(state = initialState, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
// 获取 store 中存储的状态 state
store.getState()
// 订阅数据的变化
store.subscribe(() => {
console.log(store.getState())
});
// 获取dispatch 触发 action
store.dispatch(increment)
2. React + Redux
2.1 在 React 中不使用 Redux 时遇到的问题
在 React 中组件通信的数据流是单向的,顶层组件可以通过 Props 属性向下层组件传递数据,而下层组件不能向上层组件传递数据。要实现下层组件修改数据,需要上层组件传递修改数据的方法到下层组件。等项目越来越大的时候,组件间传递数据变得越来越困难。
2.2 在 React 项目中加入 Redux 的好处
使用 Redux 管理数据,由于 Store 独立于组件,使得数据管理独立于组件,解决了组件与组件之间传递数据困难的问题。
2.3 下载 Redux
npm install redux react-redux
2.4 Redux 工作流程
组件通过 dispatch 方法触发 Action
Store 接受 Action 并将 Action 分发给Reducer
Reducer 根据 Action 类型对状态进行更改并将更改后的状态返回给 Store
组件订阅了 Store 中的状态, Store 中的状态更新会同步到组件
2.5 Redux 使用步骤
2.5.1 创建 store
// src/store/index.js
import { createStore } from 'redux'
import reducer from './reducers/counter.reducer'
export const store = createStore(reducer)
在根组件中使用store:
import React from 'react';
import ReactDOM from 'react-dom';
import Counter from './components/Counter'
import { Provider } from 'react-redux'
import {store} from './store'
/**
* react-redux
* Provider
* connect
*/
ReactDOM.render(
// 通过 provider 组件,将store 放在了全局的组件可以够得着的地方
<Provider store={store}>
<Counter />
</Provider>,
document.getElementById('root')
);
2.5.2 创建 reducer
// src/store/reducers/counter.reducer.js
import { DECREMENT, INCREMENT } from "../count/counter.const";
const initialState = {
count: 0
}
export default function reducer (state = initialState, action) {
switch (action.type) {
case INCREMENT:
return { count: state.count + 1 };
case DECREMENT:
return { count: state.count - 1 };
default:
return state;
}
}
// src/store/count/counter.const.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
2.5.3 在组件中使用 connect 接受 store 里面的 state 和 dispatch
connect方法接受两个参数,返回一个高阶组件。
connect方法的第一个参数是mapStateToProps方法,将store中的state传递到组件的props中,mapStateToProps方法的参数是state,返回值是一个对象,会传递到组件中,写法如下:
const mapStateToProps = (state) => ({
count: state.count,
a: 'a', // 这里怎么定义,组件中就可以或得到一个属性
})
connect方法的第二个参数是mapDispatchToProps方法,将store中的dispatch传递到组件的props中,mapDispatchToProps方法的参数是dispatch,返回值是一个对象,对象中的方法可以使用dispatch,这个对象中的方法会传递到组件中,写法如下:
const mapDispatchToProps = (dispatch) => ({
increment () {
dispatch({ type: 'increment'})
},
decrement () {
dispatch({ type: 'decrement' })
}
})
此外,我们还可以通过redux中的bindActionCreators来帮我们创建action函数:
import {bindActionCreators} from 'redux'
// bindActionCreators 会返回一个对象
const mapDispatchToProps = dispatch => (
// 解构
...bindActionCreators({
increment () {
return { type: 'increment'}
},
decrement () {
return { type: 'decrement'}
}
}, dispatch)
)
或者写成:
const mapDispatchToProps = dispatch => bindActionCreators({
increment () {
return { type: 'increment'}
},
decrement () {
return { type: 'decrement'}
}
}, dispatch)
也可以将bindActionCreators的第一个参数进行抽离:
import * as counterActions from '../store/actions/counter.actions'
const mapDispatchToProps = dispatch => bindActionCreators(conterActions, dispatch)
// src/store/actions/counter.actions.js
import { DECREMENT, INCREMENT } from "../count/counter.const"
export const increment = () => ({type: INCREMENT})
export const decrement = () => ({type: DECREMENT})
connect方法接受mapStateToProps和mapDispatchToProps,返回一个高阶组件,然后传入Counter组件进行导出:
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
最终组件代码如下:
// src/components/Counter.js
import React from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as counterActions from '../store/actions/counter.actions'
function Counter ({count, increment, decrement}) {
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
)
}
// 1. connect 会帮助我们去订阅 store,当store中的状态发生了变化后,可以帮我们重新渲染组件 // 2. connect 方法可以让我们获取 store 中的状态,将状态通过组建的props属性映射给组件 // 3. connect 方法可以让我们获取 dispatch 方法
const mapStateToProps = (state) => ({
count: state.count,
a: 'a', // 这里怎么定义,组件中就可以或得到一个属性
})
const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
2.5.4 为 action 传递参数
传递参数
<button onClick={() => increment(5)}> + 5</button>
接受参数,传递reducer
export const increment = payload => ({type: INCREMENT, payload})
export const decrement = payload => ({type: DECREMENT, payload})
reducer根据接受收到的数据进行处理
export default function reducer (state = initialState, action) {
switch (action.type) {
case INCREMENT:
return { count: state.count + action.payload };
case DECREMENT:
return { count: state.count - action.payload };
default:
return state;
}
}
2.6 redux 实现弹出框案例
store中的状态越多,reducer中的switch分支就会越多,不利于维护,需要拆分reducer
src/index.js
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux'
import {store} from './store'
ReactDOM.render(
// 通过 provider 组件,将store 放在了全局的组件可以够得着的地方
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
src/store/index.js
// src/store/index.js
import { createStore } from 'redux'
import reducer from './reducers/counter.reducer'
export const store = createStore(reducer)
src/store/reducers/counter.reducer.js
// src/store/reducers/counter.reducer.js
import { DECREMENT, INCREMENT } from "../const/counter.const";
import { HIDEMODAL, SHOWMODAL } from "../const/modal.const";
const initialState = {
count: 0,
show: false
}
export default function reducer (state = initialState, action) {
switch (action.type) {
case INCREMENT:
return { ...state, count: state.count + action.payload };
case DECREMENT:
return { ...state, count: state.count - action.payload };
case SHOWMODAL:
return { ...state, show: true };
case HIDEMODAL:
return { ...state, show: false };
default:
return state;
}
}
src/App.js
// src/App.js
import Modal from './components/Modal'
import Counter from './components/Counter'
function App() {
return (
<div className="App">
<Counter />
<Modal />
</div>
);
}
export default App;
src/components/Modal.js
// src/components/Modal.js
import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import * as modalActions from '../store/actions/modal.actions'
function Modal ({ showStatus, show, hide }) {
const styles = {
display: showStatus ? 'block': 'none',
width: 200,
height: 200,
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
margin: 'auto',
backgroundColor: 'skyblue'
}
return (
<div>
<button onClick={show}>显示</button>
<button onClick={hide}>隐藏</button>
<div style={styles}></div>
</div>
)
}
const mapStateToProps = state => ({
showStatus: state.show
})
const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Modal)
src/store/actions/modal.action.js
// src/store/actions/modal.action.js
import { HIDEMODAL, SHOWMODAL } from "../const/modal.const"
export const show = () => ({ type: SHOWMODAL })
export const hide = () => ({ type: HIDEMODAL })
src/store/const/modal.const.js
// src/store/const/modal.const.js
export const SHOWMODAL = 'showModal'
export const HIDEMODAL = 'hideModal'
2.7 拆分reducer
使用reducer提供的工具combineReducers合并每一个小的reducer
src/store/reducers/root.reducer.js
// src/store/reducers/root.reducer.js
import {combineReducers} from 'redux'
import CounterReducer from './counter.reducer'
import ModalReducer from './modal.reducer'
// { counter: { count: 0 }, modal: { show: false } }
export default combineReducers({
counter: CounterReducer,
modal: ModalReducer
})
src/store/reducers/counter.reducer.js
// src/store/reducers/counter.reducer.js
import { DECREMENT, INCREMENT } from "../const/counter.const";
const initialState = {
count: 0,
}
export default function counterReducer (state = initialState, action) {
switch (action.type) {
case INCREMENT:
return { ...state, count: state.count + action.payload };
case DECREMENT:
return { ...state, count: state.count - action.payload };
default:
return state;
}
}
src/store/reducers/modal.reducer.js
// src/store/reducers/modal.reducer.js
import { HIDEMODAL, SHOWMODAL } from "../const/modal.const";
const initialState = {
show: false
}
export default function modalReducer (state = initialState, action) {
switch (action.type) {
case SHOWMODAL:
return { ...state, show: true };
case HIDEMODAL:
return { ...state, show: false };
default:
return state;
}
}
创建store时传入的reducer则来自于我们刚才定义的root.reducer.js
import { createStore } from 'redux'
import RootReducer from './reducers/root.reducer'
export const store = createStore(RootReducer)
在每个组件中的mapStateToProps中也要发生相应的改变(state.counter和state.modal):
const mapStateToProps = (state) => ({
count: state.counter.count,
})
const mapStateToProps = state => ({
showStatus: state.modal.show
})
3. Redux 中间件
3.1 什么是中间件
中间价允许我们扩展和增强 redux 应用程序
3.2 开发 Redux 中间件
开发中间件的模板
export default store => next => action => { }
3.3 注册中间件
中间件在开发完成以后只有被注册才能在 Redux 的工作流程中生效
src/store/index.js
// src/store/index.js
import { createStore, applyMiddleware } from 'redux'
import logger from './middlewares/logger'
createStore(reducer, applyMiddleware(
logger
))
src/store/middleware/logger.js
const logger = store => next => action => {
console.log(store)
console.log(action)
next(action) // 千万别忘了调用 next(action)
}
export default logger
如果注册多个中间件,中间件的执行顺序就是注册顺序,如:
createStore(reducer, applyMiddleware(
logger,
test
))
那么执行顺序就是先执行logger中间件,再执行test中间件。 如果中间件中的结尾不调用next(action),则整个流程就会卡在此处不会再往后执行了
3.4 Redux 中间件开发实例 thunk (异步中间件)
当前这个中间件函数不关心你想执行什么样的异步操作,只关心你执行的是不是异步操作, 如果你执行的是异步操作,你在触发 action 的时候,给我传递一个函数,如果执行的是同步操作,就传递一个 action 对象, 异步操作代码要写在你传进来的函数中 当这个中间件函数,在调用你传进来的函数时要将 dispatch 方法传递过去
src/store/middleware/thunk.js
// src/store/middleware/thunk.js
import { DECREMENT, INCREMENT } from "../const/counter.const";
const thunk = ({dispatch}) => next => action => {
if (typeof action === 'function') {
return action(dispatch) // action 方法内部会发起新的 dispatch
}
next(action)
}
export default thunk
在action文件中定义异步函数action:
src/store/actions/modal.actions.js
// src/store/actions/modal.actions.js
import { HIDEMODAL, SHOWMODAL } from "../const/modal.const"
export const show = () => ({ type: SHOWMODAL })
export const hide = () => ({ type: HIDEMODAL })
export const show_async = () => dispatch => {
setTimeout(() => {
dispatch(show())
}, 2000);
}
原本使用show的地方,现在改用show_async,实现了异步的功能
4. Redux 常用中间件
4.1 redux-thunk
4.1.1 redux-thunk 下载
npm install redux-thunk
4.1.2 引入 redux-thunk
import thunk from 'redux-thunk';
4.1.3 注册 redux-thunk
import { applyMiddleware } from 'redux'
createStore(rootReducer, applyMiddleware(thunk));
4.1.4 使用 redux-thunk 中间件
const loadPosts = () => async dispatch => {
const posts = await axios.get('/api/posts').then(response => response.data);
dispatch({type: LOADPOSTSSUCCE, payload: posts});
}
4.2 redux-saga
4.2.1 redux-saga 解决的问题
redux-saga可以将异步操作从Action Creator文件中抽离出来,放在一个单独的文件中。
4.2.2 下载redux-saga
npm install redux-saga
4.2.3 创建 redux-saga 中间件
src/store/index.js
// src/store/index.js
import createSagaMiddleware from 'redux-saga';
const sagaMiddleware = createSagaMiddleware();
4.2.4 注册 sagaMiddleware
src/store/index.js
// src/store/index.js
createStore(reducer, applyMiddleware(sagaMiddleware))
4.2.5 使用 saga 接受 action 异步执行操作
src/store/sagas/counter.saga.js
// src/store/sagas/counter.saga.js
import { takeEvery, put, delay } from 'redux-saga/effects'
import { increment } from '../actions/counter.actions'
import { INCREMENT_ASYNC } from '../const/counter.const'
// takeEvery 接收 action
// put 触发 action
function * increment_async_fn (action) {
yield delay(2000) // 此处会暂停2秒钟
yield put(increment(action.payload))
}
export default function * counterSaga () {
// 接收 action
yield takeEvery(INCREMENT_ASYNC, increment_async_fn) // 第二个函数形参会接受一个 action 函数
}
src/store/actions/counter.actions.js
// src/store/actions/counter.actions.js
// 给 saga 使用
export const increment_async = (payload) => ({ type: INCREMENT_ASYNC, payload });
src/store/const/counter.const.js
// src/store/const/counter.const.js
export const INCREMENT_ASYNC = 'increment_async'
src/components/Counter.js
<button onClick={() => increment_async(20)}>+</button>
4.2.6 启动 saga
src/store/index.js
// src/store/index.js
import counterSaga from './sagas/counter.saga'
sagaMiddleware.run(counterSaga);
4.2.7 合并 saga
src/store/saga/root.saga.js
// src/store/saga/root.saga.js
import { all } from 'redux-saga/effects'
import counterSaga from './counter.saga'
import modalSaga from './modal.saga'
export default function * rootSaga () {
yield all([
counterSaga(),
modalSaga()
])
}
modal.saga.js 没变,modal.saga.js 如下
src/store/saga/modal.saga.js
// src/store/saga/modal.saga.js
import { takeEvery, put, delay } from 'redux-saga/effects'
import { show } from '../actions/modal.actions'
import { SHOWMODAL_ASYNC } from '../const/modal.const'
// takeEvery 接收 action
// put 触发 action
function * showModal_async_fn () {
yield delay(2000)
yield put(show())
}
export default function * modalSaga () {
// 接收 action
yield takeEvery(SHOWMODAL_ASYNC, showModal_async_fn)
}
store 入口文件中的 saga 中间件启动 root.saga
src/store/index.js
// src/store/index.js
import rootSaga from './sagas/root.saga'
sagaMiddleware.run(rootSaga)
4.3 redux-actions
4.3.1 redux-actions 解决的问题
redux 流程中大量的样板代码读写很痛苦,使用 redux-action 可以简化 Action 和 Reducer 的处理
4.3.2 redux-action 下载
npm install redux-actions
4.3.3 创建 Action
import { createAction } from 'redux-actions'
const increment_action = createAction('increment');
const decrement_action = createAction('decrement');
4.3.4 创建 Reducer
src/store/actions/counter.actions.js
src/store/actions/counter.actions.js
// 使用 redux-actions
import { createAction } from 'redux-actions'
export const increment = createAction('increment')
export const decrement = createAction('decrement')
src/store/reducers/counter.reducer.js
// src/store/reducers/counter.reducer.js
import { handleActions as createReducer } from 'redux-actions'
import { increment, decrement } from '../actions/counter.actions'
const initialState = {
count: 0,
}
const handleIncrement = (state, action) => ({
count: state.count + action.payload
})
const handleDecrement = (state, action) => ({
count: state.count - action.payload
})
export default createReducer({
[increment]: handleIncrement,
[decrement]: handleDecrement,
}, initialState)
组件使用:
src/components/Counter.js
// src/components/Counter.js
function Counter ({count, increment, decrement}) {
return (
<div>
<button onClick={() => decrement(1)}>-</button>
<span>{count}</span>
<button onClick={() => increment(1)}>+</button>
</div>
)
}
redux-actions 也可以结合在 redux-saga 中使用