react从0开发的完整过程 --- 2

332 阅读19分钟

后台框架搭建、登录跳转路由转入后台

图片.png

私有化组件、存储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;

图片.png

功能: 页面刷新的之后,侧边栏依然保持高亮

第一步: 给,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,扩展

图片.png

功能点:token得数据保存

www.npmjs.com/package/rea…

图片.png

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;

图片.png

图片.png

图片.png

图片.png

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一定要绑定的

Redux4大核心:
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

图片.png

Reducer

export default function(state=initialState, action) {
  switch (action.type) {
    caseADD_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 中使用