umi

蚂蚁金服开源的企业级React框架,并不是UI框架

  1. 特性
    1. 开箱即用,内置react、react-router ...
    2. 类似next.js且功能完备的路由约定,同时也支持手动配置路由的方式;
    3. 完善的插件体系,高性能,通过插件支持PWA、以路由为单元的code splitting等等;
    4. 支持静态页面导出,适配各种环境,如中台业务、无线业务、egg、支付宝钱包
    5. 开发启动快,支持一键开启dll
    6. 一键兼容IE9、基于umi-plugin-polyfills
    7. 支持TypeScript
    8. dva 数据流的深入融合,支持duck directory、model的自动加载、code splitting等等
  2. dva 是React应用框架,封装了Redux、Redux-saga、React-router三个React工具库,目前React最流行的数据流解决放案;
数据流向.jpg
1. State:一个对象,保存整个应用状态;
2. View:React组件构成的视图层;
3. Action:一个对象,描述事件
4. connect():绑定```State```到```View```
5. dispatch():发送```Action```到```State```
  1. dvaumi的约定
    1. src源码:pages(页面)components(组件)layout(布局)model(数据模型)
    2. config配置
    3. mock数据模拟
    4. test测试
  2. 全局安装脚手架:npm i umi -g

初体验

  1. 创建一个项目目录:umidemo
  2. cd umidemo -> npm init:生成package.json
    "scripts": {
        "start": "umi dev",
        "build": "umi build"
    }
    
  3. 创建src目录,生成pages目录,默认使用约定式路由;
    cd src
    umi g page index  // index.js和index.css
    umi g page about  // bout.js和about.css
    
  4. 运行项目:npm start,自动编译生成页面配置 /src/pages/.umi目录,且项目是热部署;
    http://localhost:8000/  --> index.js
    http://localhost:8000/about  --> about.js
    

约定式路由嵌套

  1. 当出现 _layout.js 页面时默认为父组件页面,通过 {props.children} 显示子组件内容;
  2. 嵌套路由:/users,创建 pages/users 目录
        umi g page users/_layout    // pages/users/_layout.js、_layout.css
    
    1. pages/users/_layout.js
       import styles from './_layout.css'
       export default function(props) {
           return (
               <div className={styles.normal}>
                   <h1>Page users _layout</h1>
                   <div>{props.children}</div>  //引入子组件,如果没有创建子组件,则会报错
               </div>
           )
       }
      
    2. _layout.js 创建子组件,users的首页index.js
       umi g page users/index    // pages/users/index.js、index.css
      
    3. 访问嵌套路由:http://localhost:8000/users
  3. 动态路由文件的命名以 $ 为开头,users/$name.js对应路由为/users/:name
    1. users目录中,再创建$name.js、$name.css
    2. 访问$name.jshttp://localhost:8000/users/xxx
  4. 路由跳转
    1. users/index.js
        import Link from 'umi/Link'
        export default function() {
            const userList = [
                { id:1, name:'Tim' }, { id:2, name:'Jerry' }
            ]
            return (<div>
                <ul>
                    { userList.map(item=>(
                        <li key={item.id}>
                            <Link to={`/users/${item.name}`}>{item.name}</Link>
                        </li>
                    )) }
                </ul>
            </div>)
        }
    
    1. users/$name.js
        export default function(props) {
            return (
                <div>
                    <h2>{props.match.params.name}</h2>
                    <button onClick={()=>props.history.goBack()}>返回</button>
                </div>
            )
        }
    
  5. src/layout目录中的index.js将成为项目的定级布局页面,使用{props.children}显示src/pages目录中的组件。
    import styles from './index.css'
    export default function(props) {
        return (
            <div  className={styles.normal}>
                <p>我是项目的Layout布局</p>
                <div>{props.children}</div>
            </div>
        )
    }
    

配置式路由

  1. 配置式路由一旦创建,约定式路由自动失效,umi不会再自动创建路由;
  2. 在项目根目录下创建 config 目录,并创建 config.js 文件;
    export default {
        //路由配置:路径相对于src/pages
        routes: [
            { path: "/", component: "./index" },
            { path: "/about", component: "./about" },
            { 
                path: "/users",
                component: "./users/_layout",
                routes: [
                    { path: "/users", component: "./users/index" },
                    { path: "/users/:name", component: "./users/$name" },
                ]
            },
            { component: "./notfound" }  // 404页面,上面的所有路由都没有匹配时,则匹配404页面
        ]
    }
    
  3. 相应地,创建404组件:umi g page notfound
  4. 路由守卫
    1. 路由守卫组件的路径相对于项目根目录,且后缀名不能省略;
    2. 在项目根目录下创建routes目录,用于存放路由守卫组件;
    3. routes/PriviteRoute.js
        import Redirect from 'umi/redirect'
        export default function(props) {
            if(Math.random()>0.5) {
                return <Redirect to="/login" /> //没有登录时,重定向到登录页
            }
            //登录成功时,显示子路由的页面组件
            return <div>{props.children}</div>
        }
    
    1. 创建登录页umi g page login
    2. config/config.js中配置/login,并守卫/about
        routes: [
            { path: "/login", component: "./login" },
            { 
                path: "/about", 
                component: "./about",
                Routes: ["./routes/PriviteRoute.js"]  //路由守卫
            },
            ......
        ]
    
  5. 引入ant design UI库
    npm i antd -S
    npm i umi-plugin-react -D
    
    1. config/config.js
        export default {
            //路由配置
            routes: [...],
            //插件配置
            plugins: [
                [ "umi-plugin-react", { antd: true } ]
            ]
        }
    
    1. 使用时需要导入组件,因为是按需加载
        import {Button} from 'antd'
        
        <Button type="primary">Primary</Button>
    

引入dva

  1. dva主要是软件分层的概念
    1. Page负责与用户直接交互:渲染页面、接收用户的操作输入,侧重于展示型和交互逻辑;
    2. Model负责处理业务逻辑,可以理解成一个维护页面数据状态的对象,为Page做数据、状态的读写等操作;
        export default {
            namespace: 'goods',  // model的命名空间,区分多个model
            state: [],  //初始状态
            effects: {  //异步操作
            },
            reducers: {}
        }
    
    1. Service主要负责与HTTP做接口对接,跟后端做数据交互,读写数据;
  2. dva已经融合进了umi,在 config/config.js 中打开dva的开关
    plugins: [
        [ "umi-plugin-react", { antd: true, dva: true } ]
    ]
    

基本用法

    umi g page goods
    npm i axios -S
  1. 路由配置:config/config.js
    routes: [
        { path: "/goods", component: "./goods" },
    ]
    
  2. 在项目根目录下创建mock/goods.js,模拟接收请求,响应数据
    let data = [
        { title: '单页面' },
        { title: '管理项目' },
    ],
    export default {
        //method url
        "get /api/goods": function(req, res) {
            setTimeout(()=>{
                res.json({result: data})
            }, 1000);
        }
    }
    
  3. Modelsrc/models/goods.js
    import axios from 'axios'
    //调接口的逻辑应该放在 Service 层
    function getGoods() {
        return axios.get('/api/goods')
    }
    
    export default {
        namespace: 'goods',    // 命名空间,如果省略,则以文件名作为命名空间
        state: [],
        effects: {
            *getList(action, {call, put}) {  //异步操作
                //发起请求
                const res = yield call(getGoods)
                //派发异步action: initGoods 
                yield put({type:'initGoods', payload:res.data.result})
            }
        },
        reducers: {
            initGoods(state, action) {
                return action.payload
            },
            addGood(state, action) {  // 添加函数,返回一个新的state
                return [...state, {title: action.plyload.title}]
            }
        }
    }
    
  4. pages/goods.js
    import {connect} from 'dva'
    import React, {Component} from 'react'
    
    @connect(
        state=>({
            goodList: state.goods,  // 从指定命名空间内获取state
            loading: state.loading,  // 通过loading命名空间获取加载的状态
        }),
        {
            getList: ()=>{
                {type: 'goods/getList'}  // action的type需要以命名空间为前缀,后跟reducer
            },
            addGood: title=>({
                type:'goods/addGood',
                payload: {title}
            })
        }
    )
    export default class extends Component {
        componentDidMount() {
            this.props.getList()  //触发事件,发起请求,获取数据
        }
        render() {
            if(this.props.loading.models.goods) {
                //命名空间goods 的请求在加载中
                return <div>loading</div>
            }
            return (
                <div>
                    <ul>
                    {
                        this.props.goodList.map((good, index)=>{
                            return <li key={index}>{good.index}</li>
                        })
                    }
                    </ul>
                    <button onClick={()=>this.props.addGood("商品3")}>添加</button>
                </div>
            )
        }
    }
    

升级路由守卫

  1. 在项目根目录下创建mock/login.js
    export default {
        "post /api/login": function(req, res) {
            const {username, password} = req.body
            if(username==="xxx"&&password==="xxx") {
                return res.json({code: 200, data: {token:"xxxx", name:"xxx"}})
            }
            return res.json({code: 404, info:"登录失败"})
        }
    }
    
  2. Modelsrc/models/login.js
    import axios from 'axios'
    import router from 'umi/router'
    
    //初始的state
    const initUserInfo = {
        token: "",
        name: ""
    }
    //调接口的逻辑应该放在 Service 层
    function login(data) {
        return axios.post('/api/login', data)
    }
    
    export default {
        namespace: 'login',
        state: initUserInfo,
        effects: {  //异步操作
            *login(action, {call, put}) {
                //发起请求
                const res = yield call(login, action.payload)
                if(res.data.code===200) {  //登录成功
                    //派发异步action
                    yield put({type:'init', payload:res.data.data})
                    //登录成功,跳转去首页
                    router.push('/')
                } else {
                    console.log("登陆失败!")
                }
            }
        },
        reducers: {
            init(state, action) {
                return action.payload
            }
        }
    }
    
  3. pages/login.js
    import {connect} from 'dva'
    import React, {Component} from 'react'
    
    @connect()
    export default class extends Component {
        onSubmit() {
            //派发action,发起请求
            this.props.dispatch({type:"login/login", payload:{username:"xxx",password:"xxx"}})
        }
        render() {
            return (
                <div>
                    <button onClick={onSubmit}>登录</button>
                </div>
            )
        }
    }
    
  4. routes/PriviteRoute.js
    import React,{Component} from 'react'
    import Redirect from 'umi/redirect'
    import {connect} from 'dva'
    @connect(
        state => ({token:state.login.token})  //从指定命名空间内获取state
    )
    export default class extends Component {
        render() {
            if(this.props.token) {
                //登录成功时,显示子路由的页面组件
                return <div>{this.props.children}</div>
            }
            //没有登录时,重定向到登录页
            return <Redirect to="/login" />
        }
    }
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容