dva 框架简介
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架
D.Va拥有一部强大的机甲,它具有两台全自动的近距离聚变机炮、可以使机甲飞跃敌人或障碍物的推进器、 还有可以抵御来自正面的远程攻击的防御矩阵。
—— 来自 守望先锋
dva 框架特性
- 易学易用,仅有 6 个 api,对 redux 用户尤其友好,配合 umi 使用后更是降低为 0 API
- elm 概念,通过 reducers, effects 和 subscriptions 组织 model
- 插件机制,比如 dva-loading 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading
- 支持 HMR,基于 babel-plugin-dva-hmr 实现 components、routes 和 models 的 HMR
在使用 dva 前,你需要本地安装 dva-cli 相应的脚手架,浏览器自动打开 http://localhost:8000/#/,效果如下图
$ npm install dva-cli -g
$ dva -v
$ dva new dva-demo
? Do you insist on using dva-cli? (y/N) y
$ cd dva-demo
$ npm start
dva 框架结构
|-- config -- 全局配置文件
|-- mock -- mock 本地模拟数据接口
|-- node_modules -- 项目依赖
|-- public -- 项目静态页面
|-- index.html
|-- src
|-- assets -- 静态资源库:字体、图标
|-- components -- 全局组件库
|-- models -- 全局模型库
|-- pages -- 容器组件库
|-- services -- API 文档
|-- utils -- 工具类库
|-- index.css -- 项目静态界面样式
|-- index.js -- 项目静态界面脚本
|-- router.js -- 项目路由文件
|-- .editorconfig
|-- .eslintrc
|-- .gitignore
|-- .roadhogrc.mock.js
|-- .webpackrc
|-- package-lock.json
|-- package.json
|-- README.md
1)页面组件分析 page
页面组件采用文件夹模式,首字母大写,包含:index.css 样式和 index.js 脚本两个文件,index.css 赋予当前组件的样式属性,index.js 赋予当前组件渲染。
// #################### 引入前端框架资源 ####################
import React, { useEffect } from 'react';
import { connect } from 'dva';
// #################### 引入静态资源部分 ####################
import './index.css';
// #################### 定义界面布局排版 ####################
// #################### 定义登录界面组件 ####################
function ExamplePage (props) {
return (
<div className="view-example">
</div>
);
}
// #################### 暴露 Redux 高阶组件 ####################
export default connect(({ example }) => ({
// 这里通过 Redux 返回组件的 state
}))(ExamplePage);
在暴露 Redux 高阶组件可以看出,引入了一个 example 映射的 Redux —— 映射模型对象
2)页面模型分析 model
页面模型需要设置初始状态 state,一般为界面相关控件数据元素字段,通过页面组件 dispatch 调用 effects 相应的接口,根据接口返回值去执行 reducer 达到状态 state 的刷新;有的界面不涉及到接口调用,直接通过 reducer 去刷新状态 state 的值,从而达到界面的数据渲染。
export default {
namespace: 'example', // model 的名字,用来在页面中找到要调用哪一个model,就想id一样
state: {}, // 用来存放数据的地方
// 用来绑定监听页面事件,只有触发监听事件才执行,例:监听路由变化而处理某种业务
subscriptions: {
setup({ dispatch, history }) {
},
},
// 用来启用异步接口调用,得到结果后,put 传入到 reducer 更新 state
effects: {
*fetch({ payload }, { call, put }) { // eslint-disable-line
yield put({ type: 'save' });
},
},
// 用来更新 state 状态,从而达到服务器渲染界面 SSR
reducers: {
save(state, action) {
return { ...state, ...action.payload };
},
},
};
页面模型使用需要在 项目 src/index.js 内,引入模型,并进行模型绑定,如下
import dva from 'dva';
import { createBrowserHistory as createHistory } from 'history'; // 转成浏览器模式,路由不带 #
import ModelExample from './models/example';
// import Router from './router';
import './index.css';
// 1. Initialize
const app = dva({ history: createHistory() });
// 2. Plugins
// app.use({});
// 3. Model
app.model({ namespace: 'example', ...ModelExample });
// 4. Router
app.router(require('./router').default);
// 5. Start
app.start('#root');
3)接口服务分析 service
import request from '../utils/request';
// 暴露界面组件所需要的接口调用:
export async function query() {
return await request('/api/users');
}
4)模板组件分析 components
组件传值通过 props 的方式进行父传子的单向传递,propTypes 用于校验 props 属性值
import React from 'react';
const Example = (props) => {
return (
<div>
Example
</div>
);
};
Example.propTypes = {
};
export default Example;
5)路由管理分析 router.js
路由相对配置都已完成,主要涉及到子路由的配置,按上述说明添加子 Page,然后在 router.js 头部引入子 Page,最后在 Switch 组件内添加即可
import React from 'react';
import { Router, Route, Switch } from 'dva/router';
import IndexPage from './pages/Index';
import LoginPage from './pages/Login';
import NoFoundPage from './pages/NoFound';
import WelcomePage from './pages/Welcome';
function RouterConfig({ history }) {
return (
<Router history={history}>
<Switch>
<Route exact path="/login" component={LoginPage} />
<IndexPage exact>
<Switch>
<Route exact path="/" component={WelcomePage} />
<Route exact path="/welcome" component={WelcomePage} />
<Route exact path="*" component={NoFoundPage} />
</Switch>
</IndexPage>
</Switch>
</Router>
);
}
export default RouterConfig;
6)静态资源引入方式
import './index.css';
import Lazy from '../../assets/img/lazy_load.jpg'; // 引入本地懒加载图片资源,方便处理图片裂图
7)页面生命周期函数
这里说明下,我们的项目所有的组件都是采用 Hooks 方式生成的 Function 函数式组件,想比较类组件每次都要声明 state 状态,使用 Function 函数式组件更好的达到组件的复用性,并且搭配高阶组件(Higher-Order Components)和渲染属性(Render Props)更好的缩减组件属性的迭代传递。
有时候,页面一渲染我们就需要使用 componentDidMount() 生命周期函数来执行接口调用,但是在 Function 函数式组件是没法使用 componentDidMount() 生命周期函数的,而 Hooks 为我们提供了 useEffect() 方法,类似于 componentDidMount(),这里不细讲 Hooks,详细了解请自行网上查询 —–
import React, { useEffect } from 'react';
function ExamplePage (props) {
// 监听 Hook 的生命周期 useEffect 函数,相当于类组件 componentDidMount(),页面加载完成后执行
useEffect(() => { dispatch(); }, []);
}