redux-saga核心讲解

1,043 阅读5分钟

redux-saga介绍

场景

redux中的action仅支持原始对象,要处理有副作用的action,需要使用中间件。中间件可以在发出action,到reducer函数接受aciton之间,执行具有副作用的操作。 在这种场景下,需要一个中间件,redux作者推荐的redux-thunk和开源的redux-saga。

为何推荐使用redux-saga

redux-thunk和redux-saga都能处理副作用的场景,但是redux-saga显得更为优雅。

redux-thunk

redux-thunk的实现代码如下:

    function createThunkMiddleware(extraArgument) {
        return ({ dispatch, getState }) => next => action => {
            if (typeof action === 'function') {
                return action(dispatch, getState, extraArgument);
            }

            return next(action);
        };
    }

    const thunk = createThunkMiddleware();
    thunk.withExtraArgument = createThunkMiddleware;

    export default thunk;

从上面代码可以看出,如果action是函数,就调用thunk这个函数,调用步骤是return action(dispatch, getState, extraArgument), 也可以说thunk仅仅是执行了action这个函数,而不在乎函数内部是怎么样的,比如:

    export default ()=>(dispatch)=>{
        fetch('/api/goodList',{ //fecth返回的是一个promise
            method: 'get',
            dataType: 'json',
        }).then(function(json){
            var json=JSON.parse(json);
            if(json.msg==200){
                dispatch({type:'init',data:json.data});
            }
        },function(error){
        console.log(error);
        });
    };

如果一个业务界面下有好几个异步操作,那就需要为每个异步操作都定义一个如此的action,显然不易维护。

> action缺点
>> action的形式不统一
>> 异步操作太过分散,分散在各个action中
> 会导致action不易维护
redux-saga

正由于redux-thunk的缺陷,redux-saga来得非常及时,不但处理了action的副作用,而且比thunk显得更加优雅好用。

redux核心讲解

辅助函数

redux-saga提供了一些辅助函数,用来在一些特定的action 被发起到Store时派生任务。 主要的辅助函数为:takeEvery 和 takeLatest

takeEvery

takeEvery 允许多个 fetchData 实例同时启动。在某个特定时刻,尽管之前还有一个或多个 fetchData 尚未结束,我们还是可以启动一个新的 fetchData 任务

让我们通过常见的 AJAX 例子来演示一下。每次点击 Fetch 按钮时,我们发起一个 FETCH_REQUESTED 的 action。 我们想通过启动一个从服务器获取一些数据的任务,来处理这个 action。

首先我们创建一个将执行异步 action 的任务:

    import { call, put } from 'redux-saga/effects'

    export function* fetchData(action) {
        try {
            const data = yield call(Api.fetchUser, action.payload.url);
            yield put({type: "FETCH_SUCCEEDED", data});
        } catch (error) {
            yield put({type: "FETCH_FAILED", error});
        }
    }

然后在每次 FETCH_REQUESTED action 被发起时启动上面的任务。

    import { takeEvery } from 'redux-saga'

    function* watchFetchData() {
        yield* takeEvery('FETCH_REQUESTED', fetchData)
    }

    // 注:上面的 takeEvery 函数可以使用下面的写法替换
    function* watchFetchData() {
    
        while(true){
            yield take('FETCH_REQUESTED');
            yield fork(fetchData);
        }
    }
takeLatest

如果我们只想得到最新那个请求的响应(例如,始终显示最新版本的数据),我们可以使用 takeLatest 辅助函数

    import { takeLatest } from 'redux-saga'

    function* watchFetchData() {
    yield* takeLatest('FETCH_REQUESTED', fetchData)
    }

和takeEvery不同,在任何时刻takeLatest只允许执行一个fetchData任务,并且这个任务是最后被启动的那个,如果之前已经有一个任务在执行,那之前的这个任务将会自动被取消

Effect Generator

redux-saga框架提供了很多创建effect的函数,下面我们就来简单的介绍下开发中最常用的几种:

  • take(pattern)
  • put(action)
  • call(fn, ...args)
  • fork(fn, ...args)
  • select(selector, ...args)

take(pattern)
take函数可以理解为监听未来的action,它创建了一个命令对象,告诉middleware等待一个特定的action, Generator会暂停,直到一个与pattern匹配的action被发起,才会继续执行下面的语句,也就是说,take是一个阻塞的 effect

    function* watchFetchData() {
        while(true) {
        // 监听一个type'FETCH_REQUESTED'的action的执行,直到等到这个Action被触发,才会接着执行下面的 		yield fork(fetchData)  语句
            yield take('FETCH_REQUESTED');
            yield fork(fetchData);
        }
    }

put(action)
put函数是用来发送action的 effect,你可以简单的把它理解成为redux框架中的dispatch函数,当put一个action后,reducer中就会计算新的state并返回,注意: put 也是阻塞 effect

    export function* toggleItemFlow() {
        let list = []
        // 发送一个type'UPDATE_DATA' 的Action,用来更新数据,参数为 `data:list`
        yield put({
        type: actionTypes.UPDATE_DATA,
        data: list
        })
    }

call(fn, ...args)
call函数你可以把它简单的理解为就是可以调用其他函数的函数,它命令 middleware 来调用fn 函数, args为函数的参数,注意: fn 函数可以是一个 Generator 函数,也可以是一个返回 Promise 的普通函数,call 函数也是阻塞 effect

    export const delay = ms => new Promise(resolve => setTimeout(resolve, ms))

    export function* removeItem() {
        try {
            // 这里call 函数就调用了 delay 函数,delay 函数为一个返回promise 的函数
            return yield call(delay, 500)
        } catch (err) {
            yield put({type: actionTypes.ERROR})
        }
    }

fork(fn, ...args)
fork 函数和 call 函数很像,都是用来调用其他函数的,但是fork函数是非阻塞函数,也就是说,程序执行完 yield fork(fn, args) 这一行代码后,会立即接着执行下一行代码语句,而不会等待fn函数返回结果后,在执行下面的语句

    import { fork } from 'redux-saga/effects'

    export default function* rootSaga() {
    // 下面的四个 Generator 函数会一次执行,不会阻塞执行
    yield fork(addItemFlow)
    yield fork(removeItemFlow)
    yield fork(toggleItemFlow)
    yield fork(modifyItem)
    }

select(selector, ...args)
select 函数是用来指示 middleware调用提供的选择器获取Store上的state数据,你也可以简单的把它理解为redux框架中获取store上的 state数据一样的功能 :store.getState()

    export function* toggleItemFlow() {
        // 通过 select effect 来获取 全局 state上的 `getTodoList` 中的 list
        let tempList = yield select(state => state.getTodoList.list)
    }