React-Redux学习手册(1)—详述React-Redux库基本概念、Redux DevTools extension插件、中间件及基本使用等原理(附详细案例代码解析过程)

1. 重点提炼

  • react 与 redux 的配合
  • react-redux 库
    • provider 组件:通过 provider 组件的 store 属性注入状态数据(注入到应用中)
    • connect 方法:包装组件,把 store 中的,dispatchstateprops 等注入到组件的props 属性下面
    • Redux DevTools extension:浏览器插件,对 store 进行开发调试
    • middleware(可为每次的请求和响应进行扩展):为 dispatch(提交action对象) 提供更多的扩展(如一些日志记录)
      • redux-chunk:对 dispatch 进行功能扩展,使其能够处理异步任务(函数)
      • logger (日志)中间件
  • (提问题的关注点)我想要什么?我做了什么?现在出现了什么问题?

2. example01

引例

2.1 example01-1

框子

App.js

import React from 'react';
import List from './components/List'
 
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {items : [
                {
                    id: 1,
                    name: 'iPhone XR',
                    price: 542500
                },
                {
                    id: 2,
                    name: 'Apple iPad Air 3',
                    price: 377700
                },
                {
                    id: 3,
                    name: 'Macbook Pro 15.4',
                    price: 1949900
                },
                {
                    id: 4,
                    name: 'Apple iMac',
                    price: 1629900
                },
                {
                    id: 5,
                    name: 'Apple Magic Mouse',
                    price: 72900
                },
                {
                    id: 6,
                    name: 'Apple Watch Series 4',
                    price: 599900
                }
            ]}
    }
 
    render() {
        return(
            <div>
                <List data={this.state.items}/>
            </div>
        )
    }
 
}
 
export default App;

components/List.js

import React from 'react';
 
export default class List extends React.Component {
 
    render() {
 
        // 当前这个组件希望接受items数据
        console.log(this.props.data)
 
        return(
            <div>
            </div>
        );
 
    }
 
}

image-20200609160757690

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.01-1
Branch:branch2

commit description:v0.01-1-example01-1(引例—框子)

tag:v0.01-1

2.2 example01-2

框子

React-Redux01\app\src\components\Item.js

import React from 'react';

class Item extends React.Component {

    constructor(props) {
        super(props);
    }



    render() {
        // 当前这个组件也希望接受items数据
        console.log(this.props.datas)

        return(
            <div>

            </div>
        );

    }

}

export default Item;

React-Redux01\app\src\components\List.js

import React from 'react';
import Item from "./Item";

export default class List extends React.Component {

    render() {

        // 当前这个组件希望接受items数据
        console.log(this.props.data)

        return(
            <div>
                <Item datas={this.props.data}/>
            </div>
        );

    }

}

image-20200609164257434

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.01-2
Branch:branch2

commit description:v0.01-2-example01-2(引例—框子)

tag:v0.01-2

2.3 example01-3

数据是可以通过 props方式传递个子组件,但是如果我们的应用的组件嵌套层级比较多,那么这个时候,数据的传递将会变成灾难。怎么解决呢?

把数据存储在一个大家都能很方便(很直接)就能访问的位置

  • localStorage(内存限制:5MB左右)
  • 后端

App.js

import React from 'react';
import List from './components/List'
 

class App extends React.Component {
 
  constructor(props) {
    super(props);
 
    this.state = {
      items: [
        {
          id: 1,
          name: 'iPhone XR',
          price: 542500
        },
        {
          id: 2,
          name: 'Apple iPad Air 3',
          price: 377700
        },
        {
          id: 3,
          name: 'Macbook Pro 15.4',
          price: 1949900
        },
        {
          id: 4,
          name: 'Apple iMac',
          price: 1629900
        },
        {
          id: 5,
          name: 'Apple Magic Mouse',
          price: 72900
        },
        {
          id: 6,
          name: 'Apple Watch Series 4',
          price: 599900
        }
      ]
    }
    // 数据存在本地
    localStorage.setItem('items', JSON.stringify(this.state.items));
  }
 
  render() {
    return(
        <div>
          <List />
        </div>
    )
  }
 
}
 
export default App;

src/components/List.js

import React from 'react';
import Item from './Item';
 
export default class List extends React.Component {
 
    render() {
        let items = localStorage.getItem('items');
        // items如果存在,就将其转为对象
        items = items && JSON.parse(items);
        console.log(items);
 
        return(
            <div>
                <Item />
            </div>
        );
 
    }
 
}

src/components/Item.js

import React from 'react';
 
export default class Item extends React.Component {
 
    render() {
 
        // 当前这个组件也希望接受 items 数据
        // console.log(this.props.datas);
 
        let items = localStorage.getItem('items');
        items = items && JSON.parse(items);
        console.log(items);
 
        return(
            <div>
 
            </div>
        );
 
    }
 
}

image-20200609164257434

image-20200609170517188

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.01-3
Branch:branch2

commit description:v0.01-3-example01-3(引例—localStorage存放数据)

tag:v0.01-3

  • 把数据存储在一个大家都能很方便(很直接)就能访问的位置
    • localStorage(内存限制:5MB左右)
      • 虽然我们可以通过 localStorage 来存储数据,但是,数据的操作是不是很随意的(很不安全!一不小心可能就把数据给改了,其他组件使用的时候,很容易挂掉。)
    • 后端(也可存在后端)
    • 因此我们需要寻找更好的办法:(redux完美符合)
      • 能够共享数据
      • 能够管理数据

2.4 example01-4

我们使用redux:

npm i redux

src/store/reducer/users.js

let users = [{
    id: 1,
    username: 'zs',
    password: '123'
},
{
    id: 2,
    username: 'ls',
    password: '123'
},
{
    id: 3,
    username: 'ww',
    password: '123'
},
{
    id: 4,
    username: 'xm',
    password: '123'
}];
 
export default (state = users, action) => {
    switch (action.type) {
        default:
            return state;
    }
}

src/store/reducer/items.js

let items = [
    {
        id: 1,
        name: 'iPhone XR',
        price: 542500
    },
    {
        id: 2,
        name: 'Apple iPad Air 3',
        price: 377700
    },
    {
        id: 3,
        name: 'Macbook Pro 15.4',
        price: 1949900
    },
    {
        id: 4,
        name: 'Apple iMac',
        price: 1629900
    },
    {
        id: 5,
        name: 'Apple Magic Mouse',
        price: 72900
    },
    {
        id: 6,
        name: 'Apple Watch Series 4',
        price: 599900
    }
];
 
export default (state = items, action) => {
    switch (action.type) {
        default:
            return state;
    }
}

src/store/index.js

/**
* 使用 redux 来管理数据
* */
import {createStore, combineReducers} from 'redux';
 
import users from './reducer/users';
import items from './reducer/items';
 
const store = createStore(
    combineReducers({
        users,
        items
    })
);
 
export default store;

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import store from "./store";

console.log(store.getState());

ReactDOM.render(
    <App />,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

我们可在整个应用的最顶层index里导入:

image-20200609225911679

我希望在listitem中都能用这个数据,我们如何去取呢?直接导入即可。

src/components/List.js

import React from 'react';
import Item from "./Item";
import store from "../store";

export default class List extends React.Component {

    render() {

        console.log(store.getState());

        return(
            <div>
                <Item datas={this.props.data}/>
            </div>
        );

    }

}

image-20200609230409057

我们其实很容易发现redux实际就是localstorage的变体版,无非是存储的位置不一样,这里是往内存中存储的,通过一个变量去管理这些数据的,而刚刚是存在localstorage中的。实际上后端道理也一样,只不过数据交给后端维护了而已。

src/components/Item.js

import React from 'react';
// 引用的路径会随着当前文件的位置变化而变化(如果组件嵌套非常复杂,后期修改这个引用位置变化会很麻烦,不太方便引用管理)
// 如果想更好的解决这个问题,就用react-redux
import store from "../store";
 
export default class Item extends React.Component {
 
    render() {
 
        console.log(store.getState());
 
        return(
            <div>
            </div>
        );
 
    }
 
}

image-20200609230857552

引用的路径会随着当前文件的位置变化而变化(如果组件嵌套非常复杂,后期修改这个引用位置变化会很麻烦,不太方便引用管理)
如果想更好的解决这个问题,就用react-redux

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.01-4
Branch:branch2

commit description:v0.01-4-example01-4(引例—redux管理数据)

tag:v0.01-4

  • redux会存在一些问题
    • 引用的路径会随着当前文件的位置变化而变化(如果组件嵌套非常复杂,后期修改这个引用位置变化会很麻烦,不太方便引用管理)
    • 更新问题,我们举一个例子:

2.5 example01-5

React+Redux更新问题

React-Redux01\app\src\components\Item.js

import React from 'react';
import store from "../store";

class Item extends React.Component {

    constructor(props) {
        super(props);
    }
    
    render() {
        let {items} = store.getState();

        return(
            <div>
                <ul>
                    {
                        items.map(item => {
                            return(
                                <li key={item.id}>{item.name}</li>
                            )
                        })
                    }
                </ul>
            </div>
        );

    }

}

export default Item;
image-20200609233045487

做一个添加功能:

store\reducer\items.js

let items = [
    {
        id: 1,
        name: 'iPhone XR',
        price: 542500
    },
    {
        id: 2,
        name: 'Apple iPad Air 3',
        price: 377700
    },
    {
        id: 3,
        name: 'Macbook Pro 15.4',
        price: 1949900
    },
    {
        id: 4,
        name: 'Apple iMac',
        price: 1629900
    },
    {
        id: 5,
        name: 'Apple Magic Mouse',
        price: 72900
    },
    {
        id: 6,
        name: 'Apple Watch Series 4',
        price: 599900
    }
];

let maxId = 6; // 当前的id

export default (state = items, action) => {
    switch (action.type) {
        case 'ADD_ITEM':
            return [...state, {
                id: ++maxId,
                name: action.payload.name,
                price: 10000
            }];
        default:
            return state;
    }
}

components\Item.js

import React from 'react';
import store from "../store";

class Item extends React.Component {

    constructor(props) {
        super(props);

        this.addNewItem = this.addNewItem.bind(this);
    }

    addNewItem() {
        let val = this.el.value;
        // console.log(val);

        store.dispatch({
            type: 'ADD_ITEM',
            payload: {
                name: val
            }
        });

        this.el.value = '';

        // 如果直接使用 store ,会绕开组件的更新,因为它既不是 state的变化,也不是 props的变化
        console.log(store.getState());
    }

    render() {
        let {items} = store.getState();

        return(
            <div>
                <input type="text" ref={el => {
                    this.el = el;
                }}/> <button onClick={this.addNewItem}>添加</button>

                <ul>
                    {
                        items.map(item => {
                            return(
                                <li key={item.id}>{item.name}</li>
                            )
                        })
                    }
                </ul>
            </div>
        );

    }

}

export default Item;

我们打印发现store中数据增加了,但是页面还未有反应。

如果直接使用 store ,会绕开组件的更新,因为它既不是 state的变化,也不是props的变化。

image-20200610102807223

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.01-5
Branch:branch2

commit description:v0.01-5-example01-5(引例—redux管理数据—数据增加无法自动触发视图更新)

tag:v0.01-5

2.6 example01-6

我们需要在state中保存一下,每次更新再重新赋值给state,这样操作会麻烦一些,有没有更加好的解决办法呢?

React-Redux01\app\src\components\Item.js

import React from 'react';
import store from "../store";

class Item extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            items: store.getState()
        }

        this.addNewItem = this.addNewItem.bind(this);
    }

    addNewItem() {
        let val = this.el.value;
        // console.log(val);

        store.dispatch({
            type: 'ADD_ITEM',
            payload: {
                name: val
            }
        });

        this.el.value = '';

        // 如果直接使用 store ,会绕开组件的更新,因为它既不是 state的变化,也不是 props的变化
        console.log(store.getState());

        this.setState(store.getState())
    }

    render() {
        let {items} = store.getState();

        return(
            <div>
                <input type="text" ref={el => {
                    this.el = el;
                }}/> <button onClick={this.addNewItem}>添加</button>

                <ul>
                    {
                        items.map(item => {
                            return(
                                <li key={item.id}>{item.name}</li>
                            )
                        })
                    }
                </ul>
            </div>
        );

    }

}

export default Item;

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.01-6
Branch:branch2

commit description:v0.01-6-example01-6(引例—redux管理数据—数据增加实现触发视图更新)

tag:v0.01-6

3. react-redux

再次强调的是,reduxreact 并没有直接关系,它是一个独立的 JavaScript 状态管理库,如果我们希望中 React 中使用 Redux,需要先安装 react-redux

3.1 react-redux

它解决了:1、引用问题(不用每次还得找store,项目结构复杂,对后面的维护造成很多时间上的消耗) 2、更新问题(不用再自己给state赋值更新视图了。原理其实是我们上会说到redeuxsubscribe() 方法会监听数据变化,然后处理组件的更新问题,而不是调用组件的render

3.1.1 安装

npm i -S redux react-redux
// ./store/reducer/user.js
let user = {
    id: 0,
    username: ''
};
 
export default (state = user, action) => {
    switch (action.type) {
        default:
            return state;
    }
}
// ./store/reducer/users.js
let users = [{
    id: 1,
    username: 'zs',
    password: '123'
},
{
    id: 2,
    username: 'ls',
    password: '123'
},
{
    id: 3,
    username: 'ww',
    password: '123'
},
{
    id: 4,
    username: 'xm',
    password: '123'
}];
 
export default (state = users, action) => {
    switch (action.type) {
        default:
            return state;
    }
}
// ./store/reducer/items.js
let items = [
    {
        id: 1,
        name: 'iPhone XR',
        price: 542500
    },
    {
        id: 2,
        name: 'Apple iPad Air 3',
        price: 377700
    },
    {
        id: 3,
        name: 'Macbook Pro 15.4',
        price: 1949900
    },
    {
        id: 4,
        name: 'Apple iMac',
        price: 1629900
    },
    {
        id: 5,
        name: 'Apple Magic Mouse',
        price: 72900
    },
    {
        id: 6,
        name: 'Apple Watch Series 4',
        price: 599900
    }
];
 
export default (state = items, action) => {
    switch (action.type) {
        default:
            return state;
    }
}
// ./store/reducer/cart.js
export default (state = [], action) => {
    switch (action.type) {
        default:
            return state;
    }
}
// ./store/index.js
import {createStore, combineReducers} from 'redux';
 
import user from './reducer/user';
import users from './reducer/users';
import items from './reducer/items';
import cart from './reducer/cart';
 
let reducers = combineReducers({
    user,
    users,
    items,
    cart
});
 
const store = createStore(reducers);
 
export default store;

3.1.2 Provider 组件

想在 React 中使用 Redux ,还需要通过 react-redux 提供的 Provider 容器组件把 store 注入到应用中

// index.js
import {Provider} from 'react-redux';
import store from './store';
 
ReactDOM.render(
    <Provider store={store}>
        <Router>
            <App />
        </Router>
    </Provider>,
    document.getElementById('root')
);

3.1.3 connect 方法

有了 connect 方法,我们不需要通过 props 一层层的进行传递, 类似路由中的 withRouter ,我们只需要在用到 store 的组件中,通过 react-redux 提供的 connect 方法,把 store 注入到组件的 props 中就可以使用了

import {connect} from 'react-redux';
 
class Main extends React.Component {
  render() {
    console.log(this.props);
  }
}
 
export default connect()(Main);

默认情况下,connect 会自动注入 dispatch 方法

3.1.3.1 注入 stateprops
export default connect( state => {
  return {
    items: state.items
  }
} )(Main);

connect 方法的第一个参数是一个函数

  • 该函数的第一个参数就是 store 中的 state : store.getState()
  • 该函数的返回值将被解构赋值给 props : this.props.items
3.1.3.2 example02

引入React-Redux

3.1.3.2.1 example02-1

Provider组件是容器组件,我们将App包进去,会注入一些东西,并接收参数。

我们这里它会将store通过props的方式传递给Provider组件,它的内部会接收一个store,它会把这个store转发给App组件(通过props转发),最终Appprops接收store。当其触发更新的时候,它会把新的内容重新传递给App,即将store转化了props,因此store更新后,组件内部自动刷新。

React-Redux01\app\src\index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {Provider} from 'react-redux'; // 类似高阶或容器组件
import store from "./store";

console.log(store.getState());

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

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

React-Redux01\app\src\components\List.js

import React from 'react';
import Item from "./Item";
import store from "../store";

export default class List extends React.Component {

    render() {

        return(
            <div>
                <Item />
            </div>
        );

    }

}

React-Redux01\app\src\components\Item.js

import React from 'react';
import {connect} from 'react-redux';

class Item extends React.Component {

    constructor(props) {
        super(props);

        this.addNewItem = this.addNewItem.bind(this);
    }

    addNewItem() {
        // let val = this.el.value;
        // // console.log(val);
        //
        // store.dispatch({
        //     type: 'ADD_ITEM',
        //     payload: {
        //         name: val
        //     }
        // });
        //
        // this.el.value = '';
        //
        // // 如果直接使用 store ,会绕开组件的更新,因为它既不是 state的变化,也不是 props的变化
        // console.log(store.getState());
        //
        // this.setState(store.getState())
    }

    render() {
        console.log(this.props);

        return(
            <div>
                <input type="text" ref={el => {
                    this.el = el;
                }}/> <button onClick={this.addNewItem}>添加</button>

                <ul>
                    {/*{*/}
                    {/*    items.map(item => {*/}
                    {/*        return(*/}
                    {/*            <li key={item.id}>{item.name}</li>*/}
                    {/*        )*/}
                    {/*    })*/}
                    {/*}*/}
                </ul>
            </div>
        );

    }

}

export default Item;

我们可以对比一下:打印什么都没有

image-20200610113352375

props没有东西,需要经过connect包装:

connect工厂函数,调用以后会返回一个包装组件(高阶组件)

export default connect()(Item);

dispatch就是store里面的派发函数

image-20200610113607947

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.02-1
Branch:branch2

commit description:v0.02-1-example02-1(connect工厂函数返回一个包装组件)

tag:v0.02-1

3.1.3.2.2 example02-2

connect 方法的第一个参数是一个函数

  • 该函数的第一个参数就是 store 中的 state : store.getState()
  • 该函数的返回值将被解构赋值给 props : this.props.items

state 就是仓库的state,该函数的返回值是一个对象,该对象就被解构赋值给 props

React-Redux01\app\src\components\Item.js

import React from 'react';
import {connect} from 'react-redux';

class Item extends React.Component {

    constructor(props) {
        super(props);

        this.addNewItem = this.addNewItem.bind(this);
    }

    addNewItem() {
        let val = this.el.value;

        this.props.dispatch({
            type: 'ADD_ITEM',
            payload: {
                name: val
            }
        });

        this.el.value = '';
    }

    render() {

        console.log(this.props);

        return(
            <div>

                <input type="text" ref={el => {
                    this.el = el;
                }}/> <button onClick={this.addNewItem}>添加</button>

                <ul>
                    {
                        this.props.items.map(item => {
                            return(
                                <li key={item.id}>{item.name}</li>
                            )
                        })
                    }
                </ul>

            </div>
        );

    }

}

export default connect( (state) => {
    // state 就是仓库的 state,该函数的返回值是一个对象,该对象就被解构赋值给 props
    return {
        items: state.items
    };
} )(Item);

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.02-2
Branch:branch2

commit description:v0.02-2-example02-2(connect工厂函数实现仓库数据实时更新视图)

tag:v0.02-2

还有一些细节地方,需要我们看文档:

我们看参数名称很容易知道其功能,第一个参数mapStateToProps :将仓库中的State映射到组件的props,第二个参数mapDispatchToPropsDispatch方法映射到组件的props

image-20200610121739345

4. Redux DevTools extension

Redux开发工具扩展:

为了能够更加方便的对 redux 数据进行观测和管理,我们可以使用 Redux DevTools extension 这个浏览器扩展插件

https://github.com/zalmoxisus/redux-devtools-extension

const store = createStore(
    reducers,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

打开浏览器开发者工具面板 -> redux

直接在谷歌商店中安装即可。

我们也可把项目下载下来,自己编译生成:

image-20200610124243565

用git 克隆项目:

image-20200610124312199

然后手动编译:

先用npm把它的依赖都装上:

image-20200610124728883

我们可以看看package.json

image-20200610125008106

image-20200610125021003

查看这里的命令

如:“build:extension” :源代码进行编译,会自动生成一个扩展

{
  "version": "2.17.1",
  "name": "remotedev-redux-devtools-extension",
  "description": "Redux Developer Tools for debugging application state changes.",
  "scripts": {
    "start": "gulp",
    "build:extension": "rimraf build/extension && cross-env BABEL_ENV=production gulp build:extension",
    "build:firefox": "cross-env BABEL_ENV=production gulp build:firefox",
    "build:examples": "babel-node examples/buildAll.js",
    "precompress:extension": "npm run lint && npm run test:app && npm run build:extension && npm run test:chrome && npm run test:electron",
    "precompress:firefox": "npm run lint && npm run build:firefox && npm run test:app",
    "compress:extension": "gulp compress:extension",
    "compress:firefox": "gulp compress:firefox",
    "docs:clean": "rimraf _book",
    "docs:prepare": "gitbook install",
    "docs:build": "npm run docs:prepare && gitbook build",
    "docs:watch": "npm run docs:prepare && gitbook serve",
    "docs:publish": "npm run docs:clean && npm run docs:build && cd _book && git init && git commit --allow-empty -m 'update book' && git checkout -b gh-pages && touch .nojekyll && git add . && git commit -am 'update book' && git push git@github.com:zalmoxisus/redux-devtools-extension gh-pages --force",
    "clean": "rimraf build/ && rimraf dev/",
    "lint": "eslint .",
    "test:app": "cross-env BABEL_ENV=test mocha --require test/app/setup.js --recursive test/app",
    "test:chrome": "gulp test:chrome",
    "test:electron": "gulp test:electron && rimraf test/electron/tmp",
    "test": "npm run test:app && npm run build:extension && npm run test:chrome && npm run test:electron"
  },

直接用npm start build:extension 启动报了很多错,不太容易解决,如果想解决可仔细分析一下错因。

image-20200610125755368

可在百度搜别人下载好的去下载安装(小迪跳墙弄得)。

装好以后需要在代码中添加:

image-20200610130628630

其实这个插件会自动在window下追加一个方法,这个方法其实就是调用该插件。

src/store/index.js

/**
* 使用 redux 来管理数据
* */
import {createStore, combineReducers} from 'redux';
 
import users from './reducer/users';
import items from './reducer/items';
 
const store = createStore(
    combineReducers({
        users,
        items
    }),
 
    // 调用插件
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
 
export default store;

@@INIT这个其实就是action动作,每次执行的action动作在这里都会有记录。

image-20200610131118006

看看仓库中的State:
image-20200610131303253

也可选择用多种结构去查看:

image-20200610131447351

image-20200610131500961

比较操作过程中的差异性的记录

image-20200610131536325

添加数据就产生action,可以查看数据的变化

image-20200610131703148

image-20200610131752264

也可过滤查询:

image-20200610131830224

之前讲过reducer的一个纯函数概念,不修改传入参数,我们在这里就可详细看到整个数据变化的过程,非常的方便。

可回放每一步的action

image-20200610132153491

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.02-3
Branch:branch2

commit description:v0.02-3-example02-3(引入redux)

tag:v0.02-3

5. 异步 action

许多时候,我们的数据是需要和后端进行通信的,而且中开发模式下,很容易就会出现跨域请求的问题,好在 create-react-app 中内置了一个基于 node 的后端代理服务,我们只需要少量的配置就可以实现跨域

5.1 package.json 配置

相对比较的简单的后端 URL 接口,我们可以直接中 package.json 文件中进行配置

// 后端接口
http://localhost:7777/api/items
// http://localhost:3000
{
  ...
  //
  "proxy": "http://localhost:7777"
}
axios({
  url: '/api/items'
});

5.2 example03

异步action

5.2.1 example03-1

刚刚我们所做的所有操作都是基于同步式修改的,我们没有去和后端交互也没有做其他的处理。但更多时候需要与后端或者其他东西做交互,有可能有异步处理的需求。针对异步处理,会不会产生一些问题呢?我们搭一下环境,配上后端(nodejs koa)。

npm init -y

npm i koa koa-router

server1\app.js

const Koa = require('koa');
const KoaRouter = require('koa-router');
 
const app = new Koa();
const router = new KoaRouter();
 
router.get('/', async ctx => {
    ctx.body = 'hello';
});
 
 
app.use( router.routes() );
 
app.listen(7777);

node .\app.js

image-20200610140101166

让后端存数据:

const Koa = require('koa');
const KoaRouter = require('koa-router');
 
const app = new Koa();
const router = new KoaRouter();
 
let items = [
    {
        id: 1,
        name: 'iPhone XR',
        price: 542500
    },
    {
        id: 2,
        name: 'Apple iPad Air 3',
        price: 377700
    },
    {
        id: 3,
        name: 'Macbook Pro 15.4',
        price: 1949900
    },
    {
        id: 4,
        name: 'Apple iMac',
        price: 1629900
    },
    {
        id: 5,
        name: 'Apple Magic Mouse',
        price: 72900
    },
    {
        id: 6,
        name: 'Apple Watch Series 4',
        price: 599900
    }
];
 
 
router.get('/', async ctx => {
    ctx.body = 'hello';
});
 
router.get('/api/items', async ctx => {
    ctx.body = items;
});
 
app.use( router.routes() );
 
app.listen(7777);

image-20200708005451434

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.03-1
Branch:branch2

commit description:v0.03-1-example03-1(引入后端nodejs)

tag:v0.03-1

5.2.2 example03-2

组件挂载完毕后,我们在componentDidMount中发送数据请求,我们安装axios:

npm i axios

React-Redux01\app\src\components\Item.js

import React from 'react';
import {connect} from 'react-redux';
import axios from 'axios';

class Item extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            items:[]
        }

        this.addNewItem = this.addNewItem.bind(this);
    }

    addNewItem() {
    }

    async componentDidMount() {
        let rs = await axios({
            url:'http://localhost:7777/api/items'
        })
        console.log(rs);
    }

    render() {

        return(
            <div>

                <input type="text" ref={el => {
                    this.el = el;
                }}/> <button onClick={this.addNewItem}>添加</button>

                <ul>
                    {
                        this.props.items.map(item => {
                            return(
                                <li key={item.id}>{item.name}</li>
                            )
                        })
                    }
                </ul>

            </div>
        );

    }

}

export default connect( (state) => {
    // state 就是仓库的 state,该函数的返回值是一个对象,该对象就被解构赋值给 props
    return {
        items: state.items
    };
} )(Item);

报错并警告,这个时候浏览器发现此时的请求是跨域的,给拦截了。可通过cors解决或者通过(前端)代理的方式。

image-20200610142328338

用前端代理方式,其实我们每次运行react,它也帮我们在本地开启了一个服务(http://localhost:3000/),

其实是脚手架工具会帮助我们启一个本地服务,除此之外还进行了代理请求,当我们发送http://localhost:7777/items请求的时候,肯定会跨域,受到同源策略限制,后端是没有这种同源策略限制,

后端就算跨域也是可以正常完成的。

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.03-2
Branch:branch2

commit description:v0.03-2-example03-2(异步action——axios请求跨域问题)

tag:v0.03-2

5.2.3 example03-3

React-Redux01\app\src\components\Item.js

async componentDidMount() {
    let rs = await axios({
        url: '/api/items'
    });
    console.log(rs);
}

这样填写就相当于把请求发送到了当前服务器,这个时候不会产生跨源,但是本地服务器没有写这个路由。因此我们需要做一些配置,如果当前请求满足某种规则,就让其帮忙访问真实的地址。即:

/api/items => http://localhost:3000/api/items => http://localhost:7777/api/items

image-20200708114256552

/api/items => http://localhost:3000/api/items (转发)=>http://localhost:7777/api/items

这种转发,称之为正向代理,不过还有反向代理

它俩都有三个共同的角色:

请求者 代理者 实际目标

请求者通常是浏览器 代理者http://localhost:3000 实际目标 http://localhost:7777

正向代理:

请求者&代理者在同一个域(被服务端) 目标在另一个服务器(服务端

反向代理:

请求者是一个域(被服务端) 代理&目标是另外一组(服务端

其实vue、react本地开发大部分都属于正向代理。

如果代码编译完后丢到服务器上,这个时候必然涉及跨域,假如前端代码通过server1访问,而后端代码通过server2访问,(通常开发的时候,浏览器也与代理者同在一台服务器上,一般开发都正向代理,而发布后,浏览器就不在一起了,就是反向代理了)浏览器和它们是分开的,这个时候就是反向代理模式了。

相对比较的简单的后端 URL 接口,我们可以直接中 package.json 文件中进行配置。

React-Redux01\app\package.json

{
  "name": "app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.3.2",
    "@testing-library/user-event": "^7.1.2",
    "axios": "^0.19.2",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-redux": "^7.2.0",
    "react-scripts": "3.4.1",
    "redux": "^4.0.5"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "proxy": "http://localhost:7777"
}

现在没有跨域错误,而是报404

image-20200610161116072

这个时候需要重启服务,注意修改配置文件后,需要重启服务的!

image-20200610174740486

重启React服务

image-20200610174740486

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.03-3
Branch:branch2

commit description:v0.03-3-example03-3(异步action——解决简单axios请求跨域问题)

tag:v0.03-3

5.2.4 example03-4

以上只是针对简单的情况下直接替换。

假如服务器端的接口变了,则前端的代理请求也需要发生变化。

\server1\app.js

const Koa = require('koa');
const KoaRouter = require('koa-router');
 
const app = new Koa();
const router = new KoaRouter();
 
let items = [
    {
        id: 1,
        name: 'iPhone XR',
        price: 542500
    },
    {
        id: 2,
        name: 'Apple iPad Air 3',
        price: 377700
    },
    {
        id: 3,
        name: 'Macbook Pro 15.4',
        price: 1949900
    },
    {
        id: 4,
        name: 'Apple iMac',
        price: 1629900
    },
    {
        id: 5,
        name: 'Apple Magic Mouse',
        price: 72900
    },
    {
        id: 6,
        name: 'Apple Watch Series 4',
        price: 599900
    }
];
 
 
router.get('/', async ctx => {
    ctx.body = 'hello';
});
 
router.get('/items', async ctx => {
    ctx.body = items;
});
 
app.use( router.routes() );
 
app.listen(7777);

src/components/Item.js

    async componentDidMount() {
        let rs = await axios({
            // url: '/api/items'
            url: '/items'
        });
        console.log(rs);
    }

image-20200610174740486

虽然打印无误,但是很容易产生另外一种问题。这里我们发送了请求,但是可能在做react项目的时候,你可能会用到react-router(路由),我们做了前端路由配置,而前端路由里也有‘/items’,很容易造成本地代理服务器,它分不清楚这是前端请求还是后端请求,这样就产生了冲突。

因此一般使用一个特殊前缀表示(如:‘/api/items’),我这个应用请求的是哪一台服务器,而不是我本地的。如果有多个服务器多个接口,可能就会用‘/api1/items’‘/api2/items’‘/apiX/items’等来代表不同服务器的接口地址来进行转发,因此这种模式下在package.json中写“proxy”的方法就适用不了了。

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.03-4
Branch:branch2

commit description:v0.03-4-example03-4(异步action——前端代理服务器url需要特殊前缀)

tag:v0.03-4

5.3 ./src/setupProxy.js 配置

针对相对复杂的情况,(proxy:代理转发和重写)可以有更多的配置

需要在本地安装一个中间件

npm i -S http-proxy-middleware

我们如果发现有问题最好去官网看看demo

image-20200610185710017

// javascript
 
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
 
const app = express();
 
app.use('/api', createProxyMiddleware({ target: 'http://www.example.org', changeOrigin: true }));
app.listen(3000);
 
// http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar
5.3.1 example04

src/setupProxy.js

const { createProxyMiddleware } = require('http-proxy-middleware');
 
module.exports = function(app) {
    app.use('/api', createProxyMiddleware({
        target: 'http://localhost:7777/',
        // 路径重写,去掉“/api”
        pathRewrite: {
            '^/api': ''
        } }));
    // (存在多个服务器)如果想配置多个再调用ues方法即可
}

配置好别忘了重启本地服务

image-20200610174740486

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.04
Branch:branch2

commit description:v0.04-example04(异步action——复杂的代理转发http-proxy-middleware)

tag:v0.04

5.4 example05

需求:请求完数据更新到store

React-Redux01\app\src\store\reducer\items.js

let items = [
];

let maxId = 6; // 当前的id

export default (state = items, action) => {
    switch (action.type) {
        // case 'ADD_ITEM':
        //     return [...state, {
        //         id: ++maxId,
        //         name: action.payload.name,
        //         price: 10000
        //     }];
        case 'UPDATE_ITEMS':
            // 拿到的就是更新的全部数据,一次性返回
            return action.payload.items;
        default:
            return state;
    }
}

React-Redux01\app\src\components\Item.js

import React from 'react';
import {connect} from 'react-redux';
import axios from 'axios';

class Item extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            items:[]
        }

        this.addNewItem = this.addNewItem.bind(this);
    }

    addNewItem() {
    }

    async componentDidMount() {
        let rs = await axios({
            // url:'/api/items'
            url: '/items'
        })
        // 更新到store
        this.props.dispatch({
            type: 'UPDATE_ITEMS',
            payload: {
                items: rs.data
            }
        });
    }

    render() {

        return(
            <div>

                <input type="text" ref={el => {
                    this.el = el;
                }}/> <button onClick={this.addNewItem}>添加</button>

                <ul>
                    {
                        this.props.items.map(item => {
                            return(
                                <li key={item.id}>{item.name}</li>
                            )
                        })
                    }
                </ul>

            </div>
        );

    }

}

export default connect( (state) => {
    // state 就是仓库的 state,该函数的返回值是一个对象,该对象就被解构赋值给 props
    return {
        items: state.items
    };
} )(Item);

image-20200610224116493

看上去似乎没有任何问题。但是有没有考虑过一个问题,我们如果想把这块代码封装起来,怎么做呢?我们这里的dispatch只能处理同步的,即只能传入一个数据进去,它也没有通过dispatch发送请求。

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.05
Branch:branch2

commit description:v0.05-example05(异步action——请求完数据更新到store)

tag:v0.05

        let rs = await axios({
            url: '/api/items'
        });
 
        this.props.dispatch({
            type: 'UPDATE_ITEMS',
            payload: {
                items: rs.data
            }
        });

希望this.props.dispatch接收一个异步函数,直接提交就行了,把这个过程封装起来,但它本身只接收对象,不接收函数。
我们用Middleware中间件来实现,叫做redux-thunk

6. Middleware

默认情况下,dispatch 是同步的,我们需要用到一些中间件来处理

const logger = store => next => action => {
  console.group(action.type)
  console.info('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  console.groupEnd(action.type)
  return result
}

6.1 redux.applyMiddleware

通过 applyMiddleware 方法,我们可以给 store 注册多个中间件

注意:devTools 的使用需要修改一下配置

npm i -D redux-devtools-extension
...
import { composeWithDevTools } from 'redux-devtools-extension';
...
const store = createStore(
  reducres,
  composeWithDevTools(
    applyMiddleware( logger )
  )
)

7. redux-thunk

作用:这是一个把同步 dispatch 变成异步 dispatch 的中间件

7.1 安装

npm i -S redux-thunk
import {createStore, combineReducers, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
 
import user from './reducer/user';
import items from './reducer/items';
import cart from './reducer/cart';
 
let reducers = combineReducers({
    user,
    items,
    cart
});
 
const store = createStore(
    reducers,
    composeWithDevTools(applyMiddleware(
        thunk
    ))
);

7.2 example06

实现this.props.dispatch接收一个异步函数,直接提交就行了,把这个过程封装起来,但它本身只接收对象,不接收函数。

7.2.1 example06-1

import thunk from 'redux-thunk',把它引入到仓库中,然后用中间件把这个dispatch改了,它里面有一个方法applyMiddleware,它的作用就是中间件,把我们引入的thunk扔进去,对dispatch方法进行包装,去改变dispatch的行为。先不调用中间件对dispatch方法进行包装,先注释看看效果。

React-Redux01\app\src\store\index.js

/**
 * 使用 redux 来管理数据
 * */
import {createStore, combineReducers, applyMiddleware} from 'redux';

import thunk from 'redux-thunk'

import users from './reducer/users';
import items from './reducer/items';

const store = createStore(
    combineReducers({
        users,
        items
    }),

    // 对 dispatch 方法进行包装
    // applyMiddleware(thunk)

    // 调用插件
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

export default store;

React-Redux01\app\src\components\Item.js

import React from 'react';
import {connect} from 'react-redux';
import axios from 'axios';

class Item extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            items:[]
        }

        this.addNewItem = this.addNewItem.bind(this);
    }

    addNewItem() {
    }

    async componentDidMount() {
        // let rs = await axios({
        //     // url:'/api/items'
        //     url: '/items'
        // })
        // // 更新到store
        // this.props.dispatch({
        //     type: 'UPDATE_ITEMS',
        //     payload: {
        //         items: rs.data
        //     }
        // });
        this.props.dispatch( function () {

        });
    }

    render() {

        return(
            <div>

                <input type="text" ref={el => {
                    this.el = el;
                }}/> <button onClick={this.addNewItem}>添加</button>

                <ul>
                    {
                        this.props.items.map(item => {
                            return(
                                <li key={item.id}>{item.name}</li>
                            )
                        })
                    }
                </ul>

            </div>
        );

    }

}

export default connect( (state) => {
    // state 就是仓库的 state,该函数的返回值是一个对象,该对象就被解构赋值给 props
    return {
        items: state.items
    };
} )(Item);

this.props.dispatch默认只能接收一个对象(action动作),不能接收函数的。

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.06-1
Branch:branch2

commit description:v0.06-1-example06-1(把同步dispatch 变成异步 dispatch 的中间件,注释包装函数)

tag:v0.06-1

打开注释,调用中间件对dispatch方法进行包装,就不报错了。

7.2.2 example06-2

注释调用中间件对dispatch方法进行包装,完善接收的函数,传递函数对象。

dispath 最终是接受一个 action 对象,我们可以通过函数来产生这样的一个对象,但是这个函数必须是同步的。

React-Redux01\app\src\components\Item.js

    async componentDidMount() {
        // let rs = await axios({
        //     // url:'/api/items'
        //     url: '/items'
        // })
        // // 更新到store
        // this.props.dispatch({
        //     type: 'UPDATE_ITEMS',
        //     payload: {
        //         items: rs.data
        //     }
        // });
        // 这个函数只能是同步的
        function updateAction() {
            return {
                type: 'UPDATE_ITEMS',
                payload: {
                    items: []
                }
            }
        }

        // dispath 最终是接受一个 action 对象,我们可以通过函数来产生这样的一个对象,但是这个函数必须是同步的
        this.props.dispatch( updateAction() );
    }

image-20200708135155369

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.06-2
Branch:branch2

commit description:v0.06-2-example06-2(把同步dispatch 变成异步 dispatch 的中间件,注释包装函数,传递函数对象)

tag:v0.06-2

7.2.3 example06-3

注释调用中间件对dispatch方法进行包装,完善接收的函数,传递函数对象。

dispath最终是接受一个action对象,我们可以通过函数来产生这样的一个对象,但是这个函数必须是同步的。我们传递异步函数试试。

React-Redux01\app\src\components\Item.js

    async componentDidMount() {
        // let rs = await axios({
        //     // url:'/api/items'
        //     url: '/items'
        // })
        // // 更新到store
        // this.props.dispatch({
        //     type: 'UPDATE_ITEMS',
        //     payload: {
        //         items: rs.data
        //     }
        // });
        // 这个函数只能是同步的
        function updateAction() {
            // return {
            //     type: 'UPDATE_ITEMS',
            //     payload: {
            //         items: []
            //     }
            // }
            return new Promise((resolve) => {
                setTimeout(() => {
                    resolve({
                        type: 'UPDATE_ITEMS',
                        payload: {
                            items: []
                        }
                    });
                }, 1000);
            })
        }

        // dispath 最终是接受一个 action 对象,我们可以通过函数来产生这样的一个对象,但是这个函数必须是同步的

        // dispatch 会被修改,这里的 dispatch 并不是真正的原来的 dispatch 函数,
        this.props.dispatch( updateAction() );
    }

updateAction()不是立马执行的,立刻产生一个对象(1s后才产生action对象),这里的定时器是一个异步任务,this.props.dispatch它不会接受异步任务的,它必须要求立马返回一个对象给它。这是原生的dispatch的一种行为,这个时候如果把Ajax封装进来的话,这个时候必然就会产生问题了。

image-20200610235617008

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.06-3
Branch:branch2

commit description:v0.06-3-example06-3(把同步dispatch 变成异步 dispatch 的中间件,注释包装函数,传递异步函数试试)

tag:v0.06-3

7.2.4 example06-4

实际上是希望的代码是这样的:

Ajax请求(异步)发送完毕后,再返回一个action对象。

    async componentDidMount() {
        // let rs = await axios({
        //     // url:'/api/items'
        //     url: '/items'
        // })
        // // 更新到store
        // this.props.dispatch({
        //     type: 'UPDATE_ITEMS',
        //     payload: {
        //         items: rs.data
        //     }
        // });
        // 这个函数只能是同步的
        // async function updateAction(dispatch) {
        async function updateAction() {
            let rs = await axios({
                url: '/api/items'
            });


            return {
                type: 'UPDATE_ITEMS',
                payload: {
                    items: []
                }
            }
        }

        // dispath 最终是接受一个 action 对象,我们可以通过函数来产生这样的一个对象,但是这个函数必须是同步的
        this.props.dispatch( updateAction() );
    }

默认情况下只能处理同步的,我把注释打开,调用中间件对dispatch方法进行包装。

React-Redux01\app\src\store\index.js

/**
 * 使用 redux 来管理数据
 * */
import {createStore, combineReducers, applyMiddleware} from 'redux';

import thunk from 'redux-thunk'

import users from './reducer/users';
import items from './reducer/items';

const store = createStore(
    combineReducers({
        users,
        items
    }),

    // 对 dispatch 方法进行包装
    applyMiddleware(thunk)

    // 调用插件
    // window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

export default store;

但是还是报错

image-20200611001925081

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.06-4
Branch:branch2

commit description:v0.06-4-example06-4(把同步dispatch 变成异步 dispatch 的中间件,打开包装函数,传递异步函数,报错)

tag:v0.06-4

7.2.5 example06-5

我暂时找不到问题,我们去查查官网

image-20200611002935681

包装以后,this.props.dispatch它已经不是原生的dispatch了,它会自动地把真正地dispatch传到函数参数里。dispatch参数直接往里传递即可。

React-Redux01\app\src\components\Item.js

import React from 'react';
import {connect} from 'react-redux';
import axios from 'axios';

class Item extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            items:[]
        }

        this.addNewItem = this.addNewItem.bind(this);
    }

    addNewItem() {
    }

    componentDidMount(dispatch) {

        // dispatch参数才是真正原生的
        async function updateAction(dispatch) {
            // return {
            //     type: 'UPDATE_ITEMS',
            //     payload: {
            //         items: []
            //     }
            // }

            // return new Promise((resolve) => {
            //     setTimeout(() => {
            //         resolve({
            //             type: 'UPDATE_ITEMS',
            //             payload: {
            //                 items: []
            //             }
            //         });
            //     }, 1000);
            // })

            let rs = await axios({
                // url: '/api/items',
                url: '/api/items'
            });

            dispatch({
                type: 'UPDATE_ITEMS',
                payload: {
                    items: rs.data
                }
            });

            // return {
            //     type: 'UPDATE_ITEMS',
            //     payload: {
            //         items: []
            //     }
            // }
        }

        // dispath 最终是接受一个 action 对象,我们可以通过函数来产生这样的一个对象,但是这个函数必须是同步的

        // dispatch 会被修改,这里的 dispatch 并不是真正的原来的 dispatch 函数
        this.props.dispatch( updateAction );
    }

    render() {

        return(
            <div>

                <input type="text" ref={el => {
                    this.el = el;
                }}/> <button onClick={this.addNewItem}>添加</button>

                <ul>
                    {
                        this.props.items.map(item => {
                            return(
                                <li key={item.id}>{item.name}</li>
                            )
                        })
                    }
                </ul>

            </div>
        );

    }

}

export default connect( (state) => {
    // state 就是仓库的 state,该函数的返回值是一个对象,该对象就被解构赋值给 props
    return {
        items: state.items
    };
} )(Item);

src/store/index.js

/**
* 使用 redux 来管理数据
* */
import {createStore, combineReducers, applyMiddleware} from 'redux';
 
import users from './reducer/users';
import items from './reducer/items';
import thunk from 'redux-thunk';
 
const store = createStore(
    combineReducers({
        users,
        items
    }),
 
    // 对 dispatch 方法进行包装
    applyMiddleware(thunk),
 
    // 调用插件
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
 
export default store;

但是现在的redux的浏览器扩展插件用不了,会报错。

image-20200611004537447

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.06-5
Branch:branch2

commit description:v0.06-5-example06-5(实现同步dispatch 变成异步 dispatch 的中间件,redux插件报错)

tag:v0.06-5

7.2.6 example06-6

只能换一种方式,用代码的方式去装这个扩展插件。

npm i redux-devtools-extension

之后在仓库中引用,并再加一层包装

React-Redux01\app\src\store\index.js

/**
 * 使用 redux 来管理数据
 * */
import {createStore, combineReducers, applyMiddleware} from 'redux';

import thunk from 'redux-thunk'

import users from './reducer/users';
import items from './reducer/items';

import { composeWithDevTools } from 'redux-devtools-extension';

const store = createStore(
    combineReducers({
        users,
        items
    }),

    // 对 dispatch 方法进行包装
    // applyMiddleware(thunk),

    // 调用插件
    // window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()

    composeWithDevTools(applyMiddleware(thunk))
);

export default store;

这就大功告成了!

image-20200611005045538

原先的dispatch函数只能接收同步函数,但现在就可以传异步函数了,异步函数执行完毕之后,再执行dispatch了。这就相当于把请求action动作都封装起来了,可以在任意地方调用了。

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.06-6
Branch:branch2

commit description:v0.06-6-example06-6(实现同步dispatch 变成异步 dispatch 的中间件,实现)

tag:v0.06-6

7.3 小结原理—example07

我们大致研究一下原理:

7.3.1 example07-1

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script src="./redux.min.js"></script>
<script>



    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];


    function reducer(state, action) {

        switch (action.type) {
            case 'ADD':
                return [...state, action.payload];
            default:
                return state;
        }

    }

    let store = Redux.createStore( reducer, users );

    let unsubscribe = store.subscribe(function() {
        console.log(store.getState());
    });

    // console.log(store);

    // console.log( store.getState() );


    function add(payload) {
        return {
            type: 'ADD',
            payload
        }
    }


    store.dispatch(add({
        id: 3,
        username: 'xm',
        age: 30
    }));

    // console.log( store.getState() );

    store.dispatch(add({
        id: 3,
        username: 'xh',
        age: 30
    }));

    // console.log( store.getState() );


</script>
</body>
</html>

image-20200611005944315

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.07-1
Branch:branch2

commit description:v0.07-1-example07-1(通过add方法添加数据的,通过store.dispatch来调用add函数)

tag:v0.07-1

这里是通过add方法添加数据的,通过store.dispatch来调用add函数,传入想要数据进来,但是这个add方法,只能是同步的。

那怎样处理异步函数呢?

7.3.2 example07-2

 let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];
 
 
    function reducer(state, action) {
 
        switch (action.type) {
            case 'ADD':
                return [...state, action.payload];
            default:
                return state;
        }
 
    }
 
    let store = Redux.createStore( reducer, users );
 
 
    function add(payload) {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve( {
                    type: 'ADD',
                    payload
                })
            }, 1000);
        })
    }
 
 
    store.dispatch( add('abc') );
    console.log( store.getState() );

因为dispatch不处理异步任务,会报错!我们修改代码,其实就是中间件的原理!

image-20200611081813941

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.07-2
Branch:branch2

commit description:v0.07-2-example07-2(通过add方法添加数据的,通过store.dispatch来调用异步add函数,报错)

tag:v0.07-2

7.3.3 example07-3

我们把原来dispatch先存起来,再重写,再去调用,就是我们重写后的的dispatch函数了。处理完then后再调用原生dispatch

    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];


    function reducer(state, action) {

        switch (action.type) {
            case 'ADD':
                return [...state, action.payload];
            default:
                return state;
        }

    }

    let store = Redux.createStore( reducer, users );


    function add(payload) {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve( {
                    type: 'ADD',
                    payload
                })
            }, 1000);
        })
    }

    // 先将原有的store.dispatch存起来,再重写!
    let dispatch = store.dispatch;


    // 把它重写
    store.dispatch = function(reducer) {
        reducer.then( val => {
            console.log(val);
            // 处理完then后再调用原生dispatch
            dispatch(val);
        } );
    };

    store.dispatch( add('abc') );
    console.log( store.getState() );
    console.log(1);

这样就大功告成了,这里把执行异步的过程封装成了一个方法。把这个方法直接传给Middleware里去,它会自动注入dispatch函数进来,实际重写了。

image-20200611123035703

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/v0.07-3
Branch:branch2

commit description:v0.07-3-example07-3(通过add方法添加数据的,通过store.dispatch来调用异步add函数,实现)

tag:v0.07-3

7.3.4 伪代码原生原理


    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];
 
 
    function reducer(state, action) {
 
        switch (action.type) {
            case 'ADD':
                return [...state, action.payload];
            default:
                return state;
        }
 
    }
 
    function add(payload) {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve( {
                    type: 'ADD',
                    payload
                })
            }, 1000);
        })
    }
 
    // 先将原有的store.dispatch存起来,再重写!
    // let dispatch = store.dispatch;
 
    // 把它重写
    // store.dispatch = function(reducer) {
    //     reducer.then( val => {
    //         console.log(val);
    //         // 处理完then后再调用原生dispatch
    //         dispatch(val);
    //     } );
    // };
 
    // 函数式
    function fn1(store) {
        // 这个函数自动传入一个参数,next 下一个中间件的调用
        return function (next) {
            // 这个函数才是最终被改写的dispatch函数
            return function (action) {
 
            }
        }
    }
 
    // applyMiddleware 自动给fn1传入的第一个参数是store,它必须返回一个函数
    let store = Redux.createStore( reducer, applyMiddleware( fn1 ) );
 
    store.dispatch( fn1('abc') );
    console.log( store.getState() );

8. Redux 中间件

8.1 Redux 中间件的作用

Reudx 中的中间件是通过对 dispatch 进行的二次封装,对 action 发起之后,reducer 调用之前的行为进行扩展,我们可以通过中间件来进行:日志记录发送报告调用异步接口 ……

8.2 从日志记录需求开始

需求:再每次 dispatch 的时候打印修改之前和之后的 state

8.2.1 准备

let initState = {
  users: []
}
 
function reducers(state=initState, {type, payload}) {
  switch(type) {
    case 'user/add':
      return Object.assign(state, {
        users: [...state.users, payload]
      });
  }
 
  return state;
}
 
let store = Redux.createStore(
  reducers
);

8.2.2 最直接最原始的做法

console.log(store.getState());
store.dispatch({
  type: 'user/add',
  payload: 'zs'
});
console.log(store.getState());

8.2.3 封装 logger

function logger(store, action) {
  console.log(store.getState());
  store.dispatch(action)
  console.log(store.getState());
}
 
logger(store, {
  type: 'user/add',
  payload: 'zs'
});

8.2.4 封装 dispatch

let next = store.dispatch;
store.dispatch = function logger(action) {
  console.log(store.getState());
  next(action);
  console.log(store.getState());
}
 
store.dispatch({
  type: 'user/add',
  payload: 'zs'
});

8.3 添加错误报告发送

需求:每次 dispatch 把提交的数据进行持久化

let next = store.dispatch;
store.dispatch = function storage(action) {
  next(action);
  let s = localStorage
}
 
store.dispatch({
  type: 'user/add',
  payload: 'zs'
});

8.4 封装合并多个中间件

  • 把每个独立的中间件封装成独立的函数
  • 每个中间件都需要获取一次 dispatch
function logger(store) {
  let next = store.dispatch;
  return function loggerDispatch(action) {
    console.log('before: ', store.getState());
    next(action);
    console.log('after: ', store.getState());
  }
}
 
function storage(store) {
  let next = store.dispatch;
  return function storageDispatch(action) {
    next(action);
    localStorage.setItem('store', JSON.stringify(store.getState()));
    console.log('持久存储完成');
  }
}
 
store.dispatch = logger(store);
store.dispatch = storage(store);
 
store.dispatch({
  type: 'user/add',
  payload: 'zs'
});

8.5 方法一个方法统一处理中间件函数的注册

封装一个 applyMiddleware 方法

function applyMiddleware(store, middlewares) {
  middlewares.reverse()
  middlewares.forEach(middleware => {
    store.dispatch = middleware(store)
  })
}
 
applyMiddleware(store, [logger, storage])

8.6 抽取 next

function applyMiddleware(store, middlewares) {
  middlewares.reverse()
  let dispatch = store.dispatch
  middlewares.forEach(middleware => {
    dispatch = middleware(store)(dispatch)
  })
  store.dispatch = dispatch;
}
 
function logger(store) {
  return function(next) {
    return function loggerDispatch(action) {
      console.log('before: ', store.getState());
      next(action);
      console.log('after: ', store.getState());
    }
  }
}
 
function storage(store) {
  return function(next) {
    return function storageDispatch(action) {
      next(action);
      localStorage.setItem('store', JSON.stringify(store.getState()));
      console.log('持久存储');
    }
  }
}

8.7 ES6 改写

let logger = store => next => action => {
  console.log('before: ', store.getState());
  next(action);
  console.log('after: ', store.getState());
}
let storage = store => next => action => {
  next(action);
  localStorage.setItem('store', JSON.stringify(store.getState()));
  console.log('持久存储');
}

8.8 使用 Redux.applyMiddleware 方法

Redux 内置了一个 applyMiddleware 方法用来注册中间件

let store = Redux.createStore(
  reducers,
  Redux.applyMiddleware(logger, storage)
);

8.9 异步的问题

仔细观察我们发现,默认的 dispatch 是没有对异步的任务进行处理的,且该方法默认情况下接收的是一个 action 对象

8.9.1 redux-thunk 的原理

通过中间件重新封装 dispatch 方法,使它能够接收一个异步任务函数

let thunk = store => next => action => {
  console.log('thunk');
  new Promise( (resolve, reject) => {
    action(next);
  } )
}
 
store.dispatch( dispatch => {
  setTimeout(() => {
    dispatch({
      type: 'user/add',
      payload: 'zs'
    });
  }, 1000);
} )


(后续待补充)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值