给自己讲一遍redux以及为什么要用redux-thunk

什么是redux?我认为他就是一个公共状态管理工具,通过js的闭包,独立开辟一个环境,用于保存公共状态state,而这个state我们默认他为只读的,不能直接修改,这就保证了在使用时不会有人乱改里面的值而导致整个状态被污染的情况。如果想要更改它,就需要用到redux暴露出来的dispatch方法,接受一个action,也就是给redux派发一个动作,让他根据动作去修改state,每个动作都对应着一个操作流程,做着很单一的工作,比如add就是对某个值进行加法,只要通过action我们就能知道他要干什么。而这个action绝不会影响其他属性。这也就保证了整个函数的纯洁。
同时还有一个重要的方法是subscribe,他接受一个方法作为参数,将参数保存在数组(listenerList)里,在dispatch完成时要依次执行listenerList里面的所有方法,而这里边最有代表性的就是React.forceUpdate。因为redux中的state更新后并不会引起React的重新渲染,所以需要在它更新后,手动调用React.forceUpdate进行强制刷新,从而达到获取最新state的目的。

以上就完成了最初级的redux,但是还差点东西,什么东西呢,这个dispatch只能接受一个对象作为action,如果我的项目中有用到ajax,并且把ajax封装成了一个方法,获取到数据后,想再调用dispatch该怎么办呢?上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 第一种,不需要中间件,直接用原来的方式
function getPageData() {
    const requestNumber = Math.ceil(Math.random() * 10)
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(requestNumber)
        }, 1000)
    })
}
class TestReactRedux extends Component {
    constructor(props) {
        super(props);
    }

    handleClick() {
        getPageData().then((number) => {
            this.props.asyncAdd(number)
        })
    }
    render() {
        return (
            <div>
                {this.props.number}
                <button onClick={() => this.handleClick()}>异步+</button>
            </div>
        );
    }
}
connect(
    (store) => ({
        number: store,
    }),
    (dispatch) => ({
        asyncAdd() {
            dispatch({
                type: "add"
            });
        },
    })
)(TestReactRedux);

如果我想让组件变得更加纯净一些,想让ajax的操作在connect里写,那我该怎么办?
最简单的办法,直接挪过来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class TestReactRedux extends Component {
    render() {
        return (
            <div>
                {this.props.number}
                <button onClick={() => this.props.asyncAdd()}>异步+</button>
            </div>
        );
    }
}
connect(
    (store) => ({
        number: store,
    }),
    (dispatch) => ({
        asyncAdd() {
            getPageData().then((number) => {
                dispatch({
                    type: "add",
                    number,
                });
            });
        },
    })
)(TestReactRedux);

这么写是不是TestReactRedux组件就又少了一些代码。我还想让connect中少写一些,不想在回调里再去dispatch该怎么办,我想这么写

1
2
3
4
5
6
7
8
9
10
connect(
    (store) => ({
        number: store,
    }),
    (dispatch) => ({
        asyncAdd() {
            dispatch(getPageData)
        },
    })
)(TestReactRedux);

而getPageData是一个方法,直接执行的话,会报错在这里插入图片描述
提示我们actions必须是一个对象,可以用自定义的中间件去支持异步的actions(这里的异步我认为就是方法中调用dispatch),所以需要一个中间件去帮助dispatch支持函数作为参数,也就是redux-thunk,用法如下:

1
2
import thunk from "redux-thunk";
createStore(reducer, applyMiddleware(thunk));

这时就能跑了,但是并没有发生变化,为什么呢?因为dispatch确实执行了这个getPageData方法,但是在有结果的时候,并没有告知redux他将要发生改变,那该怎么办呢?这就要看看中间件到底该怎么用了。

1
2
3
4
5
6
export const fake_thunk = ({dispatch, getState}) => nextDispatch => action => {
    if(typeof action === "function") {
        return action(dispatch, getState)
    }
    return nextDispatch(action);
}

通过简易版的thunk可以看出来,当我的action是一个方法的时候,我会传给这个方法一个dispatch和一个getState作为参数,而我们则可以使用这里的这个dispatch进行通知redux。我们来试一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
function getPageData(dispatch, getState) {
    const requestNumber = Math.ceil(Math.random() * 10);
    console.log(getState());
    console.log(dispatch);
    return new Promise(() => {
        setTimeout(() => {
            dispatch({
                type: "add",
                number: requestNumber,
            });
        }, 1000);
    });
}

通过打印也可以看到我们接受的参数是正确的,并且也可以渲染到页面上了,这里也可以拿取之前的state,可以用它做一些其他的事情,这里就不赘述了,自己可以体验。
最后再给大家看一下修改后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function getPageData(dispatch, getState) {
    const requestNumber = Math.ceil(Math.random() * 10);
    return new Promise(() => {
        setTimeout(() => {
            dispatch({
                type: "add",
                number: requestNumber,
            });
        }, 1000);
    });
}

class TestReactReduxReal extends Component {
    render() {
        return (
            <div>
                {this.props.number}
                <button onClick={() => this.props.asyncAdd()}>异步+</button>
            </div>
        );
    }
}
export default connect(
    (store) => ({
        number: store,
    }),
    (dispatch) => ({
        asyncAdd() {
            dispatch(getPageData);
        },
    })
)(TestReactReduxReal);

是不是清晰了许多,所有与redux有关的代码都放在了接口函数中,connect只是做了一个中转,帮助接口函数传递了一个dispatch以及getState方法,而在组件中只是调用方法,没有做任何的操作,这个组件就变得相当纯净。而且也没有了回调函数,不需要一层套一层了,直接在结果里就去dispatch了,省去了返回出来一个Promise对象,再去then啊什么的。
至此一个redux就算讲完了,这里面还用到了react-redux,他其实往简单里将,就是封装了一层context上下文,把组件都当成Provider的子组件,并将store当作value传递下去,然后组件们就可以获取到这个store实例了。当然它里面还做了很多操作,这个还需要细细研究一波。