首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >『Dva』深入解析 Dva 进阶特性:打造健壮的前端应用

『Dva』深入解析 Dva 进阶特性:打造健壮的前端应用

原创
作者头像
程序员NEO
修改2024-12-27 01:16:02
修改2024-12-27 01:16:02
1.1K0
举报

一、前言

🐤本篇文章是『从零玩转 TypeScript + React 项目实战』系列文章的第 9 篇,《深入解析 Dva 进阶特性:打造健壮的前端应用》

在前端开发中,状态管理一直是一个重要话题。作为一个结合了 Redux 和 Redux-saga 的轻量级框架,Dva 为我们提供了优雅的数据流解决方案。本文将深入探讨 Dva 中的高级特性,包括全局错误处理、中间件配置以及状态初始化等重要概念。

在上一篇文章当中给大家详细的介绍完了 Dva 中的路由之间的跳转,以及如何在 Dva 中使用路由。在这篇文章当中,将会继续深入的了解 Dva 的进阶特性,帮助大家打造健壮的前端应用。

了解什么呢?打开官方文档:https://dvajs.xiniushu.com/api/ 进入到 API 栏目,我们可以看到 Dva 的 API 列表,这里我要找的是 app = dva(opts),这个 API 用来创建一个 Dva 实例。在这个 API 当中,我们可以看到有一个 opts 参数,这个参数是一个对象,里面包含了很多配置项,这些配置项就是我们接下来要学习的内容。

在上篇介绍路由跳转的时候,是不是传递了一个 history 对象,其实除了 history 对象之外,还有很多其他的配置项,这些配置项可以帮助我们更好的管理应用的状态,提升应用的健壮性。接下来,我们就来详细了解这些配置项。

1.1 initialState(初始化状态)

通过对官方文档的观察有非常多的配置项,这里呢我就是带着大家来看其中的一些比较重要的配置项,首先呢就是 initialState,这个配置项是用来初始化状态的。在 Dva 中,我们可以通过 initialState 来初始化应用的状态。

initialState 是用来专门初始化 Model 中的 state 默认值的,那怎么指定呢?也非常的简单,在官方文档中往下滚往下滚,应该是有对应的示例的,我滚啊滚啊,好像没有看到,没有看到也不要紧,自己直接写。

找到项目的 index.js 文件当中有一个 homeModel,在 homeModel 中的 state 当中有一个 count,值为 666:

其实我告诉大家除了在 Model 中定义初始状态之外,还可以在创建 Dva 实例的时候通过 initialState 来初始化状态。

废话不多说,直接上代码,通过 key value 的形式,将 home 模型的 count 设置为 777:

  • key:意思是给哪个模型设置初始状态,这里是 home 模型,所以 key 就是 home,对应的是模型的命名空间。
代码语言:javascript
复制
const app = dva({
  initialState: {
    home: {
      count: 777
    }
  }
});

我来解释一下上面代码,首先通过 dva() 创建了一个 Dva 实例,然后通过 initialState 属性来初始化 home 模型的状态。initialState 的值是一个对象,对象的 key 是模型的命名空间,value 是模型的初始状态。value 是一个对象,对象的 key 是模型的属性,value 是属性的初始值。

总的来说就是初始化命名空间为 home 的这个 Model 的数据,初始化什么数据呢?初始化它 state 中的 count 的数据,所以上面的代码就是如此的意思。

需要注意的是,如果在模型中也定义了初始状态,initialState 中的值会覆盖模型中的初始值。

如果同时在 initialState 和 Model 中都指定了数据的初始值,那么 initialState 的优先级高于 Model。

如何验证,直接访问浏览器,查看页面上的数据:

可以看到,页面上的数据已经变成了 777,说明我们通过 initialState 成功初始化了 home 模型的状态。

我这里没有报错,如果有小伙伴报错了,错误信息是 TypeError:Cannot read property 'name' of undefined,这个错误是因为在 Model 中定义了初始状态,但是没有在 initialState 中初始化,所以会报错。解决方法就是在 initialState 中初始化一下。

经过我的观察发现,我没有报错的原因是因为我没有在 Home 组件中使用到 name 这个属性,所以没有报错。如果在 Home 组件中使用了 name 这个属性,那么就会报错:

因为在 initialState 中的初始化优先级高,初始化 home 中的数据的时候,并没有初始化 home 中的 info 对象,所以会报错。

所以为了避免这个错误,我们可以在 initialState 中初始化一下:

代码语言:javascript
复制
const app = dva({
  initialState: {
    home: {
      count: 777,
      info: {
        name: 'NEO'
      }
    }
  }
});

这样就不会报错了,因为我们在 initialState 中初始化了 info 对象,所以就不会报错了, 访问页面,数据正常显示。

1.2 全局错误处理(onError)

initialState 看完了,继续看一到两个,这次看 onError,这个配置项是用来全局处理错误的(处理错误的东西)

在 Dva 中,我们可以通过 onError 钩子来捕获和处理全局错误,提升应用的健壮性。如何编写呢?也非常的简单,直接上代码:

代码语言:javascript
复制
const app = dva({
  onError: (error, dispatch) => {
    console.error('全局错误:', error);
    alert('全局错误:' + error.message);
  }
});

上面的代码中,在创建 Dva 实例时,我们通过 onError 配置项来定义一个全局错误处理函数。onError 函数接收两个参数:errordispatcherror 是捕获到的错误对象,dispatch 是用来派发 action 的函数。

onError 函数中,我们可以通过 error 对象来获取错误信息,并通过 alert 函数来弹出错误提示框。这样,当应用发生错误时,我们就可以通过 onError 钩子来捕获并处理错误。

  1. onError 什么时候会触发呢?如果在 effects 中发生了错误,并且没有在 effects 中捕获错误,那么就会触发 onError,在 onError 中捕获。
  2. 如果在 subscriptions 中通过 done 传递了错误,那么也会在 onError 中捕获。

首先来看一下在 effects 中发生错误的情况,我在 effects 中故意写一个错误的代码即可演示,在 homeModel 中的 effects 中有一个 *asyncUserInfo 方法,这个方法中有一个请求后端服务器的部分,那么我直接将后端服务停了,那么当我请求服务器的时候,如果请求服务器请求不到数据,是不是就会发生错误。easy~

虽然说我关掉了服务器,我但是在 .catch() 中捕获了错误,所以就会在 .catch() 中错误,那就先看一下再 catch() 中处理错误的情况,我就直接在 .catch() 中打印一下错误:

然后访问页面,点击一下页面上的获取按钮(如果没有按钮请新增,因为我前几篇文章当中是有写这个按钮的,在写后面的文章中我会删除一些内容,文章的代码都用 git 管理,所以可以通过 git 来查看之前的代码),我这里也贴一下代码:

代码语言:javascript
复制
<hr/>
<button onClick={() => {
    props.getUserInfo()
}}>
    获取
</button>

非常的简单,Dispatch 中已经存在 getUserInfo 了这个我就不贴了,没有的大家可以去翻阅我之前的文章,然后点击获取按钮,然后看一下控制台,这回肯定是请求不到数据的,因为我关掉了服务器:

可以看到,控制台中打印了错误信息,说明在 effects 中发生了错误,然后在 .catch() 中捕获了错误,所以就不会触发 onError。

接下来,我将 .catch() 中的错误去掉:

这样就会在 onError 中捕获错误,然后访问页面,点击获取按钮,这会发现页面上弹出了一个提示框,提示框中显示了错误信息,说明我们通过 onError 成功捕获了全局错误。

在演示之前我发现我之前在 *asyncUserInfo 中的处理异常的代码写的有问题,我先解释一下这个问题:

  1. 首先,dva 中的 onError 是全局错误处理器,用于捕获所有 effect 和 subscription 中未被捕获的错误。
  2. 在当前代码中,虽然在 fetch 的 catch 中处理了错误,但是处理后没有返回任何值,这会导致 data 为 undefined。
  3. 当后续代码尝试访问 data.data 时会触发运行时错误,这个错误会被 onError 捕获。

所以如上演示全局错误处理的时候,虽然我将 .catch() 中的错误处理代码去掉了,全局 onError 也会捕获到错误:

验证了 effects 中没有处理异常就会进入全局,这个时候我头脑一思考,发现还可以做一个测试,那么就是如果我将 .catch() 中的错误处理代码加上,然后再演示一下,流程是不是就只会走 .catch() 中的错误处理代码,不会走全局 onError 了呢?发现并不是这样就引发出了上面的问题。

修改代码来正确处理这个问题:

  1. 使用 try/catch 包裹整个异步操作
  2. 在 catch 中记录错误,提供了一个默认值
代码语言:javascript
复制
* asyncUserInfo(state, {put}) {
    try {
        const response = yield fetch('http://localhost:4000/api/data');
        const data = yield response.json();
        yield put({type: 'changeInfo', info: data.data});
    } catch (error) {
        console.log('effects error', error);
        // 如果需要在这里就处理完错误,阻止 onError 触发
        // 可以返回一个默认值
        yield put({type: 'changeInfo', info: {name: '获取数据失败'}});
    }
},

这样就不会出现 undefined 的问题,也不会触发 onError,如果你确实想让某些错误触发全局 onError,可以在 catch 中重新抛出错误:throw error;

浏览器运行结果如下 easy:

是不是在全局 onError 中捕获到了错误这个演示就没问题了,我在上面还说过如果在 subscriptions 中通过 done 传递了错误,那么也会在 onError 中捕获,那么我就来演示一下。

找到 homeModel 中的 subscriptions,这里有一个 setup 方法,这个方法除了接受一个对象之外,还传递了一个 done,这个 done 是一个回调函数,我就不在 setup 中使用 done,找到 change 因为这个方法没有做任何事情比较好看,在 change 接收 done:

然后在 change 方法中,通过 done new 一个 Error 对象,然后传递一个错误信息:

代码语言:javascript
复制
change({history, dispatch}, done) {
    console.log('change被执行了');
    done(new Error('自定义错误'));
},

这样就会在 subscriptions 中通过 done 传递了一个错误,然后在 onError 中捕获,访问页面,刷新一下,我是不是说过订阅的方法只已启动,已 start 就会被执行,所以我刷新页面就会弹出 alert 提示框:

关于 onError 的使用,在官方文档中也是有详细的介绍的,大家可以去查看:https://dvajs.xiniushu.com/api/#app-dva-opts

1.3 中间件配置(onAction)

onError 看完了,继续看一个,这次看 onAction,这个配置项是用来配置中间件的。为了避免页面上一来就我先将 subscriptions 中的 done 去掉:

注释掉了之后回过头来看 onAction,在 dva 中的 onAction 就是过去我们使用的 applyMiddleware,用来配置中间件的。

onAction 的作用:用来注册中间件的,比如说官网里面就有一个很好案例,它呢注册了一个打印日志的中间件,redux-logger,这个中间件是需要安装的,只需要 npm install 安装下即可。

安装 redux-logger:

代码语言:shell
复制
npm install redux-logger

安装完成之后,我们就可以在 dva 中使用 redux-logger 中间件了,如何使用呢?首先导入 redux-logger:

代码语言:javascript
复制
import createLogger from 'redux-logger';

然后在创建 Dva 实例的时候,通过 onAction 配置项来注册 redux-logger 中间件:

代码语言:javascript
复制
const app = dva({
  onAction: createLogger
});

在上面的例子中,就代表着我要注册一个 redux-logger 中间件,这个中间件是用来打印日志的,在我们派发任务的时候会帮我们打印一些日志。

这里有一个注意点:不是我的问题,是官方文档的问题,官方文档中说使用中间件的时候需要加上圆括号,这是老版本的 redux-logger 需要加,新版本的不需要加,所以这里不需要加圆括号,新版本会直接将 redux-logger 的实例对象返回给我们了。

然后访问页面,点击获取按钮,然后打开控制台,可以看到 redux-logger 打印的日志,在测试之前,要改造一下 add 与 sub,修改 home model 中的 add 和 sub reducer,确保返回时保留原有的 state 信息,否则页面使用了 props.info.name 会报错:

浏览器运行结果如下:

这个呢就是这个中间件的作用,就是用来打印日志,至于打印日志中的内容是什么含义,这个就不是我们这个文章的重点了,大家可以去查看 redux-logger 的官方文档,这里只是演示一下如何在 dva 中使用中间件。

其实这个作用在官方文档中也是有详细的介绍的:

关于剩下的内容:

代码语言:javascript
复制
onStateChange,
onReducer,
onEffect,
onHmr,
extraReducers,
extraEnhancers,

就不查看了,这些东西都非常简单,比如说 onStateChange,onStateChange 就是用来监听 state 的变化的,只要 state 发生变化,就会触发 onStateChange,onReducer 是用来封装 reducer 的一些方法,onEffect 是用来封装 effect 的一些方法,onHmr 是用来热更新的,extraReducers 是用来额外添加 reducer 的,extraEnhancers 是用来额外添加 enhancer 的。

比如说来看官方文档介绍的 extraReducers,比如要添加一个 redux-form,这个时候就用通过 extraReducers 来添加额外的 reducer:

那大家可能又会问,redux-form 是什么?redux-form 其实就是一个插件,这个插件的作用是什么,这个插件的作用就是可以帮你生成一个表单,然后这个表单当中可以自动把数据同步到 redux 中保存起来这类似的知道吧这就是 redux-form 插件的作用,也可以自己点击到 redux-form 的官方文档中查看就可以了,我不可能把这里面每一个东西都拿出来给大家写清楚讲清楚的,o了。

还有就是比如说 extraEnhancers,扩展的 enhancer,它的作用是什么呢?它的作用比如说我们可以配合 redux-persist,这是一个持久化存储的插件,我们都知道 redux 保存的数据呢,是保存到内存中的,但是如果说,我想把内存中的数据持久化到我们本地这个时候我们该怎么做呢?这个时候我们是不是可以使用一些 localStorage 或者 sessionStorage 这些东西,但是使用 localStorage 还要手动去编写代码,比较麻烦,这个时候呢就有人编写了一个插件,可以通过这个插件,自动把 redux 中保存在内存中的数据,给持久化到 localStorage 中,这类似的里面去,这个插件呢也比较简单也可以去官方文档中查看。

所以呢其它东西都不常用,比如说如下这些我刚刚所讲解的这些其实都不常用:

代码语言:javascript
复制
initialState,
onError,
onAction,
onStateChange,
onReducer,
onEffect,
onHmr,
extraReducers,
extraEnhancers,

常用的是什么,就是 dva,dva 是什么,它就是用来简化 redux,就是用来管理数据的,这个才是 dva 的核心,dva 的核心是什么?核心就是 model,它的核心就是把我们保存的数据,把我们的 reducer,把我们的同步操作,把我们的异步操作,都放到一个地方来进行管理,它的核心就是我们可以给每一个组件都创建一个 model,方便我们去管理数据这个才是它的核心!!!

所以说它的核心在我之前的文章中已经讲了讲解它的基本使用,讲解它的管理数据,异步操作的时候就是它的核心,文章链接呢我就不贴了,直接去翻阅我之前的文章就可以了。

从订阅路由路由的跳转这些内容呢是我们仅仅作为一个扩展,作为一个提高,作为一个进阶的内容,所以呢这些内容呢我就不再继续讲解了,这些内容呢大家可以去查看官方文档,官方文档中有详细的介绍,这些内容呢我就不再继续讲解了。

二、总结

本文深入探讨了 Dva 的几个重要进阶特性:

  1. initialState - 用于初始化全局状态
    • 可以在创建 dva 实例时统一配置初始状态
    • 优先级高于 Model 中定义的初始状态
    • 需要注意完整初始化所有用到的状态字段
  2. onError - 全局错误处理机制
    • 可以捕获 effects 中未处理的错误
    • 可以捕获 subscriptions 中通过 done 传递的错误
    • 便于统一处理和展示错误信息
  3. onAction - 中间件配置
    • 支持配置 Redux 中间件
    • 可以用于日志记录、持久化等功能
    • 例如集成 redux-logger 进行 action 日志追踪

虽然 Dva 提供了丰富的配置项(如 onStateChange、onReducer 等),但其核心价值在于通过 Model 机制简化 Redux 的使用,让状态管理变得更加直观和可维护。在实际开发中,应该根据项目需求合理使用这些特性,打造健壮的前端应用。

🐤如果您觉得本文对您有所帮助,欢迎点赞、收藏或分享,您的支持是我创作的最大动力!

感谢阅读
感谢阅读

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
    • 1.1 initialState(初始化状态)
    • 1.2 全局错误处理(onError)
    • 1.3 中间件配置(onAction)
  • 二、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档