umi + dva + ant-design-mobile快速搭建H5项目

介绍

最近开发了一个react项目,因为之前都是做原生混合H5开发,对redux用的不怎么熟练,这次想要锻炼下然后花几天看了一下redux和看了几个搭建方案,以及看了下公司其他的H5项目(直接用redux的项目)觉得很复杂繁琐。用过ant-design-pro 2.0正式版(加入了umi的版本)觉得很不错,所以决定从UMI入手搭一个项目来做手机端的H5项目,在去掉大部分业务代码后把这个demo拿来给大家分享一下,希望对新手搭建umi项目有一定价值。

写这篇文章是为了帮助需要的人更好的理解umi来搭建项目,第一次写文章不足之处望指正
本文的例子的demo在我的 github 地址可以下载参考 github地址
觉得对自己理解和上手umi项目有帮助的点个star

创建项目

yarn是facebook推出的,在一些较新的react书籍和资料中也是推荐使用yarn。与npm相比,yarn主要的优势在于:速度快,离线模式,版本控制。

  • 废话太多上代码干,在空文件夹下
yarn create umi

会出现一个选择框,我选择了antd,dll,hard source。这几个配置在umi官网快速上手里面有配置的解释。
确保 node 版本是 8.10 或以上

  • 安装依赖
yarn
  • 启动项目
yarn start
  • 编译打包
umi build

我对umi的一些理解

  1. 在dva 项目通常都是要单独写一个 models,然后所有的models写在里面。
    用了 umi 后,可以在pages同级下写一个models来管理所有的models也可以在每个页面的文件夹下写一个models文件夹来放当前页面需要用的models,好处是结构更加清晰了,删除起来方便不需要去删除好几个地方,且会自动注册 models
  2. 有约定式路由去掉了router.js,umi 会根据 pages 目录下的页面的js自动生成路由配置。可以参考umi官网路由部分
  • 假设 pages 目录结构如下:
+ pages/
  + users/
    - index.js
    - index.less
  - index.js
  • 那么,umi 会自动生成路由配置如下:
[
  { path: '/', component: './pages/index.js' },
  { path: '/users/', component: './pages/users/index.js' },
]
  1. 好用的动态路由(好用在页面间的传值和取值)
  • 比如要实现从列表跳转到订单详情,那一般需要带一个id过去,在详情页拿id获取一些数据,那在umi里面我们怎么做?
+ pages/
  + orderdetail/
    - $id$.js
    - index.less
  • 那在list跳转上面的orderdetail页面的时候我们要这么写(假设val.id=15),
    记得要从import router from 'umi/router'
router.push('/orderdetail/' + val.id)     
  • 在orderdetail页面取这个id(可以自己打印一下this.props看看)
this.props.match.params.id  
  1. 减少了配置文件,umi的 package.json 里会少很多依赖,再比如创建项目的时候选的antd那就包含了
  • antd
  • antd-mobile
  • babel-plugin-import

上demo

运行后的效果

首页
登陆
个人中心
全局layout组件

约定 src/layouts/index.js 为全局路由,返回一个 React 组件,通过 props.children 渲染子组件。

+ layouts/
  + baseLayout/
    - index.js
    - index.less
  - index.js
  • 首先先看下 layouts 下的 index.js
import React, { Component } from 'react'
import BaseLayout from './baseLayout';  // 底部导航的组件

const ULR_NO_LAYOUT = ['/', '/home', '/class', '/my'];  //判断在哪几个路由下需要出现底部导航
class Index extends Component {
  componentDidMount() {
  }
  renderBody = () => {
    const {location: {pathname}, children } = this.props;
    if (ULR_NO_LAYOUT.includes(pathname)) {
      return  (<BaseLayout {...this.props} />);
    }
    return (
      <React.Fragment>
        {children}
      </React.Fragment>
    );
  }

  render() {
    return (
      <React.Fragment>
        {this.renderBody()}
      </React.Fragment>
    )
  }
}

export default Index;
  • ULR_NO_LAYOUT变量是为了判断在哪几个路由下需要出现底部导航
  • BaseLayout是用 antd-mobileTabBar
mock数据

约定 mock 目录里所有的 .js 文件会被解析为 mock 文件。我们新建一个home.js

export default {
  // 支持值为 Object 和 Array
  'GET /api/users': { users: [1, 2] },

  // GET POST 可省略
  '/api/users/1': { id: 1 },

  // 支持自定义函数,API 参考 express@4
  'POST /api/users/create': (req, res) => { res.end('OK'); },
};

直接请求/api/users就能拿到 { users: [1, 2] }

request文件

需要根据自己的项目和后台的接收方法对请求方式进行封装

  • 例如request的49行和56行,是我对自己项目请求接口时增加的token,可以去掉
// body 添加token
  if (newOptions.body) {
    newOptions.body.__token__ = getToken();
  } else {
    newOptions.body = {
      __token__: getToken(),
    };
  }
  • 例如request的86行和89行增加对get传值的转换,setUrlEncoded方法,这样处理方便get方法传值的时候也能和post方法一样穿对象,自动转换带在url后面
} else if (newOptions.method === 'GET') {
    new_url = url + '?' + setUrlEncoded(newOptions.body)
    delete newOptions.body
  }

//setUrlEncoded方法
export const setUrlEncoded = (obj) => {
    let urlEncoded = '';
    if(obj && obj instanceof Object) {
        const keys = Object.keys(obj);
        if(keys && keys.length) {
            keys.forEach((key, index) => {
                urlEncoded += `${key}=${obj[key]}`;
                if(index + 1 < keys.length){
                    urlEncoded += '&';
                }
            });
        }
    }
    return urlEncoded;
}
  • request的110行需要根据自己的项目的返回值来做修改判断是否返回response
获取和验证表单的值
  • 推荐使用 rc-form
import { createForm } from 'rc-form';
@createForm()
  • 调用时 需要从this.props里拿到form里的getFieldProps, getFieldError

render() {
    const {form: {getFieldProps, getFieldError}, regLoading} = this.props;
    <div>
      <InputItem
            {...getFieldProps('name', {
              initialValue: '',
              rules: [{
                  required: true,
                  message: '请输入用户名',
                }],
            })}
            clear
            error={!!getFieldError('name')}
            onErrorClick={() => {
              const err = getFieldError('name').join('、');
              Toast.info(err, 1);
            }}
            placeholder='用户名'
          />
    </div>
}
  • 提交验证form输入内容的时候,例如
  submit(){
    const {form, dispatch} = this.props;
    form.validateFields((error, fieldsValue) => {
      if (error) {
        return;
      }
      dispatch({
        type: 'login/submit',
        payload: {
          // 入参
        },
        callback: (res) => {
          //需要实现什么
        }
      });
    });
  }
models调用和models文件
  • 例如在home的index.js要调用
import { connect } from 'dva';
@connect(({ home }) => ({ home }))
  • home的models文件夹home.js, 使用了subscriptionssubscriptions的好处应该就是可以监测全局的变化, 即使和当前页面不相关model里也可以进行数据改动或者请求接口.当然你也可以选择在页面中使用dispatch
import { reg } from 'services/home';
import router from 'umi/router';
export default {
  namespace: 'home',
  state: {
    'list':{
      'productList': '',
      'bannerList': ''
    }
  },
  effects: {
    *reg({ payload, callback }, { call, put }) {
      const response = yield call(reg, payload);
      yield put({
        type: 'setData',
        payload: response.data
      });
    }
  },
  reducers: {
    setData(state, { payload }) {
      return {
        ...state,
        list: payload,
      }
    }
  },
  subscriptions: {
    setup({ dispatch, history }) {
      return history.listen(({ pathname, search }) => {
        if (pathname == '/home'||pathname == '/') {
          dispatch({
            type: 'reg',
          });
        }
      });
    },
  },
};

Jack程 写于2018年12月16日凌晨

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

推荐阅读更多精彩内容