基于umi的React项目结构介绍

本项目是基于umi搭建的,具体可以参考 Ant Design 实战教程(beta 版)

项目搭建

以下命令顺序执行,用来完成umi的初始化

mkdir demo2
cd demo2

npm init -y

npm i antd --save

npm i umi --save-dev

然后,我们开始建项目的文件目录

  - demo2
    - dist // 存放编译后的文件

    - config
      - config.js  // umi的配置文件
    - mock
      - index.js   // 前端模拟请求数据

    - src  // 应用的所有代码
      - actions     // 处理异步请求
      - assets      // 静态资源
      - components  // 公用组件
      - pages       // 业务逻辑页面
      - reducers    // reducer 状态处理
      - util        // 公用方法
      - index.html  // 项目模板
      - index.js    // 项目入口
    - package.json      // npm init 自动生成
// src/pages/index.js
import React, { Component } from 'react'

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {};
    }
    render() {
        return (
            <div className="app-container">
                <p>hello world</p>
            </div>
        );
    }
}

export default App;

修改package.json

...
  "scripts": {
+   "dev": "umi dev",
+   "build": "umi build"
  }
...

我们运行 npm run dev 就能看到hello world.

现在我们使用的都是umi的默认配置,我们还需要自己的配置

修改配置文件

添加 umi-plugin-react 插件

npm i umi-plugin-react --save-dev

配置 config/config.js 文件

export default {
  plugins: [
    ['umi-plugin-react', {
      antd:true, //使用antd
      dva:false  //不使用dva,我们直接用redux
    }],
  ],
  // 使用自定义的router,如果不使用则是用umi的约定式路由(按文件夹路径来)
  // 这里的path都是相对于pages文件,例如:
  //  routes:[{
  //   path:'/box', // 指定路由地址(url)
  //   component:'Box/index' //引用的组件路径
  // }]
  // 引用的就是pages文件夹下 Box/index.js文件
  routes:[{
    path:'/',
    component:'index'
  }]
}

使用Routes配置

首先我们使用antd的Layout布局,修改src/pages/index.js

import React, { Component } from 'react';
import { Layout } from 'antd';

const { Header, Content, Footer } = Layout;

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {};
    }
    render() {
        return (
            <div className="app-container">
                <Header>header</Header>
                <Content>content</Content>
                <Footer>footer</Footer>
            </div>
        );
    }
}

export default App;

重新运行 npm run dev,页面就会显示出当前的界面

在pages下新建home.js文件

import React, { Component } from 'react';

class Home extends Component {
    constructor(props) {
        super(props);
        this.state = {};
    }
    render() {
        return (
            <div>
                Home 页面
            </div>
        );
    }
}

export default Home;

修改文件

 // src/pages/index.js
 ...
 <Content>content</Content>

 ==> <Content>{this.props.children}</Content>
 ...


 // config/config.js

...
  routes: [{
        path: '/',
        component: 'index',
      +  routes: [{
      +      path: '/',
      +      component: './home'
      +  }]
    }]
...

保存后页面就会发生变化,Content中就会显示 Home的内容

在umi中使用scss

在umi中可以直接使用css,但是并不支持scss,我们需要加两个loader,
直接npm安装 node-sass和sass-loader 即可,剩余的事情umi已经帮我们做好了。

npm i --save-dev node-sass sass-loader

在src/assets下新建文件夹

- assets
 +  - img
 +  - css
  +   -style.scss  // 这个样式文件一般来说存放全局的样式

在src/pages/index.js 引用style.scss

import '../assets/css/style.scss';

在home.js同级新建home.scss 文件

.home-container{
    .red{
        color:red;
    }
}

在home.js引用,并修改render


// 第一种使用scss方法
// 使用这种方法的时候样式名称不能用 "-" ,不然在使用的时候会报错
...
import homeStyle from './home.scss';
...

...
  render() {
        return (
            <div className={homeStyle.home_container}>
                <p className={homeStyle.red}>Home 页面</p>
            </div>
        );
    }
...


//  第二种使用scss方法

...
import './home.scss';
...

...
  render() {
        return (
            <div className="home_container">
                <p className="red">Home 页面</p>
            </div>
        );
    }
...


用第二种方法的情况:

刷新页面发现并没有变化,打开浏览器调试窗口,查看sources

找到引用的css文件,搜索可以看到好像我们的样式确实是存在的,只不过被加上了其他的后缀(为了保证不会出现全局污染)

ps:这个问题当时我找了好久

1.png

这个是umi自己默认加上,我们并不想要这个东西,在config/config.js文件中添加配置


...
cssLoaderOptions:{
  localIdentName:'[local]'
}
...


完善routes

现在我们的项目只有一个home页面,我们多加几个,来实现跳转的功能

简单页面跳转

在home.js同级添加文件 center.js

import React, { Component } from 'react';

class Center extends Component {
    constructor(props) {
        super(props);
        this.state = {};
    }
  
    render() {
        return (
            <div className="center-container">
                <p>这里是个人中心页面</p>
            </div>
        );
    }
}

export default Center;

修改home.js


...
import Link from 'umi/link';
...



...
 render() {
        return (
            <div className="home-container">
                <p className="red">Home 页面</p>
                <Link to="/center">个人中心</Link>
            </div>
        );
    }
...

修改config.js

 routes: [{
        path: '/',
        component: 'index',
        routes: [{
            path: '/',
            component: './home'
        }, {
    +        path: '/center',
    +        component: './center'
        }]
    }]

点击页面的"个人中心",即可跳转到个人中心页面

事件跳转

除了点击Link跳转页面外,我们还有其他的跳转需求,比如:返回上一个页面,或者登录后跳转。这些都可以算是事件跳转,

修改center.js


...
import { Button } from 'antd';
import router from 'umi/router';
...

...
 onGoBackHome = () => {
    router.goBack()
    // router.push('/')
 }
 render() {
     return (
         <div className="center-container">
             <p>这里是个人中心页面</p>
             <Button onClick={this.onGoBackHome}>返回主页</Button>
         </div>
     );
 }
...

在项目中使用redux

在umi中,redux是封装在dva中的,但是我们想用原始的那种redux (仅仅是个人原因),我们就不去使用dva的模式。

在项目中actions文件主要用于处理请求、异步等,reducers文件则是处理数据以及其他的改变

创建store

在reducers目录下新建文件

因为在umi中会自动导入redux和react-redux包,所以我们不需要在安装,可以直接使用

// src/reducers/home.js

//初始化一个homeStore
const initHomeStore = {
    isShowDesc: true
}

const homeStore = (state = initHomeStore, action) => {
    switch (action.type) {
        case 'home-show-desc':
            return {
                ...state,
                isShowDesc: action.value
            }
        default:
            return state;
    }
}

export default homeStore;

// src/reducers/index.js

// 可以把每一个页面/模块都写一个store文件,然后在index中合并成一个store
import { combineReducers } from 'redux';

import homeStore from './home';

// 合并store
const appStore = combineReducers({
    homeStore,
});

export default appStore;

使用store

store文件创建好了之后,修改pages/index.js

//导入文件
...
import { createStore } from 'redux';
import { Provider } from 'react-redux';

import appStore from '../reducers';

const store = createStore(appStore);
...


//修改render内容
 render() {
        return (
        +    <Provider store={store}>

                <div className="app-container">
                    <Header>header</Header>
                    <Content>{this.props.children}</Content>
                    <Footer>footer</Footer>
                </div>
        +    </Provider>
        );
    }

这样我们可以在所有的页面都使用store内容

修改 pages/home.js

//导入
...
import { connect } from 'react-redux';
...

class Home extends Component {
  ...

    render() {
        const { isShowDesc } = this.props;
        return (
            <div className="home-container">
                <p className="red">Home 页面</p>
                <Link to="/center">个人中心</Link>
                {
                    isShowDesc ?
                        <p>这里是详情信息</p> : ''
                }
            </div>
        );
    }
}

// mapStateToProps 方法传的state是全局的store,我们只需要homeStore,返回homeStore


const mapStateToProps = state => {
    console.log('state:', state);
    return state.homeStore;
}

export default connect(mapStateToProps)(Home);

重新启动 npm run dev

此时页面上看不到详情内容,
手动修改一下reducers/home.js 的isShowDesc值为true,保存之后在页面上就能看到详情内容
手动修改只是测试一下

下面我们来利用dispatch修改isShowDesc的值(这里不使用更简单的组件state属性来处理)

给home页面添加一个button,点击button来显示/隐藏详情


...
import { Button } from 'antd';
...

class Home extends Component {
  ...
    onButtonClick = () => {
        const { onToggerDesc, isShowDesc } = this.props;
        onToggerDesc(!isShowDesc);
    }
    render() {
        const { isShowDesc } = this.props;
        return (
            <div className="home-container">
                ...
                <Button onClick={this.onButtonClick}>显示/隐藏</Button>
                {
                    isShowDesc ?
                        <p>这里是详细信息</p> : ''
                }
            </div>
        );
    }
}

const mapStateToProps ...

const mapDispatchToProps = dispatch => ({
    onToggerDesc: (value) => {
        dispatch({ type: 'home-show-desc', value })
    }
});

export default connect(mapStateToProps, mapDispatchToProps)(Home);

这样我们就能够点击按钮来改变详情的状态

网络请求

网络请求使用fetch,使用mock模拟数据

fetch的使用

安装 fetch、mockjs

npm i --save whatwg-fetch mockjs

封装一下请求方法,并使用mockjs模拟请求

// src/util/index.js

import 'whatwg-fetch';

export const get = (url, params) => {
    let newUlr = url;
    let count = 0;
    if (params) {
        newUlr += '?';
        for (key in params) {
            let value = params[key];
            if (typeof value === 'object') {
                value = JSON.stringify(value);
            }
            count++;
            newUlr += ((count > 1 ? '&' : '') + key + '=' + value);
        }
    }
    return fetch(newUlr);
}

// src/actions/index.js
import { get } from '../util';

export const getList = ({ dispatch, params }) => {
    get('/getlist')
        .then((response) => {
            return response.json()
        }).then((value) => {
            dispatch({ type: 'home-get-list', value: value.list })
        }).catch((ex) => {
            ///失败
        });
}

// mock/index.js
import mockjs from 'mockjs';

export default {
    "/getlist": mockjs.mock({
        "list|10": [{
            city: "@city",
            name: "@cname",
            "sex|0-1": 1,
            email: "@email",
            "id|1-10": 5
        }]
    })
}

在home.js中使用


...
import { Button, Table } from 'antd';
import { getList } from '../actions';
...

class Home extends Component {
    ...
    componentDidMount() {
        this.props.onGetList();
    }
    ...

    render() {
        const { isShowDesc, listData } = this.props;

        const columns = [{
            title: '姓名',
            dataIndex: 'name'
        }, {
            title: '城市',
            dataIndex: 'city'
        }, {
            title: '性别',
            dataIndex: 'sex',
            render: (text, record) => (text === 0 ? '女' : '男')
        }, {
            title: '邮箱',
            dataIndex: 'email'
        }];
        return (
            <div className="home-container">
                ...
                <Table dataSource={listData} columns={columns} rowKey="name" />
            </div>
        );
    }
}

const mapStateToProps = ...
const mapDispatchToProps = dispatch => ({
    ...

    onGetList: () => {
        getList({ dispatch });
    }
});

export default connect(mapStateToProps, mapDispatchToProps)(Home);

保存,刷新页面后就能看到数据

Antd 主题定制

Antd的主题定制有好几种方法,该项目是基于umi的,所以就是用umi配置的方法来定制。

在config/config.js 文件中添加配置

 theme: 'src/assets/css/theme.js'

在src/assets/css 下新建theme.js文件

这里只修改一个属性值 (其他属性可以参考文档)

module.exports = {
    'primary-color': '#f39700',
};

编译后,home页面的按钮主题就被修改了

至此一个基于umi的react项目结构就讲完了。

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

推荐阅读更多精彩内容