阿西河

所有教程

公众号
🌙
阿西河前端的公众号

我的收藏

    最近访问  (文章)

      教程列表

      抓包专区
      测试专区

      dva 学习笔记

      dva 是什么

      dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。

      官方资料

      特性

      • 易学易用,仅有 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的核心其实是 saga的封装,将action,reducer等等全部引入到model中。

      过多的废话也就不再阐述了,欲知详情,请看官网,本文的目的就是快速开始,让一个拥有react+redux基础的人可以快速使用dva.js

      深入

      配置环境安装依赖之类的就不多说了,请看官方文档

      dva new dva-quickstart
      

      我们得到初始项目,目录结构如下: 接下里将会逐个目录解释,请注意看注释

      https://a.axihe.com/react/dva/dva-01.png

      入口文件 index.js

      import dva from 'dva';   //引入依赖
      import './index.css';
      
      // 1. Initialize
      const app = dva();       //初始化 dva应用
      
      // 2. Plugins
      // app.use({});          //使用中间件
      
      // 3. Model
      // app.model(require('./models/example').default); // 加载model层 (后面详细解释model)
      
      // 4. Router
      app.router(require('./router').default);           // 引入router 
      
      // 5. Start
      app.start('#root');                                // 挂载dva应用
      
      `基本上就是这样,多余的没什么好说的`
      

      路由匹配 router.js

      import React from 'react';
      import { Router, Route, Switch } from 'dva/router';  // 引入 router,用的就是 react-router
      import IndexPage from './routes/IndexPage';          // 引入路由绑定的高阶组件
       
      
      // 按照从上到下的顺序开始匹配url规则,匹配到了就是展示对应的组件view
      function RouterConfig({ history }) {                 
        return (
          <Router history={history}>
            <Switch>
              <Route path="/" exact component={IndexPage} />
            </Switch>
          </Router>
        );
      }
      
      export default RouterConfig;
      

      路由页面 routes/IndexPage.js

      在routes目录下,是路由页面,由多个高阶组件渲染而成,当然,刚初始化的项目自然没有写高阶组件,在后面的实战操作中,我们将以 路由页面 =&gt; 高阶组件 =&gt; 基础组件 路由绑定model层,高阶组件绑定路由的action事件,基础组件绑定原生事件,在路由中触发action更新数据流 的逻辑 完成一个简单标准的dva过程

      import React from 'react';
      import { connect } from 'dva';
      import styles from './IndexPage.css';
      
      // 在这个方法中,我们返回一个dom结构
      // 并且 在圆括号中 可以接受一个大对象(包含很多东西),也可以解构 只取其中的state和dispatch,具体可以在后面看
      function IndexPage() {
        return (
          <div className={styles.normal}>
            <h1 className={styles.title}>Yay! Welcome to dva!</h1>
            <div className={styles.welcome} />
            <ul className={styles.list}>
              <li>To get started, edit <code>src/index.js</code> and save to reload.</li>
              <li><a href="https://github.com/dvajs/dva-docs/blob/master/v1/en-us/getting-started.md">Getting Started</a></li>
            </ul>
          </div>
        );
      }
      
      IndexPage.propTypes = {
      };
      
      // 这里 connect方法就是redux的connect,后面的IndexPage表示绑定的高阶组件
      // 在connect的第一个括号中,是可以拿到所有的model对象,这样就可以把对应的model对象绑定到我们的高阶组件上
      export default connect()(IndexPage);  
      

      看了上面的注释很蒙也没关系,因为纸上谈兵,甚至,我兵都没有出来,你只需要知道,connect的作用及过程就好了

      数据模型 model/emample.js

      export default {
      
        namespace: 'example',         // 命名空间 作为 connect方法 中获取model对象state的 id
      
        state: {},                    // 初始化state 
      
        subscriptions: {              // 订阅
          setup({ dispatch, history }) {  // eslint-disable-line 
          },
        },
      
        effects: {                     // 异步action的handler
          *fetch({ payload }, { call, put }) {  // eslint-disable-line
            yield put({ type: 'save' });
          },
        },
      
        reducers: {                    //react-redux的reducers 用来接收action并且处理数据更新
          save(state, action) {
            return { ...state, ...action.payload };
          },
        },
      
      };
      

      当我们在高阶组件中通过connect绑定了高阶组件和model,并且在index.js中引入这个model,就可以使用标准流程: 在subscriptions方法中订阅路由变化,当路由与高阶组件相对应,调用effects请求数据,拿到数据reducer更新数据

      基础组件 components/Example.js

      代码就不贴了,大家应该都知道这里面做什么

      公共服务 services/example.js

      这里封装了一些公共使用的方法

      浅出

      pp

      项目地址:https://github.com/zhaowanhua…

      接下来,我们将quick-start项目改造成一个按照dva标准流程的小项目(如上图),帮助大家理解和使用 首先我们把上面那些文件夹下面的文件全部删干净

      修改路由模式 index.js

      import dva from 'dva';
      import './index.css';
      import createHistory from 'history/createBrowserHistory';
      
      // 这个方法里面 可以配置router的 路由模式,比如hash或者H5 histroy,
      // 具体区别可以参考我的文章 vue-router,单页应用原理一致的
      const app = dva({
          history: createHistory()
      });
      
      // 2. Plugins
      // app.use({});
      
      // 3. Model
      app.model(require('./models/List').default); // 引入model
      
      // 4. Router
      app.router(require('./router').default);
      
      // 5. Start
      app.start('#root');
      

      创建基础组件 components/Item.js

      import React from 'react';
      
      const Item = ({
          num,
          id,
          OnDelete
      }) => {
          return (
              <li onClick={() => OnDelete(id)}>
            {num}
          </li>
          );
      };
      
      
      Item.propTypes = {};
      
      export default Item;
      

      创建数据模型 models/List.js

      export default {
      
        namespace: 'list', // 这个namespace 是model的唯一识别id,在connect中需要使用这个绑定
      
        state: {},
      
        subscriptions: {
          setup({
            dispatch,
            history
          }) { // eslint-disable-line
            return history.listen(({
              pathname
            }) => {
              if (pathname === '/') {
                dispatch({
                  type: 'fetch',
                  payload: {}
                });
              }
            });
          },
        },
      
        effects: {
          * fetch({
            payload
          }, {
            call,
            put
          }) { // eslint-disable-line
            // 这里假装 获取到了服务器的数据
            const fetchData = [0, 1, 2, 3]
            yield put({
              type: 'save',
              list: fetchData
            });
          },
        },
      
        reducers: {
          // 保存
          save(state, action) {
            return {...state,
              list: action.list
            };
          },
          // 新增
          add(state, action) {
            const [..._arr] = {...state
            }.list;
            _arr.push(_arr.length)
            return {
              ...state,
              list: _arr
            }
          },
          // 删除
          del(state, action) {
            return {
              ...state,
              list: state.list.filter((item, index) => {
                return index !== action.id
              })
            }
          },
        },
      
      };
      

      写好model 是要在index.js中引入的,不然没有效果

      创建高阶组件 components/List.js

      export default {
      
        namespace: 'list', // 这个namespace 是model的唯一识别id,在connect中需要使用这个绑定
      
        state: {},
      
        subscriptions: {
          setup({
            dispatch,
            history
          }) { // eslint-disable-line
            return history.listen(({
              pathname
            }) => {
              if (pathname === '/') {
                dispatch({
                  type: 'fetch',
                  payload: {}
                });
              }
            });
          },
        },
      
        effects: {
          * fetch({
            payload
          }, {
            call,
            put
          }) { // eslint-disable-line
            // 这里假装 获取到了服务器的数据
            const fetchData = [0, 1, 2, 3]
            yield put({
              type: 'save',
              list: fetchData
            });
          },
        },
      
        reducers: {
          // 保存
          save(state, action) {
            return {...state,
              list: action.list
            };
          },
          // 新增
          add(state, action) {
            const [..._arr] = {...state
            }.list;
            _arr.push(_arr.length)
            return {
              ...state,
              list: _arr
            }
          },
          // 删除
          del(state, action) {
            return {
              ...state,
              list: state.list.filter((item, index) => {
                return index !== action.id
              })
            }
          },
        },
      
      };
      

      创建路由页面 routes/IndexPage.js

      import React from 'react';
      import {
        connect
      } from 'dva';
      import List from '../components/List'
      
      //我们在路由页面里面渲染高阶组件,写好action,通过prop传递给基础组件
      // 这里引入的list 对应 model中的namespace
      function IndexPage({
        dispatch,
        list
      }) {
        function handleAdd() {
          dispatch({
            type: 'list/add'
          });
        }
      
        function handleDelete(id) {
          dispatch({
            type: 'list/del',
            id: id,
          });
        }
        return (
          <div>
            <List list={list} OnAdd={handleAdd} OnDelete={handleDelete}></List>
          </div>
        );
      }
      
      
      IndexPage.propTypes = {};
      
      // 通过connect方法绑定路由页面和model,你可以把connect方法的第一个参数(方法里的) 打印出来看看都有什么东西,不要让解构扰乱了你的眼睛,connect((obj)=>{console.log(obj)})()
      export default connect(({
        list
      }) => {
        return list; // 这里是state中的list,通过connect,在每次数据更新的时候,流向我们的view,更新视图,你可以在这里"打桩",看看具体的数据流动
      })(IndexPage);
      

      参考

      卖前端学习教程

      只需几十元,就能买到培训班的内部教程!开启高薪之路!

      零基础小白阿里P7的教程都有!

      同时长期收购所有培训班的前端教程

      目录
      目录