

🐤本篇文章是『从零玩转 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 对象之外,还有很多其他的配置项,这些配置项可以帮助我们更好的管理应用的状态,提升应用的健壮性。接下来,我们就来详细了解这些配置项。
通过对官方文档的观察有非常多的配置项,这里呢我就是带着大家来看其中的一些比较重要的配置项,首先呢就是 initialState,这个配置项是用来初始化状态的。在 Dva 中,我们可以通过 initialState 来初始化应用的状态。
initialState 是用来专门初始化 Model 中的 state 默认值的,那怎么指定呢?也非常的简单,在官方文档中往下滚往下滚,应该是有对应的示例的,我滚啊滚啊,好像没有看到,没有看到也不要紧,自己直接写。
找到项目的 index.js 文件当中有一个 homeModel,在 homeModel 中的 state 当中有一个 count,值为 666:

其实我告诉大家除了在 Model 中定义初始状态之外,还可以在创建 Dva 实例的时候通过 initialState 来初始化状态。
废话不多说,直接上代码,通过 key value 的形式,将 home 模型的 count 设置为 777:
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 中初始化一下:
const app = dva({
initialState: {
home: {
count: 777,
info: {
name: 'NEO'
}
}
}
});
这样就不会报错了,因为我们在 initialState 中初始化了 info 对象,所以就不会报错了, 访问页面,数据正常显示。

initialState 看完了,继续看一到两个,这次看 onError,这个配置项是用来全局处理错误的(处理错误的东西)
在 Dva 中,我们可以通过 onError 钩子来捕获和处理全局错误,提升应用的健壮性。如何编写呢?也非常的简单,直接上代码:
const app = dva({
onError: (error, dispatch) => {
console.error('全局错误:', error);
alert('全局错误:' + error.message);
}
});上面的代码中,在创建 Dva 实例时,我们通过 onError 配置项来定义一个全局错误处理函数。onError 函数接收两个参数:error 和 dispatch。error 是捕获到的错误对象,dispatch 是用来派发 action 的函数。
在 onError 函数中,我们可以通过 error 对象来获取错误信息,并通过 alert 函数来弹出错误提示框。这样,当应用发生错误时,我们就可以通过 onError 钩子来捕获并处理错误。
首先来看一下在 effects 中发生错误的情况,我在 effects 中故意写一个错误的代码即可演示,在 homeModel 中的 effects 中有一个 *asyncUserInfo 方法,这个方法中有一个请求后端服务器的部分,那么我直接将后端服务停了,那么当我请求服务器的时候,如果请求服务器请求不到数据,是不是就会发生错误。easy~

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

然后访问页面,点击一下页面上的获取按钮(如果没有按钮请新增,因为我前几篇文章当中是有写这个按钮的,在写后面的文章中我会删除一些内容,文章的代码都用 git 管理,所以可以通过 git 来查看之前的代码),我这里也贴一下代码:
<hr/>
<button onClick={() => {
props.getUserInfo()
}}>
获取
</button>
非常的简单,Dispatch 中已经存在 getUserInfo 了这个我就不贴了,没有的大家可以去翻阅我之前的文章,然后点击获取按钮,然后看一下控制台,这回肯定是请求不到数据的,因为我关掉了服务器:

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

这样就会在 onError 中捕获错误,然后访问页面,点击获取按钮,这会发现页面上弹出了一个提示框,提示框中显示了错误信息,说明我们通过 onError 成功捕获了全局错误。
在演示之前我发现我之前在 *asyncUserInfo 中的处理异常的代码写的有问题,我先解释一下这个问题:
所以如上演示全局错误处理的时候,虽然我将 .catch() 中的错误处理代码去掉了,全局 onError 也会捕获到错误:

验证了 effects 中没有处理异常就会进入全局,这个时候我头脑一思考,发现还可以做一个测试,那么就是如果我将 .catch() 中的错误处理代码加上,然后再演示一下,流程是不是就只会走 .catch() 中的错误处理代码,不会走全局 onError 了呢?发现并不是这样就引发出了上面的问题。
修改代码来正确处理这个问题:
* 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 对象,然后传递一个错误信息:
change({history, dispatch}, done) {
console.log('change被执行了');
done(new Error('自定义错误'));
},这样就会在 subscriptions 中通过 done 传递了一个错误,然后在 onError 中捕获,访问页面,刷新一下,我是不是说过订阅的方法只已启动,已 start 就会被执行,所以我刷新页面就会弹出 alert 提示框:

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

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

注释掉了之后回过头来看 onAction,在 dva 中的 onAction 就是过去我们使用的 applyMiddleware,用来配置中间件的。
onAction 的作用:用来注册中间件的,比如说官网里面就有一个很好案例,它呢注册了一个打印日志的中间件,redux-logger,这个中间件是需要安装的,只需要 npm install 安装下即可。
安装 redux-logger:
npm install redux-logger安装完成之后,我们就可以在 dva 中使用 redux-logger 中间件了,如何使用呢?首先导入 redux-logger:
import createLogger from 'redux-logger';
然后在创建 Dva 实例的时候,通过 onAction 配置项来注册 redux-logger 中间件:
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 中使用中间件。
其实这个作用在官方文档中也是有详细的介绍的:

关于剩下的内容:
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 中,这类似的里面去,这个插件呢也比较简单也可以去官方文档中查看。

所以呢其它东西都不常用,比如说如下这些我刚刚所讲解的这些其实都不常用:
initialState,
onError,
onAction,
onStateChange,
onReducer,
onEffect,
onHmr,
extraReducers,
extraEnhancers,常用的是什么,就是 dva,dva 是什么,它就是用来简化 redux,就是用来管理数据的,这个才是 dva 的核心,dva 的核心是什么?核心就是 model,它的核心就是把我们保存的数据,把我们的 reducer,把我们的同步操作,把我们的异步操作,都放到一个地方来进行管理,它的核心就是我们可以给每一个组件都创建一个 model,方便我们去管理数据这个才是它的核心!!!
所以说它的核心在我之前的文章中已经讲了讲解它的基本使用,讲解它的管理数据,异步操作的时候就是它的核心,文章链接呢我就不贴了,直接去翻阅我之前的文章就可以了。
从订阅路由路由的跳转这些内容呢是我们仅仅作为一个扩展,作为一个提高,作为一个进阶的内容,所以呢这些内容呢我就不再继续讲解了,这些内容呢大家可以去查看官方文档,官方文档中有详细的介绍,这些内容呢我就不再继续讲解了。
本文深入探讨了 Dva 的几个重要进阶特性:
虽然 Dva 提供了丰富的配置项(如 onStateChange、onReducer 等),但其核心价值在于通过 Model 机制简化 Redux 的使用,让状态管理变得更加直观和可维护。在实际开发中,应该根据项目需求合理使用这些特性,打造健壮的前端应用。
🐤如果您觉得本文对您有所帮助,欢迎点赞、收藏或分享,您的支持是我创作的最大动力!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。