redux-saga

作者: 林ze宏 | 来源:发表于2019-07-07 21:13 被阅读0次

    1 概述

    redux-saga 是 redux 一个中间件,用于解决异步问题。

    2 es6 Generator

    解决地狱回调问题,通过 yield 关键字,可以让函数的执行流挂起。

    使用在线工具验证:https://jsbin.com/musedihito/edit?js,console

    参考:
    https://blog.csdn.net/tcy83/article/details/80427195

    2.1 基础示例

    function* geo() { // 声明一个 Generator 函数
      const x = yield 'xxx';
      console.log(x); // 返回的是 undefined
      const y = yield 'yyy'
    }
    const Geo = geo(); // 调用,返回 Generator 对象赋值给 Geo
    console.log(Geo.next()); // 通过 next() 方法,执行Generator 中的函数体。
    console.log(Geo.next());
    console.log(Geo.next());
    
    
    
    结果

    2.1.1 进一步分析

    yield和next方法 例子1 例子2
    function* geo() {
      const post = yield $.getJSON("https://jsonplaceholder.typicode.com/posts");
      console.log(post[0].title);
      const users = yield $.getJSON("https://jsonplaceholder.typicode.com/users");
      console.log(users[0]);
      
    }
    
    run(geo);
    
    function run(geo) {
      const Geo = geo(); 
      
      function handle(yielded) {
        if(!yielded.done) {
          yielded.value.then(function(data){
            return handle(Geo.next(data));
          })
        }
      }
      
      handle(Geo.next());
      
    }
    
    
    

    2.3 区别 yield* 和 yield

    区别yield和yield*

    4 简单理解 'redux-saga/effects' 中的几个关键字:fork,call, put,takeEvery,takeLatest,all

    4.1 fork

    参考:https://redux-saga-in-chinese.js.org/docs/advanced/ForkModel.html

    创建一个新的进程或者线程,并发发送请求。

    关键代码:
    
    function* user() {
      yield takeEvery('FETCH_REQUEST', fetch_user); // 监听 FETCH_REQUEST action
    }
    
    // 并发发送请求
    function* fetch_user() {
      const [users, todos] = [
        yield fork(fetchResource, 'https://jsonplaceholder.typicode.com/users'),
        yield fork(fetchResource, 'https://jsonplaceholder.typicode.com/todos')
      ]
    }
    
    
    function* fetchResource(resource) {
      const data = yield call(axios.get, resource);
      
      // 获取 call 数据,触发成功后的 action
      yield put({ type: 'FETCH_SUCESS', uu: data });
      
    }
    
    

    4.2 call

    发送 api 请求

    4.3 put

    发送对应的 dispatch,触发对应的 action

    4.4 takeEvery

    • 监听对应的 action;
    • 每一次 dispatch 都会触发;例如:点击一个新增的按钮,2s 后触发新增动作,在2s内不断点击按钮,这时候,每一次点击,都是有效的。

    yield takeEvery('FETCH_USER', fetch_user);

    4.5 takeLatest

    • 监听对应的 action;
    • 只会触发最后一次 dispatch;例如:点击一个新增的按钮,2s 后触发新增动作,在2s内不断点击按钮,这时候,只有最后一次点击是有效的。

    yield takeLatest('FETCH_USER', fetch_user);

    4.6 all

    跟 fork 一样,同时并发多个 action,没有顺序。

    yield all([ // 同时并发多个
      ...rootUser,
      add()
    ])
    
    

    5 demo 示例

    使用 create-react-app 脚手架初始化;
    在线请求API:https://jsonplaceholder.typicode.com/

    目录结构

    src/index.js:项目入口文件配置 redux 和 saga

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './App';
    import * as serviceWorker from './serviceWorker';
    
    import { createStore, applyMiddleware } from 'redux'; // 中间件和 store
    import { Provider } from 'react-redux'; // provider
    import { composeWithDevTools } from 'redux-devtools-extension'; // 调试工具
    import rootReducer from './reducers'; // reducers
    
    import createSagaMiddleware from 'redux-saga'; // 1:saga 引入createSagaMiddleware
    import rootSaga from './sagas'; // 5:自己写的根 rootSaga
    
    
    // 2:创建saga中间件
    const sagaMiddleware = createSagaMiddleware()
    
    const store = createStore(
      rootReducer,
      composeWithDevTools( // 3:把 sagaMiddleware 当做一个中间件,引用调试工具
        applyMiddleware(sagaMiddleware)
      )
    )
    
    // 4:启动 saga
    sagaMiddleware.run(rootSaga);
    
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root')
    );
    
    serviceWorker.unregister();
    
    
    

    src/App.js:页面

    import React, { Component } from 'react';
    import './App.css';
    import { connect } from 'react-redux';
    import { increate, increateAsync, fetch_user } from './actions/counter';
    
    class App extends Component {
    
      render() {
        const { isFetch, error, user } = this.props.users;
        let data = "";
        if (isFetch) {
          data = '正在加载中。。。'
        } else if (user) {
          data = user.data[0]['name'];
        } else if (error) {
          data = error.message;
        }
        return (
          <div className="App">
            {/* 触发dispatch,发送对应的action */}
            <div style={{ marginBottom: 20 }}>
              <p>{this.props.counter}</p>
              <button onClick={() => this.props.increate()}>新增</button>
              &nbsp;&nbsp;&nbsp;
              <button onClick={() => this.props.increateAsync()}>异步新增</button>
              &nbsp;&nbsp;&nbsp;
              <button onClick={() => this.props.fetch_user()}>axios请求</button>
            </div>
            <h2>{data}</h2>
          </div>
        );
      }
    }
    
    const mapStateToProps = (state) => {
      return {
        counter: state.counter, // state 对应的 key, 在 reducers/index.js 中声明。
        users: state.us
      }
    }
    
    // mapStateToProps,在 reducers/index.js 中,通过 connect 导入对应的 state
    // { increate, increateAsync, fetch_user } ,通过 connect 导入对应的action,在view触发相应的action
    export default connect(mapStateToProps, { increate, increateAsync, fetch_user })(App);
    
    
    
    

    constants/index.js:常量

    export const INCREMENT = "INCREMENT";
    export const INCREMENT_ASYNC = "INCREMENT_ASYNC";
    
    
    

    actions/counter.js:action

    import { INCREMENT, INCREMENT_ASYNC } from '../constants';
    
    export const increate = () => {
      return {
        type: INCREMENT
      }
    }
    
    export const increateAsync = () => {
      return {
        type: INCREMENT_ASYNC
      }
    }
    
    
    export const fetch_user = () => {
      return {
        type: 'FETCH_REQUEST'
        // type: 'FETCH_USER'
      }
    }
    
    
    
    

    reducers/index.js:reducers 的入口文件

    import { combineReducers } from 'redux';
    import us from './users';
    import counter from './counter';
    
    export default combineReducers({
      us,
      counter
    })
    
    

    reducers/counter.js:

    import { INCREMENT } from '../constants';
    
    const counter = (state = 1, action = {}) => {
      switch (action.type) {
        case INCREMENT:
          return state + 1;
        default: return state
      }
    }
    
    export default counter;
    
    
    

    reducers/users.js:

    const initialState = {
      isFetch: false,
      error: null,
      user: null
    }
    
    const u = (state = initialState, action = {}) => {
      switch (action.type) {
        case 'FETCH_REQUEST':
          return state = {
            isFetch: true,
            error: null,
            user: null
          };
        case 'FETCH_SUCESS':
          return state = {
            isFetch: false,
            error: null,
            user: action.uu
          };
        case 'FETCH_FAIL':
          return state = {
            isFetch: false,
            error: action.errors,
            user: null
          };
        default: return state
      }
    }
    
    export default u;
    
    

    sagas/index.js:sagas 的入口文件

    import { all, fork } from 'redux-saga/effects';
    import rootUser from './fetchUser';
    import { add } from './counter';
    
    export default function* rootSaga() {
      yield all([ // 同时并发多个
        ...rootUser,
        add()
      ])
    }
    
    
    

    sagas/counter.js:

    import { INCREMENT_ASYNC, INCREMENT } from '../constants';
    import { delay } from 'redux-saga';
    import { call, put, takeEvery} from 'redux-saga/effects';
    
    function* increase() {
      yield call(delay, 1000); // 需要执行异步的时候,直接调用 call
      yield put({ type: INCREMENT });
    }
    
    // 直接 export 函数,没有做整理
    export function* add() {
      yield takeEvery(INCREMENT_ASYNC, increase);
    }
    
    
    

    sagas/fetchUser.js:

    使用数组导出函数,就不用一个一个函数导出,优化了代码!
    
    import { call, takeEvery, put, fork } from 'redux-saga/effects';
    import { delay } from 'redux-saga';
    import axios from 'axios'
    
    function* fetch_user() {
      try {
        const users = yield call(axios.get, 'https://jsonplaceholder.typicode.com/users'); // axios.get('https://jsonplaceholder.typicode.com/users')      
        yield put({ type: 'FETCH_SUCESS', uu: users });
      } catch (e) {
        yield put({ type: 'FETCH_FAIL', errors: e });
      }
    }
    
    function* fetch_todo() {
      const todos = yield call(axios.get, 'https://jsonplaceholder.typicode.com/todos'); // axios.get('https://jsonplaceholder.typicode.com/users')      
      console.log(todos);
    }
    
    function* user() {
      yield takeEvery('FETCH_REQUEST', fetch_user); // 正在加载数据
    }
    
    function* todo() {
      yield takeEvery('FETCH_TODO', fetch_todo);
    }
    
    // 使用数组导出
    const rootUser = [
      user(),
      todo()
    ]
    
    export default rootUser;
    
    
    

    package.json:

    {
      "name": "redux-saga-demo",
      "version": "0.1.0",
      "private": true,
      "dependencies": {
        "axios": "^0.18.0",
        "react": "^16.6.0",
        "react-dom": "^16.6.0",
        "react-redux": "^5.1.0",
        "react-scripts": "2.1.0",
        "redux": "^4.0.1",
        "redux-devtools-extension": "^2.13.5",
        "redux-saga": "^0.16.2"
      },
      "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject"
      },
      "eslintConfig": {
        "extends": "react-app"
      },
      "browserslist": [
        ">0.2%",
        "not dead",
        "not ie <= 11",
        "not op_mini all"
      ]
    }
    
    
    
    效果

    6 总结体会

    1:使用同步的操作,处理异步的请求;
    2:使用 redux + redux-saga,在入口文件 index.js 配置 saga;
    3:在 saga 中,使用 takeEvery 或者 takeLatest 在项目启动的时候,监听对应的 action,触发对应的 action;
    4:当页面触发了对应的 action 时,除了会去寻找对应的 reducer(找不到也没事),进行操作;也会触发 saga 监听的 action,进行异步请求等操作;

    代码:
    https://github.com/hongzelin/redux-saga-demo

    7 参考

    相关文章

      网友评论

        本文标题:redux-saga

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