zoukankan      html  css  js  c++  java
  • Redux 中间件和异步操作

      回顾一下Redux的数据流转,用户点击按钮发送了一个action,  reducer 就根据action 和以前的state 计算出了新的state, store.subscribe 方法的回调函数中 store.getState() 获取新的state, 把state 注入到页面元素中,实现页面状态的更新。你发现根本就没有机会去做一个异步的操作,但现实世界中又有大量的异步操作,那怎么办? 这就用到了Redux的中间件。

      中间件,说白了,就是中间的部分,正常的流程中,先执行函数a,再执行 b 函数,但如果在a 和b之间插入了中间件,那么流程就变成了,先执行a 函数,再执行中间件,最后执行b函数, 如果中间件在执行的过程中出错了,那b 函数就没有机会执行了,可以看到中间件的出现,阻碍a 到b 的顺序执行,那么这时候,我们就可以在中间件中作一系的事情,比如日志的记录,异步处理。具体到Redux中就是,在正常的Redux流程 中,发送一个action, 它立刻会到reducer中,那么我们就要在发送action 和进入reducer 之间插入中间件,阻止这种顺序的操作,我们在这些中间件中作异步操作等等。怎样插入中间件呢? Redux 提供了一个applyMiddleware 函数,把要使用的中间件作为参数传递给它就好了,  applyMiddleware 函数在createStore的时候调用,使用到的中间件是redux-thunk, 用于发送异步请求。为了更好使用中间件,纯html 方式不太好演示,这里简配置一个webpack,  只使用webapck-dev-server 就可以了。

      在任意的文件夹中打开命令行工具(git bash),  mkdir redux-middleware && cd redux-middleware && npm init -y  快速创建项目。npm i webpack webpack-dev-server webpack-cli -D 安装webpack-dev-server. webpack4 提供了零配置,默认的入口文件是src目录下的index.js, 要创建src目录和它下面的index.js。 由于webpack-dev-server 是把文件打包到内存中,打包后的文件放到根目录下面的main.js, 所以main.js 不用建,不过要建一个html 文件引入它们, 最后npm install redux redux-thunk -S.

      redux-thunk 作为异步请求的中间件,使用方式非常简单,当dispatch 一个函数的时候,它就执行这个函数,而不传递给reducer, 只有当dispatch 一个对象的时候,它才会传递到reducer 中,进行state的更改。也就是说,当进行异步请求的时候,首先要dispatch一个函数,也就是说action creator 要返回一个函数(异步action creator),这个函数有一个dispatch 作为参数,函数体内就可以发送异步请求, 然后在函数的内部,比如获取到数据了或报错了, 再dispatch 一个对象,把获取的到数据或错误信息传递到reducer 中,进而改变state,完成数据的更新。模板如下

    function asyncActionCreator() {
      return dispacth => {
        fetchData(url)
          .then(() => {
            dispacth({
              type: 'success'
            })
          })
          .catch(err => {
            dispacth({
              type: 'failure'
            })
          })
      }
    }
    
    store.dispacth(asyncActionCreator())

       现在做一个简单的实例,就是点击按钮,随机获取一个用户的信息,网上有这么一个api接口https://randomuser.me/api/?results=1 , 后面的result=1 表示一次只获取一个,是一个get 请求,返回的结果(json 格式)如下,进行了删减, 只显示name,geneder, email 和img

    {
        "results": [
            {
                "gender": "female",
                "name": {
                    "title": "mrs",
                    "first": "minttu",
                    "last": "murto"
                },
                "email": "minttu.murto@example.com",
                "picture": {
                    "large": "https://randomuser.me/api/portraits/women/40.jpg",
                    "medium": "https://randomuser.me/api/portraits/med/women/40.jpg",
                    "thumbnail": "https://randomuser.me/api/portraits/thumb/women/40.jpg"
                }
            }
        ]
    }

      准备工作完毕,开始写代码,但在写之前,还要考虑一下程序的state, 到底存储什么信息,页面上要展示什么?看一下操作,点击按钮,发起异步的数据请求,这里是一个异步action, 异步请求开始,这时就要给用户提示,需要一个状态status,提示正在加载,因此也需要一个action; 请求数据成功,获取到user,除了status 需要更新之外,它还需要一个状态 user 来存储获取到的数据,需要一个action. 如果失败了,通常更新status 表示加载失败,一个action. 只要涉及到状态的改变,都需要一个action, 在Redux 中只有action 才能改变state. 此外最好再加两个state,statusClass 和sendingRequest. statusClass 对提示的status 进行样式处理,比如加载失败用红色.  sendingRequest 它的取值为true or false, 表示有没有在请求,可以通过它,来决定页面上显示什么内容,比如在加载的时候,不显示用户信息。这两个状态随着status的变化而变化,所以不需要action.那我们的state 就如下所示, 在index.js 中书写

    // 初始状态, 定义state的形态
    const initialState = {
        sendingRequest: false, // 是否正在请求
        status: '',    // 加载提示
        statusClass: '',  // 加载提示样式
        user: {  // 用户信息
            name: '',
            gender: '',
            email: '',
            img: ''
        }
    }

      那页面上显示信息呢?status 加载提示和user 信息,status 这里用bootstrap 的alert 组件 ,user 信息用Card组件.  当然status 还要加上样式stautsClass

    <body style="text-align: center; margin-top: 50px">
        <button type="button" class="btn btn-primary" id="getUser">获取用户信息</button>
        <!-- 加载提示 -->
        <div style=" 18rem; margin: 20px auto; display: none" id="status"></div>
        <!-- 用户信息 -->
        <div class="card" style=" 20rem; margin: 20px auto; display: none" id="userInfo">
            <img class="card-img-top" src="" id="img">
            <div class="card-body">
                <h5 class="card-title" id="name"></h5>
                <h5 class="card-title" id="gender"></h5>
                <h5 class="card-title" id="email"></h5>
            </div>
        </div>
    
        <script src="main.js"></script>
    </body>

       npx webpack-dev-server 启动服务器, 浏览器中输入localhost:8080 看一下效果, 可以看到在初始状态,加载提示和用户信息都是display 为none,不显示,整个页面只显示一个button. 

      那么现在要做的就是当用户点击的时候,动态显示提示内容,

      和最终的用户信息

     

      好了,现在开始写js 代码来实现这个功能。通过分析state的时候,一个异步action,就是点击按钮发送请求,它需要写一个异步的action creator. 三个同步action  因为它们改变状态,请求发送type: 'USER_REQUEST';  请求成功 type: 'USER_RECEIVED',  还要带有一个请求成功回来的user;数据请求失败type:  'USER_FAIL'; 写一下这四个action creator

    // 三个同步action 都是返回的对象,用来改变state.
    function userRequest() {  //获取数据时
        return { type: 'USER_REQUEST' }
    }
    
    function userReceived(userData) { // 获取成功
        return {
            type: 'USER_RECEIVED',
            payload: userData
        }
    }
    
    function userFail() { // 获取失败
        return {
            type: 'USER_FAIL'
        }
    }
    
    // getUser的异步action, 注意,它一定返回的是一个函数, 该函数有一个dispatch 作为参数,
    // 该函数内部根据不同的情况发送不同的 同步action 来改变state
    function getUser() {
        return dispatch => {
            dispatch(userRequest()); // 正在请求action,'USER_REQUEST', 整个应用的state 可以设为status为‘正在加载’
    
            return fetch('https://randomuser.me/api/?results=1')
                .then(
                    response => {
                        if (response.ok) {
                            return response.json()
                        } else {
                            return undefined;
                        }
                    },
                    error => {
                        dispatch(userFail(error));  // 请求失败的action, 'USER_FAIL',status为‘加载失败’
                    }
                )
                .then(json => {
                    console.log(json)
                    if (!json) {
                        dispatch(userFail()); 
                        return;
                    }
                    dispatch(userReceived(json.results)) // 请求成功的action 'USER_RECEIVED', 直接更改user
                })
        }
    }

      现在有了action 就再写reducer 了,改变的状态的action只有3个,type: 'USER_REQUEST';   type: 'USER_RECEIVED',   type: 'USER_FAIL'

      type: 'USER_REQUEST', 表示正在发送请求,那么 sendingRequest 肯定设为true, status 就设为'正在加载', 正在加载 使用样式statusClass 为 'alert alert-info';

      type: 'USER_RECEIVED', 请求成功了,sendingRequest 设为false, user的信息也获取到了,status 也就不用显示了,还是初始化的'' ,statusClass 也是一样,不过user 状态的处理有点麻烦,因为在intialState中 user 是一个对象,所以在这里要重新创建一个对象user 来替换以前的user, 又因为获取回来的数据不能直接使用,所以进行拼接,然后给新创建的user 对象的属性一一进行赋值。

      type: 'USER_FAIL': 请求失败了,虽然失败了,但请求还是发送成功了, 所以sendingRequest 设为false, 由于没有获取到数据,user 信息不用更新,还是初始化的状态,但status 信息肯定要更新,status 设为 '加载数据失败' ,statusClass 为红色 'alert alert-danger'

      最后不要忘记default 分支,返回默认的初始值。

    function userState(state = initialState, action) {
        switch (action.type) {
            case 'USER_REQUEST':
                return {
                    ...state,
                    sendingRequest: true,
                    status: '正在加载',
                    statusClass: 'alert alert-info'
                }
            case 'USER_RECEIVED': {
                const user = {
                    name: '',
                    email: '',
                    gender: '',
                    img: ''
                }
                user.name = `${action.payload[0].name.first} ${action.payload[0].name.last}`
                user.email = action.payload[0].email;
                user.gender = action.payload[0].gender;
                user.img = action.payload[0].picture.large;
                return {
                    ...state,
                    user,
                    sendingRequest: false
                }
            }
            case 'USER_FAIL':
                return {
                    ...state,
                    sendingRequest: false,
                    status: '获取数据失败',
                    statusClass: 'alert alert-danger'
                }
            default:
                return state;
        }
    }

      有了reducer , 终于可以创建store了,由于使用es6 模块化,所以要进行引用,由于使用了中间件redux-thunk, 所以要引入createStore, applyMiddleware,  thunk, index.js 顶部

    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';

      创建store

    // 创建store, 要把中间件传进去
    const store = createStore(userState, applyMiddleware(thunk))

      获取状态渲染页面了,使用document.getElementById 获取元素比较简单,稍微有点麻烦的是页面元素的切换,首先是 sendingRequest 的判断,为true时,正在请求,只能显示加载提示,为false的时候,表示请求完成,但又有两种情况,成功或失败,所以渲染函数如下

    const statusEl = document.getElementById('status'); // 加载提示部分
    const userInfoEl = document.getElementById('userInfo'); // 用户信息展示部分
    const nameEl = document.getElementById('name');
    const genderEl = document.getElementById('gender');
    const emailEl = document.getElementById('email');
    const imgEl = document.getElementById('img');
    
    function render() {
        const state = store.getState();
        if (state.sendingRequest) { // 正在请求的时候,显示提示信息status, 用户信息不显示
            userInfoEl.style.display = 'none';
            statusEl.style.display = 'block';
            statusEl.innerHTML = state.status;
            statusEl.className = state.statusClass;
        } else { // 请求完成后它又分两种情况,
            console.log(state)
            if (state.user.name !== '') { // 请求成功,获取到user 显示user 
                userInfoEl.style.display = 'block';
                statusEl.style.display = 'none';
                nameEl.innerHTML = state.user.name;
                emailEl.innerHTML = state.user.email;
                genderEl.innerHTML = state.user.gender;
                imgEl.src = state.user.img;
            } else { // 请求失败,这里按理说应该写一个error 显示的,为了简单,直接使用了提示status 
                userInfoEl.style.display = 'none';
                statusEl.style.display = 'block';
                statusEl.innerHTML = state.status;
                statusEl.className = state.statusClass;
            }
        }
    }

      使用subscribe 监听状态的变化,然后调用render 函数。

    store.subscribe(() => {
        render()
    })

      最后就是获取到button, 添加点击事件,发送异步action getUser

    // 点击按钮发送请求
    document.getElementById('getUser').addEventListener('click', () => {
        store.dispatch(getUser());
    })

      整个功能就完成了。这时随着功能的复杂,程序开发过程中随时都有可能出现错误,就需要进行调试,对于redux 来说,最主要的就发送的action 和 改变的state, 如果能记录下来,就可以加快调试,有一个中间件,就是redux-logger, 干这个活, npm install redux-logger --save,  安装完成后,使用就简单了,在js 中引入,并放入到applyMiddleware

    import { createLogger } from 'redux-logger';
    const store = createStore(userState, applyMiddleware(thunk, createLogger()));

       总结一下在redux-thunk 中间件下的异步请求,整个请求过程放到一个接受dispatch 作为参数的函数体( dispatch => { 异步请求})中,只有dispatch 一个函数,redux-thunk 才不会把action 发送给reducer.  而在异步请求的过程中,至少要发送三个同步action, 请求中,请求成功,请求失败,它们要传递给reducer 去改变state, 因为这些信息是要给用户看的,相应的,我们的 reducer 也要至少处理 请求中,请求成功,请求失败 三个action, 返回各个对应的状态。 

  • 相关阅读:
    streamsets 集成 cratedb 测试
    streamsets k8s 部署试用
    streamsets rest api 转换 graphql
    StreamSets sdc rpc 测试
    StreamSets 相关文章
    StreamSets 多线程 Pipelines
    StreamSets SDC RPC Pipelines说明
    StreamSets 管理 SDC Edge上的pipeline
    StreamSets 部署 Pipelines 到 SDC Edge
    StreamSets 设计Edge pipeline
  • 原文地址:https://www.cnblogs.com/SamWeb/p/11332484.html
Copyright © 2011-2022 走看看