redux学习(下)

作者: NSO | 来源:发表于2018-04-12 22:38 被阅读23次

    依照惯例,开头先放出redux中文文档地址

    异步场景的基本思路

    之前讨论的所有场景都是同步场景,实际开发中肯定有很多异步场景,下面讨论一下异步场景的问题。
    首先,异步场景和同步场景本质上还是利用actionreducerstore这些东西,只是设计reduceraction的时候更加巧妙,考虑到了异步状态。
    对应不同的action,在reducer中添加专门表现异步状态的内容。

    function post(
      state = {
        isFetching: false,
        items: []
      },
      action
    ) {
      switch(action.type) {
        case REQUEST:
          return  Object.assign({}, state, {
            isFetching: true,
          })
        case RECEIVE:
          return Object.assign({}, state, {
            isFetching: false,
            items: action.posts
          })
        default:
          return state 
      }
    }
    

    搞定了reducer还要考虑异步场景的action,我们先不仔细探究代码实现,只要想明白如果store.dispatch(someAction),然后按照异步回调的顺序触发上面的reducer,那么异步场景就实现了。

    解决异步场景

    上面我们笼统地讲了异步场景的实现思路,目前看来reducer部分已经没有大问题和同步场景十分类似,action部分和同步场景有所区别。
    简单思考可以发现,我们需要在action creator函数中实现异步,即当我们调用一个action creator时,不是简单需要返回一个action对象,而是根据异步的场景返回不同的action。看起来我们需要改造action
    理解了这个问题,就找到了异步场景的症结。
    redux有许多配套的中间件(middleware),关于中间件的概念可以查看上面的文档,我们不仔细讨论。简要地说,中间件在调用action creator后起作用,可以有多个中间件存在,数据流会在中间件之间传递,数据流经过所有中间件处理流出时应该还是一个简单的action对象,到达store.dispatch(action)时所有内容应该和同步数据流一致。

    redux-thunk 实现

    redux-thunk改造了store.dispatch,使action creator能接收一个函数作为参数。改造完action creator之后,就可以创造一个内部根据状态再次异步操作的action

    const fetchPosts = postTitle => (dispatch, getState) => {
      dispatch(requestPosts(postTitle));
      return fetch(`/some/API/${postTitle}.json`)
        .then(response => response.json())
        .then(json => dispatch(receivePosts(postTitle, json)));
      };
    };
    

    结合一个小例子来看一下
    已有一个container组件

    class example extends Component {
        constructor() {
            super();
        }
    
        componentDidMount() {
            组件需要调用后端API载入数据
            this.props.getName();
        }
    
        render () {
            console.log(this.props)
            return (
                <div>
                    // 组件按照不同状态显示‘querying...’/正常数据
                    { this.props.info.server.isFetching ?
                        (
                            <span>
                                querying...
                            </span>
                        )
                        :
                        (
                            // 点击请求API刷新状态
                            <span onClick={() => this.props.getName()}>
                                { this.props.info.server.name }
                            </span>
                        )
                    }
                    ...
                </div>
            );
        }
    }
    
    const mapStateToProps = (state) => {
        return {
            info: state.info
        }
    };
    
    const mapDispatchToProps = (dispatch) => {
        return {
            getName: () => {
                dispatch(getServerInfo());
            }
        };
    };
    
    
    const Example = connect(
        mapStateToProps,
        mapDispatchToProps
    )(example);
    
    
    export default Example;
    

    下面开始实现这个小功能

    编写对应的action

    function fetchServerInfo() {
        return {
            type: 'GET SERVER INFO'
        };
    }
    
    function receiveServerInfo({ payload: server }) {
        return {
            type: 'RECEIVE SERVER INFO',
            payload: server
        };
    }
    
    // 这是一个改造后的action creator,可以异步dispatch(action)
    function getServerInfo() {
        return (dispatch, getState) => {
            dispatch(fetchServerInfo());
            return fetch(`http://server/info`)
                .then(
                    response => response.json(),
                    error => console.log('an error', error)
                )
                .then(
                    (json) => {
                        let { server } = json;
                        dispatch(receiveServerInfo({ payload: server }));
                    }
                )
        }
    }
    

    编写reducer

    // 加入一个isFetching的状态标志位
    const initialInfo = {
        server: {
            isFetching: false,
            name: 'localhost',
            status: ''
        }
    };
    
    function setServerName (state = initialInfo, action) {
        let server = state.server;
    
        switch (action.type) {
            case 'GET SERVER INFO':
                return Object.assign(
                    {},
                    server,
                    {
                        isFetching: true
                    }
                );
            case 'RECEIVE SERVER INFO':
                let { payload: { name } } = action;
                return Object.assign(
                    {},
                    server,
                    {
                        isFetching: false,
                        name
                    }
                );
            default:
                return server
    
        }
    }
    
    function info (state = initialInfo, action) {
        return {
            server: setServerName(state.title, action)
        }
    }
    
    export default info;
    

    添加middleware

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

    redux-saga 实现

    redux-saga是一种不一样的实现方式,它没有在action creator上做文章,它监听了action,如果action中有effect,那么它可以调用Genertor函数实现异步。
    还是结合上面的小例子来看一下:
    已有一个container组件

    class example extends Component {
        constructor() {
            super();
        }
    
        componentDidMount() {
            组件需要调用后端API载入数据
            this.props.getName();
        }
    
        render () {
            console.log(this.props)
            return (
                <div>
                    // 组件按照不同状态显示‘querying...’/正常数据
                    { this.props.info.server.isFetching ?
                        (
                            <span>
                                querying...
                            </span>
                        )
                        :
                        (
                            // 点击请求API刷新状态
                            <span onClick={() => this.props.getName()}>
                                { this.props.info.server.name }
                            </span>
                        )
                    }
                    ...
                </div>
            );
        }
    }
    
    const mapStateToProps = (state) => {
        return {
            info: state.info
        }
    };
    
    const mapDispatchToProps = (dispatch) => {
        return {
            getName: () => {
                dispatch(getServerInfo());
            }
        };
    };
    
    
    const Example = connect(
        mapStateToProps,
        mapDispatchToProps
    )(example);
    
    
    export default Example;
    

    创建一个saga文件监听action

    import { takeEvery } from 'redux-saga';
    import { call, put } from 'redux-saga/effects';
    import requestServerInfo from '../../services/info'
    
    // 遇到被监听的action会调用下面的函数,并将异步操作分解
    function* getServerInfo() {
        try {
            yield put({ type: 'GET SERVER INFO' });
            const { data: { server: server } } = yield call(requestServerInfo, 'http://server/test');
            yield put({ type: 'RECEIVE SERVER INFO', payload: { server } });
        } catch (e) {
            yield put({ type: 'RECEIVE AN ERROR' });
        }
    }
    
    // 在此处使用takeEvery监听action
    function* infoSaga() {
        yield* takeEvery('QUERY_INFO', getServerInfo);
    }
    
    export { infoSaga };
    

    store中应用saga

    import { createStore, applyMiddleware } from 'redux';
    import createSagaMiddleware from 'redux-saga';
    import { infoSaga } from '../sagas/info';
    import reducer from '../reducers/all';
    
    // 引入redux-saga中间件
    const sagaMiddleware = createSagaMiddleware();
    const store = createStore(
        reducer,
        applyMiddleware(
            sagaMiddleware
        )
    );
    sagaMiddleware.run(infoSaga);
    
    export default store;
    

    剩下reducer部分和上面一样处理加入isFetching标志位即可,没有变化

    参考:
    Redux 中文文档
    Redux-Saga 中文文档
    阮一峰 Redux 入门教程(二):中间件与异步操作

    相关文章

      网友评论

        本文标题:redux学习(下)

        本文链接:https://www.haomeiwen.com/subject/lihzhftx.html