七:以理论结合实践方式梳理前端 React 框架 ——— React 状态管理

react 状态机制

虽然 react 前面使用通过创建一个 context【上下链接】对象,基于 Provider【供应者】—— Consumer【消费者】来进行数据的获取,但这种方式局限于只能读取数据,对数据的其他操作还是需要通过 props 属性来操作

props
props
props
props
props
props
props
父组件
子组件
孙组件
重孙组件
重重孙组件
重重重孙组件
第N代孙组件

可以说,这种感觉就像带着女票去宾馆,买的是胶体 BYT,虽然完美的起到了保护措施,解决了后顾之忧,但中间还是有那么一点缺真实感

BYT 的厂家为了给用户多一点真实感,生产出一种液体 BYT,sou~,为了完美的体验,当然首选液体 BYT 啦~


随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state (状态), 这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等

管理不断变化的 state 非常困难,如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么,state 在什么时候,由于什么原因,如何变化已然不受控制, 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰

这里的复杂性很大程度上来自于:总是将两个难以理清的概念混淆在一起:变化和异步。 我称它们为曼妥思和可乐。如果把二者分开,能做的很好,但混到一起,就变得一团糟。一些库如 React 试图在视图层禁止异步和直接操作 DOM 来解决这个问题。美中不足的是,React 依旧把处理 state 中数据的问题留给了你,Redux就是为了帮你解决这个问题

在这里插入图片描述

Components 组件这个客户要去 Store 超市买东西,在超市门口花了一块钱搞了辆购物车 Action,进入超市后,由于超市很大,我们需要去不同产品区 Reducers 找寻所需的商品进行购买

三大原则:

  • 单一数据源:整个应用的 state 被储存在一棵 object tree 中,且只存在于唯一一个 store

  • 状态是只读:唯一改变 state 的方法就是触发 actionaction 是一个用于描述已发生事件的普通对象

  • 纯函数执行:为了描述 action 如何改变 state tree ,你需要编写 reducers

Store 就是把它们联系到一起的对象。Store 有以下职责:

  • 维持应用的 state
  • 提供 getState() 方法获取 state
  • 提供 dispatch(action) 方法更新 state
  • 通过 subscribe(listener) 注册监听器
  • 通过 subscribe(listener) 返回的函数注销监听器

Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state

Action 是把数据从应用传到 store 的有效载荷,它是 store 数据的唯一来源

下面通过 redux 状态管理创建基于 react + antd 框架的 todolist 项目

$ npx create-react-app todo-list
$ cd react-demo
$ npm install antd --save
$ npm install babel-plugin-import --save-dev
$ npm install redux --save
$ npm run eject

调整目录结构,删除不需要的文件,修改根目录下的 package.jsonbabel 处,在 persets 后面添加

|-- config
|-- node_modules
|-- public
|-- script
|-- src
	|-- components
	|-- redux
	|-- app.css
	|-- App.js
	|-- index.js
|-- ...

index.js 代码如下:引入 antd 中文包,并设置 antd 全局中文语言设定

import React from 'react';
import ReactDOM from 'react-dom';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/es/locale/zh_CN';  			// 引入中文包
import App from './App';
ReactDOM.render(
    <React.StrictMode>
        <ConfigProvider locale={zhCN}>
            <App />
        </ConfigProvider>
    </React.StrictMode>,
    document.getElementById('root')
);

配置 package.json 代码如下:设置 antd 按需加载,避免代码臃肿

{
    ......
    "babel": {
        "presets": [
            "react-app"
        ],
        "plugins": [
            [
                "import",
                {
                    "libraryName": "antd",
                    "libraryDirectory": "es",
                    "style": "css"
                }
            ]
        ]
    },
    ......
}

App.js 代码如下:用于查看 antd 是否为 按需加载

import React, { Component } from 'react';
import { Button } from 'antd';
class App extends Component {
    constructor(props) {
        super(props);
        this.state = {  }
    }
    render() { 
        return (
            <div>
                <Button type="primary">Primary Button</Button>
                <Button>Default Button</Button>
                <Button type="dashed">Dashed Button</Button>
                <br />
                <Button type="text">Text Button</Button>
                <Button type="link">Link Button</Button>
            </div>
        );
    }
}
export default App;

在这里插入图片描述


基于页面可以分三个组件:头部 ButtonList 按钮组合组件、中间 TableList 表格列表组件、底部 TotalList 统计数据组件,那么在 components 下创建这三个组件

|-- components
	|-- ButtonList
		|-- index.jsx
	|-- TableList
		|-- index.jsx
	|-- TotalList
		|-- index.jsx

ButtonList/index.jsxTableList/index.jsxTotalList/index.jsxApp.js 代码如下

import React, { useState } from 'react';
import { Modal, Button, Form, Input } from 'antd';
export default function ButtonList (props) {
    const [isModalVisible, setModalVisible] = useState(false);
    const [form] = Form.useForm();
    const showModal = () => {
        setModalVisible(true);
    };
    const hideModal = () => {
        setModalVisible(false);
    };
    const handleOk = () => {
    
    }
    return (
        <>
            <div style={{ position: 'absolute', top: '16px', zIndex: 1 }}>
                <Button type="primary" danger style={{marginRight: '20px'}}>全部删除</Button>
                <Button type="primary" onClick={showModal}>新增</Button>
            </div>
            <Modal title="Basic Modal" visible={isModalVisible} onOk={handleOk} onCancel={hideModal}>
                <Form layout={{ labelCol: { span: 8 }, wrapperCol: { span: 16 } }} form={form} name="control-hooks">
                    <Form.Item name="name" label="姓名" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                    <Form.Item name="yuwen" label="语文" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                    <Form.Item name="shuxue" label="数学" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                    <Form.Item name="yingyu" label="英语" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                </Form>
            </Modal>
        </>
    )
}
import React, { useState } from 'react';
import { Table, Space } from 'antd';
export default function TableList (props) {
    const { selectedRowKeys } = props;
    const columns = [
        {
            title: '姓名',
            dataIndex: 'name',
        },
        {
            title: '语文',
            dataIndex: 'yuwen',
            sorter: {
                compare: (a, b) => a.yuwen - b.yuwen,
                multiple: 3,
            },
        },
        {
            title: '数学',
            dataIndex: 'shuxue',
            sorter: {
                compare: (a, b) => a.shuxue - b.shuxue,
                multiple: 2,
            },
        },
        {
            title: '英语',
            dataIndex: 'yingyu',
            sorter: {
                compare: (a, b) => a.yingyu - b.yingyu,
                multiple: 1,
            },
        },
        {
            title: '操作',
            render: (text, record) => (
                <Space>
                    <a>删除</a>
                </Space>
            ),
        }
    ];
    const rowSelection = {
        selectedRowKeys,
        onChange: selectedRowKeys => {},
    };
    let datalist = [];
    return (
    	<Table 
            columns={columns} dataSource={datalist} 
            pagination={{position: ['topRight', 'none']}} 
            rowSelection={rowSelection} 
        />
    )
}
import { Row, Col } from 'antd';
export default function TotalList (props) {
    const { datalist } = props, len = datalist.length;
    let totalyw = 0, totalsx = 0, totalyy = 0;
    datalist.map(item => {
        totalyw += item.yuwen;
        totalsx += item.shuxue;
        totalyy += item.yingyu;
    })
    return (
        <Row style={{lineHeight: '40px', borderBottom: '1px solid #cccccc'}}>
            <Col span={4} style={{marginRight: '26px', paddingLeft: '20px'}}>平均分</Col>
            <Col span={4} style={{marginRight: '34px'}}>{totalyw / len}</Col>
            <Col span={4} style={{marginRight: '36px'}}>{totalsx / len}</Col>
            <Col span={4}>{totalyy / len}</Col>
            <Col span={4}></Col>
        </Row>
    )
}
import React, { Component } from 'react';
import './app.css';
import TableList from './components/TableList/index';
import ButtonList from './components/ButtonList/index';
import TotalList from './components/TotalList/index';
class App extends Component {
    constructor(props) {
        super(props);
        this.state = { datalist: [], selectedRowKeys: [] };
    }
    render() { 
        const { datalist, selectedRowKeys } = this.state;
        return (
            <div className="view-main">
                <ButtonList datatotal={datalist.length} />
                <TableList datalist={datalist} selectedRowKeys={selectedRowKeys} />
                <TotalList datalist={datalist} />
            </div>
        );
    }
}
export default App;

以上就是通过 antd 生成的 todolist 页面效果,现在在 redux 文件夹内创建 store.jsreducer.jsaction.jsstore.js 是唯一的,用来创建唯一的 Store 对象,传递的一定是一个 reducer 函数

import { createStore } from 'redux';
import reducer from './reducer';
export default createStore(reducer);

reducer.js 是用来解析 store 和更新 store 的函数

const defaultState = {
    datalist: [],				// todolist 中间表格列表数据
    selectedRowKeys: [],        // 用于判定选中项目,方便删除多列判定
};
export default (state = defaultState, action) => {
    // 由于对象是引用数据类型,需要通过深拷贝赋值新对象,而此处解构的原因是为了做深拷贝
    let newState = { ...state };
    return newState;
}

通过设置默认状态数据,查看 redux 配置是否成功

const defaultState = {
    datalist: [
        { name: '小明', yuwen: 87, shuxue: 95, yingyu: 87 },
        { name: '小花', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '小红', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '小李', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '天天', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '菲菲', yuwen: 84, shuxue: 96, yingyu: 92 },
    ],							// todolist 中间表格列表数据
    selectedRowKeys: [],        // 用于判定选中项目,方便删除多列判定
};
export default (state = defaultState, action) => {
    // 由于对象是引用数据类型,需要通过深拷贝赋值新对象,而此处解构的原因是为了做深拷贝
    let newState = { ...state };        
    return newState;
}
import React, { Component } from 'react';
import './app.css';
import store from './redux/store';
import TableList from './components/TableList/index';
import ButtonList from './components/ButtonList/index';
import TotalList from './components/TotalList/index';
class App extends Component {
    constructor(props) {
        super(props);
        let { datalist, selectedRowKeys } = store.getState();
        this.state = { datalist, selectedRowKeys };
    }
    render() { 
        const { datalist, selectedRowKeys } = this.state;
        return (
            <div className="view-main">
                <ButtonList datatotal={datalist.length} />
                <TableList datalist={datalist} selectedRowKeys={selectedRowKeys} />
                <TotalList datalist={datalist} />
            </div>
        );
    }
}
export default App;

通过设置新增状态数据,查看 redux 是否新增成功,状态是否更新

import React, { useState } from 'react';
import { Modal, Button, Form, Input } from 'antd';
export default function ButtonList (props) {
    const [isModalVisible, setModalVisible] = useState(false);
    const [form] = Form.useForm();
    const { dispatch } = props;
    const showModal = () => {
        setModalVisible(true);
    };
    const hideModal = () => {
        setModalVisible(false);
    };
    const handleOk = () => {
        form.validateFields().then(res => {
            dispatch({ type: 'inputItem', value: form.getFieldsValue() });
            setModalVisible(false);
        });
    }
    return (
        <>
            <div style={{ position: 'absolute', top: '16px', zIndex: 1 }}>
                <Button type="primary" danger style={{marginRight: '20px'}} onClick={() => dispatch({ type: 'deleteList' })}>全部删除</Button>
                <Button type="primary" onClick={showModal}>新增</Button>
            </div>
            <Modal title="Basic Modal" visible={isModalVisible} onOk={handleOk} onCancel={hideModal}>
                <Form layout={{ labelCol: { span: 8 }, wrapperCol: { span: 16 } }} form={form} name="control-hooks">
                    <Form.Item name="name" label="姓名" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                    <Form.Item name="yuwen" label="语文" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                    <Form.Item name="shuxue" label="数学" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                    <Form.Item name="yingyu" label="英语" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                </Form>
            </Modal>
        </>
    )
}
import React, { Component } from 'react';
import './app.css';
import store from './redux/store';
import TableList from './components/TableList/index';
import ButtonList from './components/ButtonList/index';
import TotalList from './components/TotalList/index';
class App extends Component {
    constructor(props) {
        super(props);
        let { datalist, selectedRowKeys } = store.getState();
        this.state = { datalist, selectedRowKeys };
    }
    componentDidMount () {
    	// 需要通过 store 的订阅功能进行视图数据更新
        store.subscribe(() => this.setState(store.getState()));
    }
    render() { 
        const { datalist, selectedRowKeys } = this.state;
        return (
            <div className="view-main">
                <ButtonList datatotal={datalist.length} dispatch={store.dispatch} />
                <TableList datalist={datalist} selectedRowKeys={selectedRowKeys} />
                <TotalList datalist={datalist} />
            </div>
        );
    }
}
export default App;

通过 store.dispatch 进行事务的传递,通过 store.subscribe 订阅给界面状态更新数据

const defaultState = {
    datalist: [
        { name: '小明', yuwen: 87, shuxue: 95, yingyu: 87 },
        { name: '小花', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '小红', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '小李', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '天天', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '菲菲', yuwen: 84, shuxue: 96, yingyu: 92 },
    ],
    selectedRowKeys: [],        // 用于判定选中项目,方便删除多列判定
};
export default (state = defaultState, action) => {
    // 由于对象是引用数据类型,需要通过深拷贝赋值新对象,而此处解构的原因是为了做深拷贝
    let newState = { ...state };        
    switch (action.type) {
        case 'inputItem':
            newState.datalist = newState.datalist.concat([action.value]);
        break;
    }
    return newState;
}

通过设置删除状态数据,查看 redux 是否删除成功,状态是否更新

import React, { useState } from 'react';
import { Table, Space } from 'antd';
export default function TableList (props) {
    const { selectedRowKeys, dispatch } = props;
    const columns = [
        {
            title: '姓名',
            dataIndex: 'name',
        },
        {
            title: '语文',
            dataIndex: 'yuwen',
            sorter: {
                compare: (a, b) => a.yuwen - b.yuwen,
                multiple: 3,
            },
        },
        {
            title: '数学',
            dataIndex: 'shuxue',
            sorter: {
                compare: (a, b) => a.shuxue - b.shuxue,
                multiple: 2,
            },
        },
        {
            title: '英语',
            dataIndex: 'yingyu',
            sorter: {
                compare: (a, b) => a.yingyu - b.yingyu,
                multiple: 1,
            },
        },
        {
            title: '操作',
            render: (text, record) => (
                <Space>
                    <a onClick={() => dispatch({ type: 'deleteItem', value: record.key - 1 })}>删除</a>
                </Space>
            ),
        }
    ];
    const rowSelection = {
        selectedRowKeys,
        onChange: selectedRowKeys => {},
    };
    let datalist = props.datalist.map((item, index) => {
        item['key'] = index + 1;
        return item;
    })
    return <Table columns={columns} dataSource={datalist} pagination={{position: ['topRight', 'none']}} rowSelection={rowSelection} />
}
import React, { Component } from 'react';
import './app.css';
import store from './redux/store';
import TableList from './components/TableList/index';
import ButtonList from './components/ButtonList/index';
import TotalList from './components/TotalList/index';
class App extends Component {
    constructor(props) {
        super(props);
        let { datalist, selectedRowKeys } = store.getState();
        this.state = { datalist, selectedRowKeys };
    }
    componentDidMount () {
        // 需要通过 store 的订阅功能进行视图数据更新
        store.subscribe(() => this.setState(store.getState()));
    }
    render() { 
        const { datalist, selectedRowKeys } = this.state;
        return (
            <div className="view-main">
                <ButtonList datatotal={datalist.length} dispatch={store.dispatch} />
                <TableList datalist={datalist} selectedRowKeys={selectedRowKeys} dispatch={store.dispatch} />
                <TotalList datalist={datalist} />
            </div>
        );
    }
}
export default App;
const defaultState = {
    datalist: [
        { name: '小明', yuwen: 87, shuxue: 95, yingyu: 87 },
        { name: '小花', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '小红', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '小李', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '天天', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '菲菲', yuwen: 84, shuxue: 96, yingyu: 92 },
    ],
    selectedRowKeys: [],        // 用于判定选中项目,方便删除多列判定
};
export default (state = defaultState, action) => {
    // 由于对象是引用数据类型,需要通过深拷贝赋值新对象,而此处解构的原因是为了做深拷贝
    let newState = { ...state };        
    switch (action.type) {
        case 'inputItem':
            newState.datalist = newState.datalist.concat([action.value]);
        break;
        case 'deleteItem':
            newState.datalist = newState.datalist.filter((item, index) => index !== action.value);
        break;
        case 'selectKeys':
            newState.selectedRowKeys = action.value
        break;
        case 'deleteList':
            newState.datalist = newState.datalist.filter(item => newState.selectedRowKeys.findIndex(index => index === item.key) === -1);
            newState.selectedRowKeys = [];
        break;
    }
    return newState;
}

在调用事务时,往往因为粗心手打错误了事务名称,在 reducer.js 仅仅是不存在于 switch,并不会报错,这样会导致开发者花费大量的时间找问题,影响开发进度,对于开发者来说,要习惯复制粘贴,最起码的不会导致两边不统一

reducer.js 所有的事务抽离出来,部署到 action.js

export const INPUT_ITEM = 'inputItem';
export const DELETE_ITEM = 'deleteItem';
export const SELECT_KEYS = 'selectKeys';
export const DELETE_LIST = 'deleteList';

通过引入暴露的事务机制对象,在 store 或组件中引入,推荐使用复制粘贴,防止手动输入错误而导致状态管理错误,且不显现错误

import { INPUT_ITEM, DELETE_ITEM, SELECT_KEYS, DELETE_LIST } from './action';
const defaultState = {
    datalist: [
        { name: '小明', yuwen: 87, shuxue: 95, yingyu: 87 },
        { name: '小花', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '小红', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '小李', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '天天', yuwen: 84, shuxue: 96, yingyu: 92 },
        { name: '菲菲', yuwen: 84, shuxue: 96, yingyu: 92 },
    ],
    selectedRowKeys: [],        // 用于判定选中项目,方便删除多列判定
};
export default (state = defaultState, action) => {
    // 由于对象是引用数据类型,需要通过深拷贝赋值新对象,而此处解构的原因是为了做深拷贝
    let newState = { ...state };        
    switch (action.type) {
        case INPUT_ITEM:
            newState.datalist = newState.datalist.concat([action.value]);
        break;
        case DELETE_ITEM:
            newState.datalist = newState.datalist.filter((item, index) => index !== action.value);
        break;
        case SELECT_KEYS:
            newState.selectedRowKeys = action.value
        break;
        case DELETE_LIST:
            newState.datalist = newState.datalist.filter(item => newState.selectedRowKeys.findIndex(index => index === item.key) === -1);
            newState.selectedRowKeys = [];
        break;
    }
    return newState;
}
import React, { useState } from 'react';
import { Modal, Button, Form, Input } from 'antd';
import { INPUT_ITEM, DELETE_LIST } from '../../redux/action';
export default function ButtonList (props) {
    const [isModalVisible, setModalVisible] = useState(false);
    const [form] = Form.useForm();
    const { dispatch } = props;
    const showModal = () => {
        setModalVisible(true);
    };
    const hideModal = () => {
        setModalVisible(false);
    };
    const handleOk = () => {
        form.validateFields().then(res => {
            dispatch({ type: INPUT_ITEM, value: form.getFieldsValue() });
            setModalVisible(false);
        });
    }
    return (
        <>
            <div style={{ position: 'absolute', top: '16px', zIndex: 1 }}>
                <Button type="primary" danger style={{marginRight: '20px'}} onClick={() => dispatch({ type: DELETE_LIST })}>全部删除</Button>
                <Button type="primary" onClick={showModal}>新增</Button>
            </div>
            <Modal title="Basic Modal" visible={isModalVisible} onOk={handleOk} onCancel={hideModal}>
                <Form layout={{ labelCol: { span: 8 }, wrapperCol: { span: 16 } }} form={form} name="control-hooks">
                    <Form.Item name="name" label="姓名" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                    <Form.Item name="yuwen" label="语文" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                    <Form.Item name="shuxue" label="数学" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                    <Form.Item name="yingyu" label="英语" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                </Form>
            </Modal>
        </>
    )
}
import React from 'react';
import { Table, Space } from 'antd';
import { DELETE_ITEM, SELECT_KEYS } from '../../redux/action';
export default function TableList (props) {
    const { selectedRowKeys, dispatch } = props;
    const columns = [
        {
            title: '姓名',
            dataIndex: 'name',
        },
        {
            title: '语文',
            dataIndex: 'yuwen',
            sorter: {
                compare: (a, b) => a.yuwen - b.yuwen,
                multiple: 3,
            },
        },
        {
            title: '数学',
            dataIndex: 'shuxue',
            sorter: {
                compare: (a, b) => a.shuxue - b.shuxue,
                multiple: 2,
            },
        },
        {
            title: '英语',
            dataIndex: 'yingyu',
            sorter: {
                compare: (a, b) => a.yingyu - b.yingyu,
                multiple: 1,
            },
        },
        {
            title: '操作',
            render: (text, record) => (
                <Space>
                    <a onClick={() => dispatch({ type: DELETE_ITEM, value: record.key - 1 })}>删除</a>
                </Space>
            ),
        }
    ];
    const rowSelection = {
        selectedRowKeys,
        onChange: selectedRowKeys => dispatch({ type: SELECT_KEYS, value: selectedRowKeys }),
    };
    let datalist = props.datalist.map((item, index) => {
        item['key'] = index + 1;
        return item;
    })
    return <Table columns={columns} dataSource={datalist} pagination={{position: ['topRight', 'none']}} rowSelection={rowSelection} />
}

react 状态管理

react-redux的两个最主要功能:

  • Provider:提供包含store的context
  • connect:连接容器组件和傻瓜组件;
$ npm install --save react-redux

provider 提供者

将用户状态管理 store 绑定在 index.js 上,这样内部所有的子组件都能通过 connect 获取

import React from 'react';
import ReactDOM from 'react-dom';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/es/locale/zh_CN';  			// 引入中文包
import { Provider } from 'react-redux';
import store from './redux/store';
import App from './App';
ReactDOM.render(
    <React.StrictMode>
        <ConfigProvider locale={zhCN}>
            <Provider store={store}>
                <App />
            </Provider>
        </ConfigProvider>
    </React.StrictMode>,
    document.getElementById('root')
);
import React from 'react';
import './app.css';
import TableList from './components/TableList/index';
import ButtonList from './components/ButtonList/index';
import TotalList from './components/TotalList/index';
function App () {
    return (
        <div className="view-main">
            <ButtonList />
            <TableList />
            <TotalList />
        </div>
    );
}
export default App;

connect 接收两个参数 mapStateToProps 和 mapDispatchToProps,第一次是 connect 函数的执行,第二次是把 connect 函数返回的函数再次执行,最后产生的就是容器组件

  • 把 Store 上的状态转化为内层傻瓜组件的 prop
  • 把内层傻瓜组件中的用户动作转化为派送给 Store 的动作

1)首先渲染列表 TableList

import React from 'react';
import { connect } from 'react-redux';
import { Table, Space } from 'antd';
import { DELETE_ITEM, SELECT_KEYS } from '../../redux/action';
function TableList (props) {
    const { selectedRowKeys, datalist } = props;
    const columns = [
        {
            title: '姓名',
            dataIndex: 'name',
        },
        {
            title: '语文',
            dataIndex: 'yuwen',
            sorter: {
                compare: (a, b) => a.yuwen - b.yuwen,
                multiple: 3,
            },
        },
        {
            title: '数学',
            dataIndex: 'shuxue',
            sorter: {
                compare: (a, b) => a.shuxue - b.shuxue,
                multiple: 2,
            },
        },
        {
            title: '英语',
            dataIndex: 'yingyu',
            sorter: {
                compare: (a, b) => a.yingyu - b.yingyu,
                multiple: 1,
            },
        },
        {
            title: '操作',
            render: (text, record) => (
                <Space>
                    <a onClick={() => props.deleteItem(record.key)}>删除</a>
                </Space>
            ),
        }
    ];
    const rowSelection = {
        selectedRowKeys,
        onChange: selectedRowKeys => props.selectKeys(selectedRowKeys),
    };
    let dataSource = datalist.map((item, index) => {
        item['key'] = index;
        return item;
    });

    return (
    	<Table 
            columns={columns} dataSource={dataSource} 
            pagination={{position: ['topRight', 'none']}} 
            rowSelection={rowSelection} 
        />
    )
}
// 用于接收状态数据,并将状态数据传递给组件
function mapStateToProps (state) {
    return {
        datalist: state.datalist,
    	selectedRowKeys: state.selectedRowKeys
    }
}
// 用于接收状态变更,并将状态变更传递给组件
function mapDispatchToProps (dispatch) {
    return {
        deleteItem: value => dispatch({ type: DELETE_ITEM, value }),
        selectKeys: value => dispatch({ type: SELECT_KEYS, value })
    }
}
// connect 接收的 mapStateToProps, mapDispatchToProps 方法返回的结果后成为一个高阶组件,传递给 TableList 组件
export default connect(mapStateToProps, mapDispatchToProps)(TableList)

2)其次渲染头部 ButtonList

import React, { useState } from 'react';
import { connect } from 'react-redux';
import { Modal, Button, Form, Input } from 'antd';
import { INPUT_ITEM, DELETE_LIST } from '../../redux/action';
function ButtonList (props) {
    const [isModalVisible, setModalVisible] = useState(false);
    const [form] = Form.useForm();
    const showModal = () => {
        setModalVisible(true);
    };
    const hideModal = () => {
        setModalVisible(false);
    };
    const handleOk = () => {
        form.validateFields().then(res => {
            props.inputItem(form.getFieldsValue());
            setModalVisible(false);
        });
    }
    return (
        <>
            <div style={{ position: 'absolute', top: '16px', zIndex: 1 }}>
                <Button type="primary" danger style={{marginRight: '20px'}} onClick={() => props.deleteList()}>全部删除</Button>
                <Button type="primary" onClick={showModal}>新增</Button>
            </div>
            <Modal title="Basic Modal" visible={isModalVisible} onOk={handleOk} onCancel={hideModal}>
                <Form layout={{ labelCol: { span: 8 }, wrapperCol: { span: 16 } }} form={form} name="control-hooks">
                    <Form.Item name="name" label="姓名" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                    <Form.Item name="yuwen" label="语文" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                    <Form.Item name="shuxue" label="数学" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                    <Form.Item name="yingyu" label="英语" rules={[ { required: true } ]} >
                        <Input />
                    </Form.Item>
                </Form>
            </Modal>
        </>
    )
}
// 用于接收状态数据,并将状态数据传递给组件
function mapStateToProps (state) {
    return {
        datalist: state.datalist,
    	selectedRowKeys: state.selectedRowKeys
    }
}
// 用于接收状态变更,并将状态变更传递给组件
function mapDispatchToProps (dispatch) {
    return {
        inputItem: value => dispatch({ type: INPUT_ITEM, value }),
        deleteList: () => dispatch({ type: DELETE_LIST })
    }
}
// connect 接收的 mapStateToProps, mapDispatchToProps 方法返回的结果后成为一个高阶组件,传递给 ButtonList 组件
export default connect(mapStateToProps, mapDispatchToProps)(ButtonList)

3)最后渲染底部 TotalList

import { Row, Col } from 'antd';
import { connect } from 'react-redux';
function TotalList (props) {
    const { datalist } = props, len = datalist.length;
    let totalyw = 0, totalsx = 0, totalyy = 0;
    datalist.map(item => {
        totalyw += item.yuwen;
        totalsx += item.shuxue;
        totalyy += item.yingyu;
    })
    return (
        <Row style={{lineHeight: '40px', borderBottom: '1px solid #cccccc'}}>
            <Col span={4} style={{marginRight: '26px', paddingLeft: '20px'}}>平均分</Col>
            <Col span={4} style={{marginRight: '34px'}}>{totalyw / len}</Col>
            <Col span={4} style={{marginRight: '36px'}}>{totalsx / len}</Col>
            <Col span={4}>{totalyy / len}</Col>
            <Col span={4}></Col>
        </Row>
    )
}
// 用于接收状态数据,并将状态数据传递给组件
function mapStateToProps (state) {
    return {
        datalist: state.datalist,
    	selectedRowKeys: state.selectedRowKeys
    }
}
// connect 接收的 mapStateToProps, mapDispatchToProps 方法返回的结果后成为一个高阶组件,传递给 TotalList 组件
// 由于底部暂未存在状态更新,就可以返回一个空对象即可
export default connect(mapStateToProps, (dispatch) => ({}))(TotalList)

react 两者区别

redux 是 react 中进行 state 状态管理的 JS 库(并不是 react 插件),一般是管理多个组件中共享数据状态

react-redux 是 redux 的官方 react 绑定库。它能够使你的 react 组件从 redux store 中读取数据,并且向 store 分发 actions 以更新数据

  • redux 和组件进行对接的时候是直接在组件中进行创建,react-redux 是运用 Provider 将组件和 store 对接
  • redux 获取 state 是直接通过 store.getState(),react-redux 获取 state 是通过 mapStateToProps 函数,只要 state 数据变化就能获取最新数据
  • redux 是使用 dispatch 直接触发,来操作 store 的数据,react-redux 是使用 mapDispathToProps 函数然后在调用 dispatch 进行触发

React 组件传值

  1. 父子组件传值:父传子,通过 props 属性主动传播,子传父,通过调用父传子方法回调被动传播
  2. 状态提升传值:两个非父子组件之间的传值,通过设定一个共同的父组件,采用通用的父组件 state
  3. 数据共享传值:通过创建一个 context【上下链接】对象,基于 Provider【供应者】—— Consumer【消费者】来进行数据的获取
  4. 路由对象传值:基于 history 监控路由和设置路由,match、location 得到路由参数
  5. 数据状态管理:基于 redux 对业务数据以管理方式进行数据的增、删、改、查处理
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值