React系列文章之DVA

前言

提示:本文章来自珠峰培训周啸天老师的视频讲解总结

视频地址:114.关于dva-cli和roadhog的使用_哔哩哔哩_bilibili

dva:https://github.com/dvajs/dva/tree/master/docs/api 「官网好像打不开」

antd: 全局化配置 ConfigProvider - Ant Design

redux-saga: 高级 · Redux-Saga


一、dva脚手架

 1、创建dva项目

           全局安装dva命令:npm install dva-cli -g     

            查看已安装dva命令:dva -v

           创建dva项目命令:dva new project-name

2、修改运行的host,端口号

        创建.env文件,代码如下

HOST=127.0.0.1
PORT=3004

        或者在package.json中修改,要先安装cross-env,然后在scripts中进行配置例如:

"start": "cross-env HOST=127.0.0.1 PORT=3000 roadhog server",
//安装包版本
"cross-env": "7.0.3",

3、dva配置

        默认有.webpackrc「默认JSON格式,也可以改成.webpackrc.js」文件,我们在该文件下配置

代码如下

{
  "hash": true,
  "html": {
    "template": "./public/index.ejs"
  },
  "disableCSSModules": true,
  "disableCSSSourceMap": true,
  "extraBabelPlugins": [
    ["import", {
      "libraryName": "antd",
      "libraryDirectory": "es",
      "style": true
    }]
  ],
  "proxy": {
    "/operating/api": {
      "target": "https://",
      "changeOrigin": true,
      "pathRewrite": { "^": "" }
    }
  },
  "env": {
    "development": {
      "extraBabelPlugins": ["dva-hmr"],
      "browserslist": [
        "last 1 chrome version",
        "last 1 firefox version",
        "last 1 safari version"
      ]
    },
    "production": {
      "browserslist": [
        ">0.2%",
        "not dead",
        "not op_mini all"
      ]
    }
  }
}

修改网页图标在public目录下添加logo.png在html文件中加入如下代码

<link rel="shortcut icon" href="./logo.png" type="image/x-icon"/>

默认加了一个注释文件防止后期忘记

### 安装包
{
  "prop-types": "^15.8.1",
  "qs": "6.6.0",
  <!-- 在jsx中直接写less代码 -->
  "styled-components": "5.3.6",
  "antd": "4.24.7",
  "antd-icons": "0.1.0-alpha.1",
  <!-- antd按需加载 -->
  "babel-plugin-import": "1.13.5",
  <!-- less预编译 -->
  "less": "4.1.3",
  "less-loader": "8.1.1"
  <!--REM响应式布局处理,移动端需要  -->
  "lib-flexible": "0.3.2",
  "postcss-pxtorem": "5.1.1",
  "babel-plugin-styled-components-px2rem": "1.5.5",
  <!--  -->
  "history": "4.10.1",
  "cross-env": "7.0.3",
  <!-- es6处理 -->
  "@babel/polyfill": "7.12.1",
  <!-- 修改主机名与端口号 -->
  "start": "cross-env HOST=127.0.0.1 PORT=3000 roadhog server",
}
<!--.webpackrc文件配置  -->
{
  <!-- 对css的处理 -->
  "disableCSSModules": true,
  "disableCSSSourceMap": true,
  <!-- 对postcss-loader的插件扩展 -->
  "extraPostCSSPlugins": [
    <!-- 插件配置有问题 -->
    "postcss-pxtorem", { "rootValue": 75, "propList": ["*"] }
  ],
  <!-- 对babel扩展应用的插件 -->
  "extraBabelPlugins": [
    ["import", {
      <!-- antd按需加载 -->
      "libraryName": "antd",
      "libraryDirectory": "es",
      "style": true
    }],
    <!-- styled-components中的px2rem -->
    ["styled-components-px2rem", { "rootValue": 75 }]
  ],
  <!-- 开发环境的跨域代理 -->
  "proxy": {
    "/operating/api": {
      "target": "https://dev-operating.hewomall.com",
      "changeOrigin": true,
      "pathRewrite": { "^": "" }
    }
  },
  <!-- 配置浏览器兼容列表「区分环境」 -->
  "env": {
    "development": {
      <!-- 开启热更新 -->
      "extraBabelPlugins": ["dva-hmr"],
      "browserslist": [
        "last 1 chrome version",
        "last 1 firefox version",
        "last 1 safari version"
      ]
    },
    "production": {
      "browserslist": [
        ">0.2%",
        "not dead",
        "not op_mini all"
      ]
    }
  }
}

4、目录结构

5、程序入口

src/index.js文件

import dva from 'dva';
/**
默认情况下roadhog脚手架已经完成了对ES6常规语法「不含装饰器等非规范语法,这些语法
还需要我们单独安装babel插件处理」,以及css3的语法兼容处理
  + es6语法:babel-loader/babel-preset-react-app
  + css语法:postcss-loader/autoprefixer
  --->设置我们的浏览器兼容列表
 */
import '@babel/polyfill';
import { createHashHistory } from 'history';
import voteModel from './models/vote';    //此model打包到主js中
import demoModel from './models/demo';
import createLoading from 'dva-loading';
// import { createLogger } from 'redux-logger';

// 1. Initialize
const app = dva({
  // 指定路由的模式,默认是hash路由: createHashHistory/createBrowserHistory
  history: createHashHistory(),
  // 扩展其他的中间件,例如:redux-logger/redux-persist...
  extraEnhancers: [],
  // 给Model板块赋初始值,它的优先级要高于每个板块中设置的初始状态值
  // 一定要保证操作的这个板块不是懒加载的
  initialState: { 
    // vote: {
    //   oppNum: 100,
    //   supNum: 50
    // },
    // demo: {
    //   num: 100,
    // }
  },
  // 每一次派发进行拦截
  // onAction: [createLogger()]
});

// 2. Plugins
//一般使用了dva-loading,它默认在redux公共状态中就会加一个loading状态
app.use(createLoading());   //它的值是一个对象{global: false,models: {...}, effects: {...}}

// 3. Model
app.model(voteModel);   // 一加载页面,我们就基于app.model注册相关的model使用
app.model(demoModel);

// 4. Router
app.router(require('./router').default);

// 5. Start 在这之前一定要先处理router
app.start('#root');

二、路由

1、路由文件routes「创建项目默认会有」

  dva/router

        + react-router-dom 中提供的所有api「v5-」

        + react-router-redux 「routerRedux」中的方法:把路由和redux结合在一起的一个模块,

可以允许我们在redux操作中,去实现路由的跳转以及传参...

路由模式

        hash路由模式:地址上带# 

        history路由模式:浏览器路由模式,不带#

2、路由的配置

import { Router, Route, Switch, Redirect } from "dva/router";
基于<HashRouter>把所有要渲染的内容包起来,开启HASH路由

        后续用到的<Link>、<Route>等,都需要在HashRouter中使用

         开启后,整个页面地址默认会设置一个 #/ 哈希值

NavLink/Link实现路由切换/跳转的组件

         它可以根据路由模式,自动设置点击A的切换方式

        最后渲染完毕后的结果依然是A标签

Switch:

        确保路由中,只要有一项匹配,则不再继续向下匹配

exact:

        设置匹配模式为精准匹配

Redirect:

        from:从哪个地址来

        to:重定向的地址

        exact是对from地址的修饰,开启精准匹配

路径地址匹配的规则:

页面地址:浏览器URL后面的哈希值

路由地址:Route中path字段指定的地址

代码如下

function RouterConfig({ history, app }) {

   /**
    history: 包含路由跳转方法的history对象,
    app: 基于dva创建的应用
   */
  return (
    <ConfigProvider locale={zhCN}>
      <Router history={history}>
        <Switch>
          <Route path="/" exact component={Vote} />
          <Route path="/demo" component={LazyDemo} />
          <Route path="/personal" component={LazyPersonal} />
          <Redirect path="*" to="/" />
        </Switch>
      </Router>
    </ConfigProvider>
  );
}

3、路由跳转

 NavLink、Link跳转
<NavLink to="/personal/order">我的订单</NavLink>
<Link to={{
    pathname: '/personal/profile',
    search: '?id=100&name=zhufeng'
}}>按钮</Link>
编程试导航路由跳转
1、history
1)、正常跳转

history.push('/personal/profile');

2)、问号传参
history.push({
  pathname: '/personal/profile',
  search: '?id=100&name=zhufeng'
});
3)、路径传参
// 路径传参:把传递的信息当做路由地址的一部分,但是需要路由地址基于":?"设置匹配规则
history.push('/personal/profile/1001/zs');
4)、隐式传参
// 隐式传参:基于state把信息传递给目标组件,但是传递的信息没有在地址栏中,刷新页面
// 传递的信息就会消失了
history.push({
  pathname: '/personal/profile',
  state: { id: `1001x`, name: 'zs'}
});
2、routerRedux
/**
routerRedux是react-router-redux中提供的对象,此对象中包含了路由跳转的方法
  + go/goBack/goForward
  + push/replace
相比较于props.history对象来讲,routerRedux不仅可以在组件中实现路由跳转,还可以在
redux操作中实现路由跳转,它本身就是redux与router的结合操作
  + 组件中「redux外部」 dispatch(routerRedux.push(...))
  + 在redux内部 yield put(routerRedux.push(...))
  + 注意:一定要基于dispatch进行派发才会跳转,因为执行routerRedux.xxx(...)返回的是
  一个action对象,
  {
    payload: {method: 'push', args: Array(1)},
    type: "@@router/CALL_HISTORY_METHOD"
  }
 */
 1)、正常跳转
dispatch(routerRedux.push('/personal/profile'));
2)、问号传参
dispatch(routerRedux.push({
  pathname: '/personal/profile',
  search: '?id=100&name=zhufeng'
}));
3)、路径传参
// 路径传参:把传递的信息当做路由地址的一部分,但是需要路由地址基于":?"设置匹配规则
dispatch(routerRedux.push('/personal/profile/1001/xiaohu'));
4)、隐式传参
// 隐式传参:基于state把信息传递给目标组件,但是传递的信息没有在地址栏中,刷新页面
// 传递的信息就会消失了
dispatch(routerRedux.push({
   pathname: '/personal/profile',
   state: {
      id: `1001x`, name: 'zs'
   }
}));
4、接收路由跳转传的参数

根据组件中的props对象中的location、match、history这三个属性接收、假设这个组件并不是基于路由匹配渲染的,那么默认情况下,他的属性中是没有history、match、location这三个对象,此时我们可以基于withRouter高阶组件,对其处理,让其具备这三个对象属性

/**
  location对象:
    + pathname: 当前的路由地址
    + search: 获取问号传参的信息 -> "?xxx=xxx"
    + state: 获取隐式传参的信息
  match对象
    + params: 存储基于路径参数传递过来的信息「对象」
  
   */
import { withRouter } from 'dva/router';
export default withRouter(MyProfile);
5、路由懒加载
import dynamic from 'dva/dynamic';
const LazyDemo = dynamic({
  app: window.app,
  models: () => [import(/* webpackChunkName: 'demo' */'./models/demo')],
  component: () => import(/* webpackChunkName: 'demo' */"./routes/Demo")
});
// /**路由懒加载 */
// const LazyOrder = dynamic({
//   // app: window.app,
//   models: () => [],
//   component: () => import(/*webpackChunkName: 'MyOrder'*/'./personal/MyOrder'),
// });

三、Modal数据模型

models下对redux-saga的封装,Modal数据模型,包含了每个状态的管理,models模板代码如下

/**dva第三部分核心:Modal数据模型,包含了每个状态的管理 */
export default {
  /**模块名 */
  namespace: 'personal',
  /**公共状态 */
  state: {
    info: null,
  },
  /**基于saga的语法,异步修改公共状态 */
  effects: {
    
  },
  /**同步修改公共状态 */
  reducers: {
    
  },

};

使用models要在index.js文件中注册

models具体使用

const delay = function delay(interval = 1000){
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(`${interval}@@saga`);
    }, interval);
  });
}

/**dva第三部分核心:Modal数据模型,包含了每个状态的管理 */
export default {
  /**模块名 */
  namespace: 'demo',
  /**公共状态 */
  state: {
    num: 10,
  },
  /**同步修改公共状态 */
  reducers: {
    increment(state, { payload = 1}) {
      return {
        ...state,
        num: state.num + payload
      }
    }
  },

  /**基于saga的语法,异步修改公共状态 */
  effects: {
    /**
      redux-saga中,我们基于take/takeLatest/takeEvery等方式创建监听器,此时写成一个个的
      Generator函数即可--> 默认是基于takeEvery的方式创建的监听器
        + 方法名就是我们创建监听器的名字
        + 方法就是派发的任务被监听后,执行的working方法
        + 此处的函数名不要和reducers中的函数名一致,因为每一次派发reducers和effects中的方法
        都去匹配执行!!如果函数名一致,则状态修改两次,所以我们一般在effects中写的名字都加Async

      方法中的参数
        + action:在组件中进行任务派发时,传递的action对象
        + 第二个参数就是redux-saga中提供的EffectsAPI,但是没有delay/debounce...
          + 基于yield select() 可以获取所有模块的公共状态

     */
    // *incrementAsync(action, saga) {
    //   // console.log(action);//{type: 'demo/incrementAsync', payload: 10, __dva_resolve: ƒ, __dva_reject: ƒ}
    //   // console.log(saga);//{__esModule: true, take: ƒ, takem: ƒ, put: ƒ, all: ƒ, …}
    //   // let state = yield select();   //获取所有模块的公共状态
    //   // console.log(state); // {routing: {…}, @@dva: 0, vote: {…}, demo: {…}}
      
    // },
    // 如果想设置不同的监听器,可以这样写
    incrementAsync:[
      // 数组第一项是working函数
      function* ({ payload }, { call, put }) {
        yield call(delay, 2000);
        yield put({
          type: 'increment',
          payload
        })
      },
      // 数组第二项是指定监听器的类型
      { type: 'takeLatest'}
    ]
  },
  /**
    在这个订阅的方法,会在页面一加载的时候就会被通知执行,所以:
      + 我们可以把页面一加载需要处理的事情「和redux相关的」在这里处理
      + 在这里也可以基于history.listen做事件监听,保证进入哪个组件再处理也可以
    demoModel是被懒加载的,只有访问了/demo这个地址(组件),demoModel才会被注册
      + 只有进入到组件,Model懒加载完毕,也被注册了,subscriptions中订阅的方法才会被执行
      + 而且只会执行一次,后期路由来回切换的时候,也不再执行了
  */
  subscriptions: {
    setup() {
      console.log('DEMO-SETUP');
    }
  },
};
// const saga = function* saga() {
//   yield takeEvery('incrementAsync', working);
// }

组件中使用

import React, { useState } from "react";
import styled from "styled-components";
import { Button } from 'antd';
import { connect } from 'dva';

const DemoBox = styled.div`
  margin: 32px auto;
  padding: 16px;
  border: 1px solid #ccc;
  width: 300px;
  span {
    display: block;
    margin-bottom: 16px;
  }
`;
const Demo = function Demo(props) {
  let { num = 0, dispatch, loading } = props;
  // 让loading的状态值和指定的模块的effects方法进行关联,每一次派发这个任务之前
  // loading = true;派发任务结束后,loading = false
  loading = loading.effects['demo/incrementAsync'];
  // let [loading, setLoading] = useState(false);
  return <DemoBox>
    <span>{num}</span>
    <Button type='primary' style={{marginRight: 16}} onClick={() => {
      // 这样派发是通知reducers/effects中的方法执行,
      // reducers同步修改状态,effects是异步修改状态
      dispatch({  
        type: 'demo/increment',
        payload: 20
      });
    }}>累加器同步</Button>
    <Button type='primary' danger onClick={async () => {
      /**
        前提:异步派发返回的结果是Promise实例,派发成功后才改变实例状态为成功
        派发失败后,实例状态是失败
          + dva中处理这样的事情,可以基于此方案处理按钮的Loading状态,实现防抖
          + 但是原生的redux-saga中,每一次异步派发并不返回Promise实例,这样的
          操作就失效了
      
       */
      // setLoading(true);
      await dispatch({
        type: 'demo/incrementAsync',
        payload: 10
      });
      // setLoading(false);
    }} loading={loading}>累加器异步</Button>
  </DemoBox>
}
export default connect(state => {
  return {
    ...state.demo,
    loading: state.loading
  }
})(Demo);

redux-saga的使用可以参考React系列之Redux-saga文章「等有时间写」


总结

dva的基本使用,包含了dva脚手架、dva/router路由、models数据模型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值