• 前端(十):使用redux管理数据


      react本身能够完成动态数据的监听和更新,如果不是必要可以不适用redux。

      安装redux: cnpm install redux --save,或者yarn add redux。

    一、react基本用法

      redux是独立的用于状态管理的第三方包,它建立状态机来对单项数据进行管理。

      上图是个人粗浅的理解。用代码验证一下:

    // src/index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { createStore } from "redux";

    function reducer(state={name: "monkey", age: 5000}, action){
    switch (action.type){
    case "add":
    state.age ++;
    return state;
    case "dec":
    if (state.age <= 4995){
    state.name = "small monkey";
    }
    state.age --;
    return state;
    default:
    return state;
    }
    }
    const store = createStore(reducer);
    const add = {type: "add"}, dec={type: "dec"};


    class App extends React.Component{
    render (){
    const style={
    display: "inline-block",
    "150px",
    height: "40px",
    backgroundColor: "rgb(173, 173, 173)",
    color: "white",
    marginRight: "20px"
    };
    const store = this.props.store;
    // console.log(store.getState());
    const obj = store.getState();
    // console.log(obj);
    return (
    <div>
    <h2>this { obj.name } is { obj.age } years old.</h2>
    <button style={style} onClick={()=>store.dispatch(this.props.add)}>增加一岁</button>
    <button style={style} onClick={()=>store.dispatch(this.props.dec)}>减少一岁</button>
    </div>
    )
    }
    }
    function render() {
    ReactDOM.render(<App store={store} add={ add } dec={ dec } />, document.getElementById('root'));
    }
    render();
    store.subscribe(render);

      因为action必须是个对象,所以只能写成add = {type: "add"}的形式,而不能直接写参数"add"。同样地,在reducer中写switch时将action.type作为参数。

      action和state一一对应,要使用action必须要在reducer里声明。

      redux没有用state来实现动态数据更新,而是通过props来传递数据,因此在组件内部只能通过props获取store,以及store.getState()获取state。

      redux将ReactDOM.render进行了一次封装来设置监听。

      redux对数据和组件进行了解耦,因而可以进行文件拆分。

      把action写成函数的形式:

    // src/index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { createStore } from "redux";

    const Add = "add", Dec="dec";
    function reducer(state={name: "monkey", age: 5000}, action){
    switch (action.type){
    case Add:
    state.age ++;
    if (state.age > 5005){
    state.name = "old monkey";
    }
    return state;
    case Dec:
    if (state.age <= 4995){
    state.name = "small monkey";
    }
    state.age --;
    return state;
    default:
    return state;
    }
    }
    const store = createStore(reducer);
    class App extends React.Component{
    render (){
    const style={
    display: "inline-block",
    "150px",
    height: "40px",
    backgroundColor: "rgb(173, 173, 173)",
    color: "white",
    marginRight: "20px"
    };
    const store = this.props.store;
    const state = store.getState();
    return (
    <div>
    <h2>this { state.name } is { state.age } years old.</h2>
    <button style={style} onClick={()=>store.dispatch(add())}>增加一岁</button>
    <button style={style} onClick={()=>store.dispatch(dec())}>减少一岁</button>
    </div>
    )
    }
    }
    function render() {
    ReactDOM.render(<App store={store} add={ add } dec={ dec } />, document.getElementById('root'));
    }
    render();
    store.subscribe(render);

       或者也可以写成原始的:

    // reducer.js
    const defaultState = {
        inputValue: '',
        list: [1, 2, 3]
    };
    
    // reducer可以接受state,但是无法修改state,所以只能拷贝修改
    export default (state = defaultState, action) => {
        if(action.type === 'change_input_value'){
            const newState = JSON.parse(JSON.stringify(state)); // 深拷贝原来的数据
            newState.inputValue = action.value;
            return newState;
        };
        if(action.type === 'add_item'){
            const newState = JSON.parse(JSON.stringify(state));
            newState.list.push(newState.inputValue);
            return newState;
        }
        if(action.type === 'delete_item'){
            const newState = JSON.parse(JSON.stringify(state));
            newState.list.splice(action.value, 1);
            return newState;
        }
        return state;
    }
    // store.js
    import {createStore} from 'redux';
    import reducer from './reducer';
    const store = createStore(
        reducer,
        window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
        );
    
    export default store;
    // ReduxDemo.js
    import React, {Fragment} from 'react';
    import 'antd/dist/antd.css';
    import { Input, Button, Row, Col, List } from 'antd';
    import store from './store';
    
    class ReduxDemo extends React.Component{
    
        constructor(props){
            super(props);
            this.state = store.getState();
            this.handleBtnClick = this.handleBtnClick.bind(this);
            this.handleInpChange = this.handleInpChange.bind(this);
            this.handleStoreChange = this.handleStoreChange.bind(this);
            this.handleItemDelete = this.handleItemDelete.bind(this);
            // 使用subscribe监听函数时一定要事先绑定到this上
            store.subscribe(this.handleStoreChange);
        }
    
        render(){
            return (
                <Fragment>
                    <Row gutter={24} size="large">
                        <Col span={12} offset={4} style={{padding: "0px"}}>
                            <Input defaultValue='todo info' style={{height:"50px"}} value={this.state.inputValue}
                                   onChange={this.handleInpChange}
                            />
                        </Col>
                        <Col span={2} style={{padding:"0px"}}>
                            <Button
                                type='primary'
                                style={{"100%", height:"50px"}}
                                onClick={this.handleBtnClick}
                            >submit</Button>
                        </Col>
                    </Row>
                    <List
                        bordered={1}
                        dataSource={this.state.list}
                        renderItem={
                            (item, index) => (
                                <List.Item column={12}
                                           onClick={this.handleItemDelete.bind(this,index)}
                                >{item}</List.Item>
                            )
                        }
                    />
                </Fragment>
            )
        }
        handleBtnClick(){
            const action = {
                type: 'add_item'
            };
            store.dispatch(action);
        }
    
        handleInpChange(e) {
            const action = {
                type: 'change_input_value',
                value: e.target.value
            };
            store.dispatch(action);
        }
        handleStoreChange(){
            this.setState(()=>store.getState());
        };
        handleItemDelete(index){
            const action = {
                type: 'delete_item',
                value: index
            };
            store.dispatch(action);     // dispatch被监听函数调用,并将action提交给store,store将上一次的数据状态state和本次的action一起交给reducer去执行逻辑处理
        }
    }
    export default ReduxDemo;

      或者改为:

    // actioncreators
    export const CHANGE_INPUT_VALUE = 'change_input_value';
    export const ADD_ITEM = 'add_item';
    export const DELETE_ITEM = 'delete_item';
    
    export const getInpChangeAction = (value)=>({
            type: CHANGE_INPUT_VALUE,
            value
    });
    export const getAddItemAction = ()=>({
        type: ADD_ITEM,
    });
    export const getDeleteItemAction = (index)=>({
        type: DELETE_ITEM,
        value: index
    });
    // reducers
    import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM} from './actionCreators'
    
    const defaultState = {
        inputValue: '',
        list: [1, 2, 3]
    };
    
    // reducer可以接受state,但是无法修改state,所以只能拷贝修改
    export default (state = defaultState, action) => {
        if(action.type === CHANGE_INPUT_VALUE){
            const newState = JSON.parse(JSON.stringify(state)); // 深拷贝原来的数据
            newState.inputValue = action.value;
            return newState;
        };
        if(action.type === ADD_ITEM){
            const newState = JSON.parse(JSON.stringify(state));
            newState.list.push(newState.inputValue);
            return newState;
        }
        if(action.type === DELETE_ITEM){
            const newState = JSON.parse(JSON.stringify(state));
            newState.list.splice(action.value, 1);
            return newState;
        }
        return state;
    }
    // store
    import {createStore} from 'redux';
    import reducer from './reducer';
    const store = createStore(
        reducer,
        window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
        );
    
    export default store;
    // ReduxDemo
    import React, {Fragment} from 'react';
    import 'antd/dist/antd.css';
    import { Input, Button, Row, Col, List } from 'antd';
    import store from './store';
    import {getAddItemAction, getDeleteItemAction, getInpChangeAction } from './actionCreators';
    
    class ReduxDemo extends React.Component{
    
        constructor(props){
            super(props);
            this.state = store.getState();
            this.handleBtnClick = this.handleBtnClick.bind(this);
            this.handleInpChange = this.handleInpChange.bind(this);
            this.handleStoreChange = this.handleStoreChange.bind(this);
            this.handleItemDelete = this.handleItemDelete.bind(this);
            // 使用subscribe监听函数时一定要事先绑定到this上
            store.subscribe(this.handleStoreChange);
        }
    
        render(){
            return (
                <Fragment>
                    <Row gutter={24} size="large">
                        <Col span={12} offset={4} style={{padding: "0px"}}>
                            <Input defaultValue='todo info' style={{height:"50px"}} value={this.state.inputValue}
                                   onChange={this.handleInpChange}
                            />
                        </Col>
                        <Col span={2} style={{padding:"0px"}}>
                            <Button
                                type='primary'
                                style={{"100%", height:"50px"}}
                                onClick={this.handleBtnClick}
                            >submit</Button>
                        </Col>
                    </Row>
                    <List
                        bordered={1}
                        dataSource={this.state.list}
                        renderItem={
                            (item, index) => (
                                <List.Item column={12}
                                           onClick={this.handleItemDelete.bind(this,index)}
                                >{item}</List.Item>
                            )
                        }
                    />
    
                </Fragment>
            )
        }
        handleBtnClick(){
            const action = getAddItemAction();
            store.dispatch(action);
        }
        handleInpChange(e) {
            const action = getInpChangeAction(e.target.value);
            store.dispatch(action);
        }
        handleStoreChange(){
            this.setState(()=>store.getState());
        };
        handleItemDelete(index){
            const action = getDeleteItemAction(index);
            store.dispatch(action);
        }
    }
    export default ReduxDemo;

    二、UI组件与容器组件拆解

      容器组件负责逻辑处理,UI组件只负责页面显示。基于这样的逻辑,可以将上述的ReduxDemo进行拆解。

    // 容器组件
    import React from 'react';
    import store from './store';
    import ReduxDemoUI from './ReduxDemoUI';
    import {getAddItemAction, getDeleteItemAction, getInpChangeAction } from './actionCreators';
    
    class ReduxDemo extends React.Component{
        constructor(props){
            super(props);
            this.state = store.getState();
            this.handleBtnClick = this.handleBtnClick.bind(this);
            this.handleInpChange = this.handleInpChange.bind(this);
            this.handleStoreChange = this.handleStoreChange.bind(this);
            this.handleItemDelete = this.handleItemDelete.bind(this);
            // 使用subscribe监听函数时一定要事先绑定到this上
            store.subscribe(this.handleStoreChange);
        }
    
        render(){
            return (
                <ReduxDemoUI
                    inputValue={this.state.inputValue}
                    list={this.state.list}
                    handleBtnClick={this.handleBtnClick}
                    handleInpChange={this.handleInpChange}
                    handleStoreChange={this.handleStoreChange}
                    handleItemDelete={this.handleItemDelete}
                />)
        }
        handleBtnClick(){
            const action = getAddItemAction();
            store.dispatch(action);
        }
        handleInpChange(e) {
            const action = getInpChangeAction(e.target.value);
            store.dispatch(action);
        }
        handleStoreChange(){
            this.setState(()=>store.getState());
        };
        handleItemDelete(index){
            const action = getDeleteItemAction(index);
            store.dispatch(action);
        }
    }
    export default ReduxDemo;
    // UI组件
    import React, {Fragment} from 'react';
    import 'antd/dist/antd.css';
    import { Input, Button, Row, Col, List } from 'antd';
    
    class ReduxDemoUI extends React.Component{
        render(){
            return (
                <Fragment>
                    <Row gutter={24} size="large">
                        <Col span={12} offset={4} style={{padding: "0px"}}>
                            <Input defaultValue='todo info' style={{height:"50px"}} value={this.props.inputValue}
                                   onChange={this.props.handleInpChange}
                            />
                        </Col>
                        <Col span={2} style={{padding:"0px"}}>
                            <Button
                                type='primary'
                                style={{"100%", height:"50px"}}
                                onClick={this.props.handleBtnClick}
                            >submit</Button>
                        </Col>
                    </Row>
                    <List
                        bordered={1}
                        dataSource={this.props.list}
                        renderItem={
                            (item, index) => (
                                <List.Item column={12}
                                           onClick={(index) => {this.props.handleItemDelete(index)}}
                                >{item}</List.Item>
                            )
                        }
                    />
    
                </Fragment>
            )
        }
    }
    export default ReduxDemoUI;

       当一个组件只有render函数时,可以改写成无状态组件,无状态组件只是一个函数,因此性能要比UI组件高。

    // 将ReduxDemoUI改写
    import React, {Fragment} from 'react';
    import 'antd/dist/antd.css';
    import { Input, Button, Row, Col, List } from 'antd';
    
    export const ReduxDemoUI = (props) => {
        return (<Fragment>
            <Row gutter={24} size="large">
                <Col span={12} offset={4} style={{padding: "0px"}}>
                    <Input defaultValue='todo info' style={{height: "50px"}} value={props.inputValue}
                           onChange={props.handleInpChange}
                    />
                </Col>
                <Col span={2} style={{padding: "0px"}}>
                    <Button
                        type='primary'
                        style={{ "100%", height: "50px"}}
                        onClick={props.handleBtnClick}
                    >submit</Button>
                </Col>
            </Row>
            <List
                bordered={1}
                dataSource={props.list}
                renderItem={
                    (item, index) => (
                        <List.Item column={12}
                                   onClick={() => {
                                       props.handleItemDelete(index)
                                   }}
                        >{item}</List.Item>
                    )
                }
            />
        </Fragment>)};
    // ReduxDemo只修改导入方式
    // import ReduxDemoUI from './ReduxDemoUI';
    import {ReduxDemoUI} from './ReduxDemoUI';

    三、请求数据渲染

      利用redux+axios发送数据,获取数据并将数据渲染。

    // actionCreators
    export const CHANGE_INPUT_VALUE = 'change_input_value';
    export const ADD_ITEM = 'add_item';
    export const DELETE_ITEM = 'delete_item';
    
    export const AXIO_LIST = 'axios_list'; // 添加状态
    
    export const getInpChangeAction = (value)=>({
            type: CHANGE_INPUT_VALUE,
            value
    });
    export const getAddItemAction = ()=>({
        type: ADD_ITEM,
    });
    export const getDeleteItemAction = (index)=>({
        type: DELETE_ITEM,
        value: index
    });
    
    // 添加action export const getAxiosListAction
    = (list) => ({ type: AXIO_LIST, list });
    // reducers
    import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, AXIO_LIST} from './actionCreators'
    
    const defaultState = {
        inputValue: '',
        list: []
    };
    export default (state = defaultState, action) => {
        if(action.type === CHANGE_INPUT_VALUE){
            const newState = JSON.parse(JSON.stringify(state)); // 深拷贝原来的数据
            newState.inputValue = action.value;
            return newState;
        };
        if(action.type === ADD_ITEM){
            const newState = JSON.parse(JSON.stringify(state));
            newState.list.push(newState.inputValue);
            return newState;
        }
        if(action.type === DELETE_ITEM){
            const newState = JSON.parse(JSON.stringify(state));
            newState.list.splice(action.value, 1);
            return newState;
        }
      // 添加状态处理
    if(action.type === AXIO_LIST){ const newState = JSON.parse(JSON.stringify(state)); newState.list = action.list; return newState; } return state; }
    // ReduxDemo
    
    import React from 'react';
    import store from './store';
    import axios from 'axios';
    import {ReduxDemoUI} from './ReduxDemoUI';
    import {getAddItemAction, getDeleteItemAction, getInpChangeAction, getAxiosListAction } from './actionCreators';
    
    class ReduxDemo extends React.Component{
        constructor(props){
            super(props);
            this.state = store.getState();
            this.handleBtnClick = this.handleBtnClick.bind(this);
            this.handleInpChange = this.handleInpChange.bind(this);
            this.handleStoreChange = this.handleStoreChange.bind(this);
            this.handleItemDelete = this.handleItemDelete.bind(this);
            // 使用subscribe监听函数时一定要事先绑定到this上
            store.subscribe(this.handleStoreChange);
        }
    
        render(){
            return (
                <ReduxDemoUI
                    inputValue={this.state.inputValue}
                    list={this.state.list}
                    handleBtnClick={this.handleBtnClick}
                    handleInpChange={this.handleInpChange}
                    handleStoreChange={this.handleStoreChange}
                    handleItemDelete={this.handleItemDelete}
                />)
        }
        handleBtnClick(){
            const action = getAddItemAction();
            store.dispatch(action);
        }
        handleInpChange(e) {
            const action = getInpChangeAction(e.target.value);
            store.dispatch(action);
        }
        handleStoreChange(){
            this.setState(()=>store.getState());
        };
        handleItemDelete(index){
            const action = getDeleteItemAction(index);
            store.dispatch(action);
        }
       // 发送请求,获取list,并交给store,store将action和list交给reducer处理,然后store在constructor中监听返回
    // 然后storec调用store.dispatch将reducer更新后的数据更新到组件的setState中,随后render方法开始渲染 componentDidMount() { axios({ method:
    'get', url: 'http://localhost:8080/ccts/node/test', responseType: 'json' }).then((response) => { // 请求成功时 const action = getAxiosListAction(response.data.list); store.dispatch(action); }).catch((error) => { console.log(error); }); } } export default ReduxDemo;

      store.js和ReduxDemoUI.js保持不变。启动一个后台服务,这里用java起了一个后台来传递list。注意在package.json中设置proxy为"http://localhost:8080"。

    @CrossOrigin(origins = "*", maxAge = 3600)
    @RestController
    @RequestMapping("/node")
    public class Service {
        @RequestMapping(value = {"/test"}, method = {RequestMethod.GET})
        @ResponseBody
        public String test(HttpServletRequest request){
            // consumes = "application/json")
            Map<String, ArrayList> map = new HashMap<String, ArrayList>(1);
            ArrayList<String> strings = new ArrayList<String>();
            strings.add("张三");
            strings.add("李四");
            strings.add("王二");
            strings.add("麻子");
            map.put("list", strings);
            return JSON.toJSONString(map);
        }
    }

    四、redux异步处理

      当一个父组件中有多个子组件时,如果每一个组件发生状态变化,store都要重新渲染一遍。使用异步可以只重新渲染发生状态变化的组件。

      安装redux和thunk的中间件redux-thunk:cnpm install redux-thunk --save。引入两个函数,并修改createStore如下,其余代码保持不变:

    import { createStore, applyMiddleware } from "redux";
    import thunk from 'redux-thunk';
    const store = createStore(reducer, applyMiddleware(thunk));

      当然还可以使用compose在控制台添加调试功能,前提是这需要在浏览器中安装redux插件,百度下载redux-devtools.crx,并直接拖动到谷歌浏览器扩展程序页面(这就是安装了)。

    import { createStore, applyMiddleware, compose } from "redux";
    const store = createStore(
    reducer,
    compose(
    applyMiddleware(thunk),
    window.devToolsExtension?window.devToolsExtension():f=>f
    )
    );

      最终代码如下:

    import {createStore, applyMiddleware, compose} from 'redux';
    import thunk from  'redux-thunk';
    import reducer from './reducer';
    const store = createStore(
        reducer,
        compose(
            applyMiddleware(thunk),
            window.devToolsExtension?window.devToolsExtension():f=>f
        )
    );
    export default store;

      thunk支持异步请求,它的action可以是一个函数。为了启用异步请求,对请求进行改写。

          thunk中间件指的是store和action之间的中间件。当store.dispatch被执行时,会检测action是一个对象或是一个函数,当是一个函数时,会自动执行这个函数并将结果再交给store。然后和对象那种处理方式一样,store将action交给reducer处理。

    // actionCreators添加如下方法
    export const getAxiosListThunk = () => {
        // 返回一个函数,它可以自动接收一个名为dispatch的方法,处理异步
        return (dispatch) => {
            axios({
                method: 'get',
                url: 'http://localhost:8080/ccts/node/test',
                responseType: 'json'
            }).then((response) => {
                // 请求成功时
                const action = getAxiosListAction(response.data.list);
                dispatch(action); // 只将store.dispatch()改写成了dispatch()
            }).catch((error) => {
                console.log(error);
            });
        };
    };
    // ReduxDemo中修改componnetDidMount
        componentDidMount() {
            const action = getAxiosListThunk();
            store.dispatch(action); // dispatch会自动调用和执行action函数
            // axios({
            //     method: 'get',
            //     url: 'http://localhost:8080/ccts/node/test',
            //     responseType: 'json'
            // }).then((response) => {
            //     // 请求成功时
            //     const action = getAxiosListAction(response.data.list);
            //     store.dispatch(action);
            // }).catch((error) => {
            //     console.log(error);
            // });
        }

    五、组件间与状态机、状态机与状态机

      内层的组件调用dispatch,那么它的父组件一直到最外层组件都需要使用store属性一层一层地传递状态机。

      状态机与状态机之间需要使用redux中的combineReducers合并成一个对象。并由dispatch调用的action来查找其对应的reducer一级返回的state。

      举个例子:

      在src目录下新建index.redux.js文件,建立两个reducer以及各自的action。

    // src/index.redux.js
    const Add = "add", Dec="dec";
    export function reducerOne(state={name: "monkey", age: 5000}, action){
    switch (action.type){
    case Add:
    state.age ++;
    if (state.age > 5005){
    state.name = "old monkey";
    }
    return state;
    case Dec:
    if (state.age <= 4995){
    state.name = "small monkey";
    }
    state.age --;
    return state;
    default:
    return state;
    }
    }
    export function add() {
    return {type: Add}
    }
    export function dec() {
    return {type: Dec}
    }

    export function reducerTwo(state="孙悟空", action){
    if(action.type >= 5005){
    state = "斗战胜佛";
    }else if(action.type <= 4995){
    state = "小猕猴";
    }
    return state;
    }
    export function monkey(number) {
    return {type: number}
    }

      在src/index.js中写入如下代码:

    // src/index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { createStore, applyMiddleware, combineReducers } from "redux";
    import thunk from 'redux-thunk';

    import { reducerOne, reducerTwo, add, dec, monkey } from "./index.redux";

    const reducer = combineReducers({reducerOne, reducerTwo});
    const store = createStore(reducer, applyMiddleware(thunk));

    class Nick extends React.Component{
    render(){
    const store = this.props.store;
    const state = store.getState();
    return <h2 >Now this monkey is so-called { state.reducerTwo }</h2>
    }
    }
    class Monkey extends React.Component{
    render (){
    const style={
    display: "inline-block",
    "150px",
    height: "40px",
    backgroundColor: "rgb(173, 173, 173)",
    color: "white",
    marginRight: "20px"
    };
    const store = this.props.store;
    const state = store.getState();
    const name = state.reducerOne.name, age = state.reducerOne.age;
    return (
    <div>
    <h2>this { name } is { age } years old.</h2>
    <button style={style} onClick={()=>{store.dispatch(add()); store.dispatch(monkey(age))}}>增加一岁</button>
    <button style={style} onClick={()=>{store.dispatch(dec()); store.dispatch(monkey(age))}}>减少一岁</button>
    <Nick store={store} />
    </div>
    )
    }
    }
    function render() {
    ReactDOM.render(<Monkey store={store} add={ add } dec={ dec } />, document.getElementById('root'));
    }
    render();
    store.subscribe(render);

      在Monkey组件中,onClick同时触发两个状态机reducerOne,reducerTwo。Nick组件接收点击事件触发的状态修改之后的store,并从中获取相应的字段进行渲染。在这里需要注意:

      1.使用combineReducers将Monkey复合组件涉及到的状态机进行合并。

      2.多个reducer时,store.getState()获取的是一个对象。可以通过reducer名来获取对应reducer的state。

      3.store和action必须一级一级的往下传递。

    六、使用react-redux(以Monkey和Nick为例)

       react-redux简化了redux在组件的依次传递问题。

      react-redux有两个核心api:Provider和connect。Provider声明所有被其包裹的子组件都可以从其store属性中获取数据,而不必逐次传递。connect允许每个组件跟Provider创建连接来获取数据。

      这里对上面的代码进行修改:

      - src/index.redux.js文件内容如下:reducer中的state此时必须重新定义,不能直接再返回原来的state。然后微调了一下显示的数据。

    // src/index.redux.js
    const Add = "add", Dec="dec";
    export function reducerOne(state={name: "monkey", age: 5000}, action){
    switch (action.type){
    case Add:
    if (state.age >= 5004){
    state.name = "old monkey";
    }else if(state.age > 4994 && state.age < 5004){
    state.name = "monkey";
    }
    return {...state, age: state.age+1};
    case Dec:
    if (state.age <= 4996){
    state.name = "small monkey";
    }else if(state.age > 4996 && state.age <= 5005){
    state.name = "monkey";
    }
    return {...state, age: state.age-1};
    default:
    return state;
    }
    }
    export function add() {
    return {type: Add}
    }
    export function dec() {
    return {type: Dec}
    }
    export function reducerTwo(state="孙悟空", action){
    if(action.type >= 5004){
    return "斗战胜佛";
    }else if(action.type <= 4996){
    return "小猕猴";
    }
    return state;
    }
    export function monkey(number) {
    return {type: number}
    }

      - src/index.js内容如下:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import { createStore, applyMiddleware, combineReducers } from "redux";
    import thunk from 'redux-thunk';
    import { Provider, connect } from 'react-redux';

    import { reducerOne, reducerTwo, add, dec, monkey } from "./index.redux";

    const reducer = combineReducers({reducerOne, reducerTwo});
    const store = createStore(reducer, applyMiddleware(thunk));

    class Nick extends React.Component{
    render(){
    return <h2 >Now this monkey is so-called { this.props.nick }</h2>
    }
    }
    class Monkey extends React.Component{
    render (){
    const style={
    display: "inline-block",
    "150px",
    height: "40px",
    backgroundColor: "rgb(173, 173, 173)",
    color: "white",
    marginRight: "20px"
    };
    return (
    <div>
    <h2>this { this.props.reducerOne.name } is { this.props.reducerOne.age } years old.</h2>
    <button style={style} onClick={()=>{this.props.add(); this.props.monkey(this.props.reducerOne.age)}}>增加一岁</button>
    <button style={style} onClick={()=>{this.props.dec();this.props.monkey(this.props.reducerOne.age)}}>减少一岁</button>
    <Nick nick={ this.props.reducerTwo } />
    </div>
    )
    }
    }
    const mapStateProps = state=>{
    return { reducerOne: state.reducerOne, reducerTwo: state.reducerTwo }
    };
    const actionCreator = { add, dec, monkey };
    Monkey = connect(mapStateProps, actionCreator)(Monkey); // 其实是加了一层装饰器


    function render() {
    ReactDOM.render(
    <Provider store={store}>
    <Monkey/>
    </Provider>
    , document.getElementById('root'));
    }
    render();

      首先,从react-redux中引入Provider,用来包装app Monkey,然后将store传递给Provider即可。

      其次,用装饰器对app Monkey进行装饰,并传递进去两个参数,第一个参数是组件所需要的reducer中的状态(这里把两个reducer的状态全部写了进来),第二个参数是action指令。connect函数会根据将reducer和action一一对应起来,所以后面直接用action函数即可。

      再次,onClick绑定action只需要写成onClick={this.props.action}即可,这里需要变更两个状态,所以用箭头函数包裹了一下。

      connect的这种写法看起来不爽,可以用装饰器的形式写。但是需要做一下babel配置:

    # 1.生成配置文件
    cnpm run eject
    # 2.安装插件
    cnpm install babel-plugin-transform-decorators-legacy --save
    # 3.在package.json中的"babel"中做如下配置:
    src/package.json
    "babel": {
    "presets": [
    "react-app"
    ],
    "plugins":[
    "transform-decorators-legacy"
    ]
    },

      此时将装饰器的那三行代码改为一行,写在Monkey类上面即可。

    @connect(state=>({ reducerOne: state.reducerOne, reducerTwo: state.reducerTwo }), { add, dec, monkey })
    class Monkey extends React.Component{
      ...
    }

      备注:react-16.6.3,babel-7.0以上安装需要做如下配置。

    1.npm run eject
    2.安装插件
    cnpm install babel-plugin-transform-decorators-legacy --save
    cnpm install --save-dev babel-preset-env
    cnpm install --save-dev babel-plugin-transform-class-properties
    cnpm install --save-dev @babel/plugin-transform-react-jsx
    cnpm install --save-dev @babel/plugin-transform-react-jsx-source
    cnpm install --save-dev @babel/plugin-transform-react-jsx-self
    cnpm install --save-dev @babel/plugin-proposal-decorators
    3.修改package.json
    "babel": {
        "presets": [
          "react-app"
        ],
        "plugins": [
          [
            "@babel/plugin-proposal-decorators",
            {
              "legacy": true
            }
          ]
        ]
    }

    七、使用React-Redux(以ReduxDemo为例)

      保持reducers.js、store.js、actionCreators.js不变,改写ReduxDemo.js和index.js,删除ReduxDemoUI.js。

    // reducers.js
    import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, AXIO_LIST} from './actionCreators'
    
    const defaultState = {
        inputValue: '',
        list: []
    };
    export default (state = defaultState, action) => {
        if(action.type === CHANGE_INPUT_VALUE){
            const newState = JSON.parse(JSON.stringify(state)); // 深拷贝原来的数据
            newState.inputValue = action.value;
            return newState;
        };
        if(action.type === ADD_ITEM){
            const newState = JSON.parse(JSON.stringify(state));
            newState.list.push(newState.inputValue);
            return newState;
        }
        if(action.type === DELETE_ITEM){
            const newState = JSON.parse(JSON.stringify(state));
            newState.list.splice(action.value, 1);
            return newState;
        }
        if(action.type === AXIO_LIST){
            const newState = JSON.parse(JSON.stringify(state));
            newState.list = action.list;
            return newState;
        }
        return state;
    }
    // store.js
    import {createStore, applyMiddleware, compose} from 'redux';
    import thunk from  'redux-thunk';
    import reducer from './reducer';
    const store = createStore(
        reducer,
        compose(
            applyMiddleware(thunk),
            window.devToolsExtension?window.devToolsExtension():f=>f
        )
    );
    export default store;
    import axios from "axios";
    
    export const CHANGE_INPUT_VALUE = 'change_input_value';
    export const ADD_ITEM = 'add_item';
    export const DELETE_ITEM = 'delete_item';
    
    export const AXIO_LIST = 'axios_list';
    
    export const getInpChangeAction = (value)=>({
            type: CHANGE_INPUT_VALUE,
            value
    });
    export const getAddItemAction = ()=>({
        type: ADD_ITEM,
    });
    export const getDeleteItemAction = (index)=>({
        type: DELETE_ITEM,
        value: index
    });
    
    export const getAxiosListAction = (list) => ({
        type: AXIO_LIST,
        list
    });
    
    export const getAxiosListThunk = () => {
        // 返回一个函数,它可以自动接收一个名为dispatch的方法,处理异步
        return (dispatch) => {
            axios({
                method: 'get',
                url: 'http://localhost:8080/ccts/node/test',
                responseType: 'json'
            }).then((response) => {
                // 请求成功时
                const action = getAxiosListAction(response.data.list);
                dispatch(action);
            }).catch((error) => {
                console.log(error);
            });
        };
    };
    // ReduxDemo.js
    import React, {Fragment} from 'react';
    import 'antd/dist/antd.css';
    import { Input, Button, Row, Col, List } from 'antd';
    import { connect } from 'react-redux';
    import {getAddItemAction, getDeleteItemAction, getInpChangeAction, getAxiosListThunk } from './actionCreators';
    
    class ReduxDemo extends React.Component{
        render(){
            return (
                <Fragment>
                    <Row gutter={24} size="large">
                        <Col span={12} offset={4} style={{padding: "0px"}}>
                            <Input defaultValue='todo info' style={{height: "50px"}} value={this.props.inputValue}
                                   onChange={(e) => {this.props.getInpChangeAction(e.target.value)}}
                            />
                        </Col>
                        <Col span={2} style={{padding: "0px"}}>
                            <Button
                                type='primary'
                                style={{ "100%", height: "50px"}}
                                onClick={() => {this.props.getAddItemAction()}}
                            >submit</Button>
                        </Col>
                    </Row>
                    <List
                        bordered={1}
                        dataSource={this.props.list}
                        renderItem={
                            (item, index) => (
                                <List.Item column={12}
                                           onClick={() => {
                                               this.props.getDeleteItemAction(index)
                                           }}
                                >{item}</List.Item>
                            )
                        }
                    />
                </Fragment>)
        }
        componentDidMount() {
            this.props.getAxiosListThunk();
        }
    }
    
    // 接收一个固定的state参数,并返回一个对象
    // connect -> props和store做连接,将store中的数据映射到props属性中去
    const mapStateProps = (state) => {
        return { inputValue: state.inputValue, list: state.list }
    };
    // 同样地,将store中的action映射到props属性中去
    const mapActionProps = {
            getAddItemAction,
            getDeleteItemAction,
            getInpChangeAction,
            getAxiosListThunk
    };
    
    ReduxDemo = connect(mapStateProps, mapActionProps)(ReduxDemo);
    export default ReduxDemo;
    // index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import {Provider} from 'react-redux';
    
    import store from './Boom09/store';
    import ReduxDemo from './Boom09/ReduxDemo';
    
    const App = (
        <Provider store={store}>
            <ReduxDemo />
        </Provider>
    );
    
    ReactDOM.render(App, document.getElementById('root'));

    四、react-router-dom

      react-router-dom是专门用于处理路由的组件。

      BrowserRouter用以包裹app,它会在this.props添加一些环境参数,这非常用。

      Route用于将路由和组件绑定,一个路由对应一个页面(组件),或者Link。其重要的两个属性为path和component,path可以接收对象,也可以接收一个url字符串。

      Link用于将链接和Route进行绑定。

      Switch用于当前路由下的所有子路由,一般和Redirect配合使用,用于重定向那些未定义的子路由。

      Redirect:重定向。其重要的属性为to,只能指向一个url字符串。

      用例一: Route + Componnet + Redirect

      1.建立两个路由指向两个组件:Login和Home, 以及一个用户登录状态的reducer。

      2.进入时首先重定向到Home页面.Home组件判断用户登录状态,登录则打印登录信息并提供注销按钮,点击按钮会重定向到Login页面;未登录则直接重定向到Login页面。

      3.用户登录则重定向到Home页面,未登录则打印登录提示并提供登录按钮,点击按钮会重定向到Home页面。

    // src/index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { createStore, applyMiddleware } from "redux";
    import thunk from 'redux-thunk';
    import { Provider } from 'react-redux';
    import {BrowserRouter, Route, Switch, Redirect } from 'react-router-dom';

    import { reducer } from "./reducer";
    import { Login } from "./Login";
    import { Home } from "./Home";

    const store = createStore(reducer, applyMiddleware(thunk));
    ReactDOM.render(
    (<Provider store={store}>
    <BrowserRouter>
    <Switch>
    <Route path="/login" component={ Login } />
    <Route path="/monkey" component={ Home } />
    <Redirect to={{ pathname: "/monkey" }}/>
    </Switch>
    </BrowserRouter>

    </Provider>),
    document.getElementById('root')
    );
    // src/Home.js
    import React from "react";
    import {connect} from 'react-redux';
    import { Redirect } from 'react-router-dom';
    import {logout} from "./reducer";

    @connect(state=>state, {logout})
    class Home extends React.Component{
    render(){
    const app = (
    <div>
    <h2>这是home界面</h2>
    <button onClick={this.props.logout}>点击注销</button>
    </div>
    );
    return this.props.isLogin ? app : <Redirect to={{ pathname: "/login"}} />
    }
    }
    export { Home }
    // src/Login.js
    import React from 'react';
    import {connect} from 'react-redux';
    import {Redirect} from 'react-router-dom';
    import {login} from "./reducer";

    @connect(state=>state, {login})
    class Login extends React.Component{
    render(){
    console.log(this.props);
    const app = (
    <div>
    <h2>请先登录!</h2>
    <button onClick={this.props.login}>点击登录</button>
    </div>
    );
    return this.props.isLogin ? <Redirect to={{pathname: "/home"}} /> : app
    }
    }
    export { Login }
    // src/reducer.js
    // 用户登录状态
    const LOGIN = "login";
    const LOGOUT = "logout";

    export function reducer(state={isLogin: false, user: "Li"}, action) {
    switch (action.type){
    case LOGIN:
    return {...state, isLogin:true};
    case LOGOUT:
    return {...state, isLogin:false};
    default:
    return state
    }
    }

    export function login() {
    return {type: LOGIN}
    }
    export function logout() {
    return {type: LOGOUT}
    }

      用例二:Route + Component + Link

    // src/index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import {BrowserRouter, Route, Switch, Redirect, Link } from 'react-router-dom';


    function LinkOne(){
    return <h2>我是LineOne组件,被Route绑定给了一个Link链接</h2>
    }
    function LinkTwo() {
    return <h2>我是LineThree组件,被Route绑定给了一个Link链接</h2>
    }
    function LinkThree(){
    return <h2>我是LineThree组件,被Route绑定给了一个Link链接</h2>
    }
    function Test(){
    return <h2>这是无效的路由,你不要搞事情!</h2>
    }

    ReactDOM.render(
    (<BrowserRouter>
    <div>
    <ul>
    <li><Link to="/">LinkOne</Link></li>
    <li><Link to="/two">LinkTwo</Link></li>
    <li><Link to="/three">LinkThree</Link></li>
    </ul>
    <Switch>
    <Route path="/" exact component={ LinkOne } />
    <Route path="/two" component={ LinkTwo } />
    <Route path="/three" component={ LinkThree } />
    <Route path='/:location' component={ Test } />
    </Switch>
    </div>
    </BrowserRouter>),
    document.getElementById('root')
    );

     

  • 相关阅读:
    java中过滤器和拦截器的区别
    Java中内部类和静态内部类的区别
    SpringBoot启动的时候切换配置文件命令
    centos7安装Subversion
    关于curl_setopt参数的记录
    Linux Samba文件共享服务,安装与案例配置
    CentOS7源码安装Redis5.0.4非关系型数据库
    Centos7部署LAMP平台之架构之路
    Centos7安装及配置DHCP服务
    CentOS 7源码安装MYSQL-5.6
  • 原文地址:https://www.cnblogs.com/kuaizifeng/p/10161452.html
Copyright © 2020-2023  润新知