使用umi+dva做一个demo

最初只是使用react 进行开发项目,发现项目过大状态管理起来就相当困难,虽然有redux, mobx,但是使用起来还是相当繁琐,而目前umi有现成的轮子使用简单,当然愿意尝试了,趁现在假期有时间简单学习记录一下

@[TOC]

一、安装umi

还是原来的套路,要使用先是安装一伙

$ npx @umijs/create-umi-app // if use npm

好了命令执行完成后目录是这样的


app directory

好接下来该开始着手开发了,但还有一件要注意的事情---

Umi官网目前已经是3.x版本了,注意这个版本与2.x有相当大差距,如:plugins 的配置

export default {
- plugins: [
-   ['umi-plugin-react', {
-     dva: {},
-     antd: {},
-     ...
-   }]
- ],
+ dva: {},
+ antd: {},
+ ...
}

其他还有很多,如果刚从2.x升级3.x就要多留意了,否则会浪费不必要的时间,具体参照upgrade-to-umi-3

二、首页

使用antd layout 创建一个首页,这个demo比较简单,所有就直接官网照搬了,部分片段如下

import { Layout, Menu, Badge, Dropdown } from 'antd';
const { Header, Footer, Content } = Layout;
const logo = require('../../public/course/logo.jpeg');

export default function Page(props) {
    return (
        <Layout>
          <Header className={styles.header}>
            <img className={styles.logo} src={logo} alt="" />
            <Menu
              theme="dark"
              mode="horizontal"
              selectedKeys={selectedKeys}
              style={{ lineHeight: '64px', float: 'left' }}
            >
              <Menu.Item key="/">
                <Link to="/">商品</Link>
              </Menu.Item>
              <Menu.Item key="/users">
                <Link to="/users">用户</Link>
              </Menu.Item>
              <Menu.Item key="/about">
                <Link to="/about">关于</Link>
              </Menu.Item>
            </Menu>
          </Header>
          <Content className={styles.content}>
            <div className={styles.box}>{props.children}</div>
          </Content>
          <Footer className={styles.footer}>learning</Footer>
        </Layout>
      );
  }

三、路由配置

一个项目中路由应该是不可缺少的一部分,umi 3.x配置是直接在 .umirc.ts文件中

import { defineConfig } from 'umi';

export default defineConfig({
  nodeModulesTransform: {
    type: 'none',
  },
  antd: {
    compact: true,
  },
  dva: {
    immer: true,
    hmr: false,
  },
  routes: [
    {
      path: '/',
      component: '@/layouts/index',
    }
  ],
  fastRefresh: {},
});

这里开启了antd 和dva 的插件使用,因为后期可能会使用到,好了这是可以yarn start 运行看看有一个简单的主页面了,但是点击一定会报错(路由没有配置完整)

四、鉴权

配置一个页面检测是否登录,当然首要任务是完善上面首页 中对应的菜单路由及页面创建了

app directory

创建目录和对应的文件,再完善路由

import { defineConfig } from 'umi';

export default defineConfig({
  nodeModulesTransform: {
    type: 'none',
  },
  antd: {
    compact: true,
  },
  dva: {
    immer: true,
    hmr: false,
  },
  routes: [
    { path: '/login', component: '@/pages/login' },
    {
      path: '/',
      component: '@/layouts/index',
      routes: [
        { path: '/', component: '@/pages/goods' },
        {
          path: '/about',
          component: '@/pages/about'
        },
        {
          path: '/users',
          component: '@/pages/users/index',
          routes: [
            {
              path: '/users/:oid/:id',
              component: '@/pages/users/$oid/$id',
            },
            { component: '@/pages/NotFound' },
          ],
        },
        { component: '@/pages/NotFound' },
      ],
    },
    { component: '@/pages/NotFound' },
  ],
  fastRefresh: {},
});

好了路由大概是这个样子了,基本完成,

现在我们假如对about 页面做一个鉴权,其实也简单,创建一个wrappers目录,再在下面创建一个auth.tsx, 内容如:

  
import { Redirect } from 'umi'

export default (props:any) => {
// 模拟鉴权
  if (Math.random() > 0.5){
    return <div>{ props.children }</div>;
  } else {
    return <Redirect to="/login" />;
  }
}

再在about 路由增加wrappers, 修改如:

        {
          path: '/about',
          component: '@/pages/about',
          wrappers: ['@/wrappers/auth'],
        },

好了点击的时候就有50%机率到登录页面了

五、mock

目前是一个demo没有后台,所有使用mock模仿异步请求,在mock目录下创建一个login.js,简单的写一个login的接口

export default {
  'post /api/login': (req, res) => {
    const {
      username,
      password
    } = req.body;
    if (username == 'admin' && password == '123') {
      var resObj = res.json({
        code: 0,
        data: {
          token: 'admin123',
          role: 'admin',
          balance: 1000,
          username: 'admin',
        },
      });
      return resObj;
    }

    if (username == 'dex' && password == '123') {
      return res.json({
        code: 0,
        data: {
          token: 'dex123',
          role: 'user',
          balance: 100,
          username: 'dex',
        },
      });
    }
    return res.status(401).json({
      code: -1,
      msg: '密码错误',
    });
  },
};

六、models

目前还需要一个与mock接口进行交付的server,于是在models下创建一个user.js来管理用户登录相关的操作

import reqService from 'axios';
import { history } from 'umi';

const userinfo =
  JSON.parse(localStorage.getItem('userinfo')) |
  {
    token: '',
    role: '',
    username: '',
    blance: '',
  };
// 登录请求
function loginReq(payload) {
  return reqService.post('/api/login', payload).then((res) => {
    return {
      code: res.data.code,
      userinfo: res.data.data,
    };
  });
}

export default {
  namespace: 'user',
  state: userinfo,
  effects: {
    *login({ payload }, { call, put }) {
      try {
        const { code, userinfo } = yield call(loginReq, payload);

        localStorage.setItem('userinfo', JSON.stringify(userinfo));
        // 触发reducers更新状态
        // 犹如import { call, put, takeEvery } from 'redux-saga/effects'中的takeEvery
        yield put({
          type: 'init',
          payload: userinfo,
        });
        history.push('/');
      } catch (error) {
        console.log(error);
      }
    },
  },
  reducers: {
    init(state, action) {
      // 将状态更新到state
      return action.payload;
    },
  },
};

这里使用umi 就比较方便,不需要直接去进行redux-saga的操作,直接使用effects和reduces函数进行数据读取状态更新即可,而这里使用到了es6的generate 生成器函数,直接将异步当成同步来进行操作更加方便了

六、login

有了mock 和models 现在可以完善一下登录了,pages下创建login目录再在其下创建index.js,内容大概是这样子

import React, { Component } from 'react';
import styles from './index.css';
import ProForm, { ProFormText } from '@ant-design/pro-form';
import { UnlockOutlined, UserOutlined } from '@ant-design/icons';
import { connect } from 'dva';
const logo2 = require('../../../public/course/logo2.jpeg');

@connect()
class index extends Component {
  onSubmit = (values) => {
    if (values) {
      this.props.dispatch({ type: 'user/login', payload: values });
    }
  };
  render() {
    return (
      <div className={styles.loginForm}>
        <ProForm
          onFinish={(values) => this.onSubmit(values)}
          submitter={{
            searchConfig: {
              submitText: '登录',
            },
            submitButtonProps: {
              size: 'large',
              style: {
                width: '100%',
              },
            },
            // 完全自定义整个区域
            render: (_, dom) => dom.pop(),
          }}
        >
          <h1
            style={{
              textAlign: 'center',
            }}
          >
            <img className={styles.logo} alt="logo" src={logo2} />
          </h1>
          <div
            style={{
              marginTop: 12,
              textAlign: 'center',
              marginBottom: 40,
            }}
          >
            WE USE ANTD DESIGN TO CREATE A LOGIN PAGE.
          </div>
          <ProFormText
            fieldProps={{
              size: 'large',
              prefix: <UserOutlined />,
            }}
            name="username"
            placeholder="admin"
            rules={[
              {
                required: true,
                message: '请输入用户名!',
              },
            ]}
          />
          <ProFormText.Password
            fieldProps={{
              size: 'large',
              prefix: <UnlockOutlined />,
            }}
            name="password"
            placeholder="123"
            rules={[
              {
                required: true,
                message: '请输入密码!',
              },
            ]}
          />
        </ProForm>
      </div>
    );
  }
}

export default index;

其中使用到了dva进行state 引入,使用dispatch 触发user/login 进行登录模拟操作,缓存用户信息,注意connect中有两个参数,第一个是

state,而state可根据models下面的文件中的namespace 调用不同的操作。第二个参数是一个对象 可以根据namespace映射对应的actions

@connect(
  (state) => ({
    loading: state.loading,
    tags: state.goods.tags,
    courses: state.goods.courses, // 获取goods 下的courses属性的值
    userinfo: state.user, // 获取user 下对应的userinfo的值
  }),
  {
    getList: () => ({
      type: 'goods/getList',
    }),
    addCart: (payload) => ({ 
      type: 'cart/addCart', // 映射cart文件中的addCart
      payload, // 同时将payload 作为方法参数传入
    }),
  },
)

七、test

最后还有个商品列表页面,基本上套路和上面差不多,就不记了,最后测试哈

商品列表

商品过滤

登录
购物车列表

好了这个demo学习完毕,源码也提交到github保存下umi-dva-antd-react-demo

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容