umi實現(xiàn)todoList

最近新項目用的umi框架乳绕,看完官網(wǎng)api后泥栖,打算運用umi+antd pro+dva上手實現(xiàn)個簡單的todoList練習(xí)一下

在網(wǎng)上找到了一個效果示例


src=http___img-blog.csdnimg.cn_20190118101803600.gif&refer=http___img-blog.csdnimg.gif

搭建項目:

前置條件:需要安裝node環(huán)境及node版本10.13以上
查看node版本:
node -v

先找個地方建個空目錄
mkdir myProject-umi&& cd myProject-umi
通過官方工具創(chuàng)建項目
npx @umijs/create-umi-app

項目創(chuàng)建成功禁偎,目錄結(jié)構(gòu)如下


image.png

package.json

{
  "private": true,
  "scripts": {
    "start": "umi dev",
    "build": "umi build",
    "postinstall": "umi generate tmp",
    "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
    "test": "umi-test",
    "test:coverage": "umi-test --coverage"
  },
  "gitHooks": {
    "pre-commit": "lint-staged"
  },
  "lint-staged": {
    "*.{js,jsx,less,md,json}": [
      "prettier --write"
    ],
    "*.ts?(x)": [
      "prettier --parser=typescript --write"
    ]
  },
  "dependencies": {
    "@ant-design/pro-layout": "^6.5.0",
    "react": "17.x",
    "react-dom": "17.x",
    "umi": "^3.5.20"
  },
  "devDependencies": {
    "@types/react": "^17.0.0",
    "@types/react-dom": "^17.0.0",
    "@umijs/preset-react": "1.x",
    "@umijs/test": "^3.5.20",
    "lint-staged": "^10.0.7",
    "prettier": "^2.2.0",
    "typescript": "^4.1.2",
    "yorkie": "^2.0.0"
  }
}

這里umi是3.x版本叛买,umi3.x官方文檔介紹 (umijs.org)厨诸,默認的腳手架內(nèi)置了 @umijs/preset-react,包含布局蜜托、權(quán)限抄囚、國際化、dva橄务、簡易數(shù)據(jù)流等常用功能幔托。比如想要 ant-design-pro 的布局,編輯 .umirc.ts 配置 layout: {}蜂挪,并且需要安裝 @ant-design/pro-layout重挑。

默認生成的.umirc.ts配置文件

import { defineConfig } from 'umi';

export default defineConfig({
  nodeModulesTransform: {
    type: 'none',
  },
  routes: [
    { path: '/', component: '@/pages/index' },
  ],
  fastRefresh: {},
});

安裝依賴:npm install
啟動項目:npm start
項目啟動后會自動在src下 生成臨時文件.umi文件夾,不要提交 .umi 目錄到 git 倉庫(一般在生成項目時間gitignore文件已經(jīng)加上了.umi)棠涮,他們會在 umi dev 和 umi build 時被刪除并重新生成谬哀。

在瀏覽器中輸入地址打開網(wǎng)頁,可以看到默認的page頁面


image.png

如果想要antd pro的布局严肪,可以在.umirc.ts中配置layout:{}史煎,或新建layout文件夾下的自定義布局

考慮到實現(xiàn)一個todoList 需要狀態(tài)管理 國際化等,目錄結(jié)構(gòu)改造如下

├── package.json
├── .umirc.ts //umi 配置驳糯,同 config/config.js篇梭,二選一,umi內(nèi)置功能的配置和插件的配置酝枢,由于項目比較簡單這里選用.umirc.ts
├── mock //mock數(shù)據(jù)
? └── todoList.ts
└── src
? ├── .umi //自動生成的臨時文件
? ├── components //公共組件
? ? └── TodoList.tsx
? ├── pages // 所有路由組件在這里
? ? └── todoList //todoList頁面
? ? ? ├── index.less
? ? ? └── index.tsx
? ├── model //狀態(tài)管理
? ? └── model.ts
? ├── locales //國際化
? ? ├── en-US.ts
? ? └── zn-CN.ts
? ├── service //請求服務(wù)
? ? └── index.ts

配置

.umirc.ts文件需要配置的項

  • 頁面路由:配置routes恬偷,格式為路由信息的數(shù)組。
{ 
  name: 'dashboard',
  icon: 'dashboard',
  hideInMenu: true,
  hideChildrenInMenu: true,
  hideInBreadcrumb: true,
  authority: ['admin'], 
}
  • name : 當前路由在菜單和面包屑中的名稱帘睦,注意這里是國際化配置的 key 袍患,具體展示菜單名可以在 /src/locales/zh-CN.js 進行配置。

  • icon : 當前路由在菜單下的圖標名竣付。

  • hideInMenu : 當前路由在菜單中不展現(xiàn)诡延,默認 false

  • hideChildrenInMenu : 當前路由的子級在菜單中不展現(xiàn)卑笨,默認 false 孕暇。

  • hideInBreadcrumb : 當前路由在面包屑中不展現(xiàn),默認 false 赤兴。

  • authority : 允許展示的權(quán)限妖滔,不設(shè)則都可見,詳見:權(quán)限管理

  • 路由用按需引入的方式桶良,配置dynamicImport項

  • 熱更新:fastRefresh

  • 國際化配置:locale
    ? default默認語言
    ? antd是否支持國際化

  • layout:antd pro布局

// .umirc.ts
import { defineConfig } from 'umi';

export default defineConfig({
  nodeModulesTransform: {
    type: 'none',
  },
  layout:{}, //antdpro布局
  routes: [
    {
        name: 'todo列表', //菜單名稱
        path: '/',
        component:'./todoList' 
    }
  ], //路由
  fastRefresh: {}, //熱更新
  locale: {
    default: 'zh-CN',
    antd: true,
  }, //國際化
  dynamicImport: {
  } //動態(tài)引入
});

mock

todoList主要的操作就是增刪改查座舍,
所以這里加了兩個接口,查詢list的接口陨帆,和更新list數(shù)據(jù)的接口(增刪改都調(diào)用這個接口)曲秉,并用setTimeout來模擬異步請求

  • getTodoList:請求初始todolist數(shù)據(jù)
  • updateTodoList:更新todolist數(shù)據(jù)
// mock/mock.ts
import { Response, Request } from "umi";

export default {
    'GET /api/getTodoList': (req: Request, res: Response) => {
        setTimeout(() => {
            res.send({
                status: 'ok',
                code: '200',
                data: [
                    {content: '初始任務(wù)1', status: '0', key: '初始任務(wù)1'},
                    {content: '初始任務(wù)2', status: '1', key: '初始任務(wù)2'}
                ]
            })
        }, 2000)
    },
    'POST /api/updateTodoList': (req: Request, res: Response) => {
        setTimeout(() => {
            res.send({
                status: 'ok',
                code: '200',
            })
        }, 2000)
    }
}

locales

//locales/en-US.ts
export default {
    'placeholder':'what to do',
    'addTask': 'add task',
    'delete': 'delete',
    'allTask': 'all task',
    'completedTask': 'completed task',
    'uncompletedTask': 'uncompleted task'
};

//locales/zh-CN.ts
export default {
    'placeholder':"你想做點什么",
    'addTask': '添加任務(wù)',
    'delete': '刪除',
    'allTask': '所有任務(wù)',
    'completedTask': '已完成任務(wù)',
    'uncompletedTask': '待辦任務(wù)'
};

model

model 作狀態(tài)管理采蚀,包含

  • namespace: 表示在全局 state 上的 key
  • state:狀態(tài)數(shù)據(jù)
  • reducers :管理同步方法,必須是純函數(shù)
  • effects :管理異步操作承二,采用了 generator 的相關(guān)概念
  • subscriptions:訂閱數(shù)據(jù)源

在 umi 中會按照約定的目錄來注冊 model榆鼠,且文件名會被識別為 model 的 namespace

model 還分為 src/models/.js 目錄下的全局 model,和 src/pages/**/models/.js 下的頁面 model

這里model文件建在src目錄下亥鸠,作為全局的model

//models/models.ts
import type {Reducer, Effect} from 'umi';
import {getTodoList, updateTodoList} from '@/services/index'
export interface IList {
    content: string;
    status: string;
    key: string;
}
export type ModelState = {
    list: IList[];
}
export interface PropsFromDva {
    data: ModelState;
} 
export type ModelType = {
    namespace: string;
    state: ModelState;
    reducers: {
        changeTaskList: Reducer<ModelState>;
    };
    effects: {
        updateTodoList: Effect,
        getTodoList: Effect
    }
}

const Model: ModelType = {
    namespace: 'data',
    state: {
        list: []
    },
    reducers: {
        changeTaskList(state, {payload}) {
            return {
                ...state,
                list: payload
            }
        }
    },
    effects: {
        *getTodoList(_,{call, put}) {
            const response = yield call(getTodoList);
            yield put({
                type: 'changeTaskList',
                payload: response.data
            })
        },
        *updateTodoList({payload},{call, put}){
            const response = yield call(updateTodoList,payload)
            return response;
        }
    }
}

export default Model;

service

//services/index.ts
import {request} from "umi";

export function updateTodoList(params:any) {
    return request(`/api/updateTodoList`, {
        method: 'post',
        params
    })
}

export function getTodoList() {
    return request(`/api/getTodoList`, {
        method: 'get',
    })
}

page

為了在做增刪改操作時妆够,三個tab同步更新數(shù)據(jù),一個方法就是將數(shù)據(jù)存儲在model负蚊,頁面從state中拿到最新數(shù)據(jù)

我們知道可以運用兩種方式關(guān)聯(lián)state和view
1神妹、connect:需要用 dva 或 umi 中導(dǎo)出 connect 方法,然后將 model 綁定到組件家妆,mapStateToProps鸵荠,在props中拿到state
2、dva 2.6x 之后伤极,提供的hook: useSelector蛹找、useDispatch

這里使用hook的形式,useDispatch useSelector

// pages/todoList/index.tsx
import styles from './index.less';
import TaskList from '@/components/TodoList';
import { useSelector, useDispatch } from 'dva';
import { PropsFromDva } from '@/models/model';
import { Tabs } from 'antd';
import { useEffect } from 'react';
import { useIntl } from 'umi';
const { TabPane } = Tabs;

const TodoList = () => {
    const dispatch = useDispatch();
    const intl = useIntl();
    const { list } = useSelector((state:PropsFromDva) => state.data);
    const completedList = list.filter(item => item.status === '1');
    const uncompletedList = list.filter(item => item.status === '0');
    const selectedKey = completedList.map(item => item.key);
    const tabList = [
        {tab: intl.formatMessage({id: 'allTask'}), key: '1', list: list},
        {tab: intl.formatMessage({id: 'completedTask'}), key: '2', list: uncompletedList},
        {tab: intl.formatMessage({id: 'uncompletedTask'}), key: '3', list: completedList},
    ];

    useEffect(() => {
        dispatch({type: 'data/getTodoList'});
    }, [])

    return (
        <div className={styles.main}>
            <div className={styles.page}>
                <Tabs defaultActiveKey='1' className={styles.tab}>
                    {tabList.map(item => 
                        <TabPane tab={item.tab} key={item.key}>
                            <TaskList list={item.list} selectedKey={selectedKey} />
                        </TabPane>
                    )}
                </Tabs>
            </div>
        </div>
    );
};

export default TodoList;

component

list組件 包含增刪改操作

// components/TodoList/index.tsx
import styles from './index.less';
import { useState } from 'react';
import { Button, Checkbox, Input } from 'antd';
import { useDispatch } from 'dva';
import { IList } from '@/models/model';
import { useIntl } from 'umi';
const CheckboxGroup = Checkbox.Group;

interface IProps {
    list: IList[];
    selectedKey: string[];
}
interface IResponse {
    status: string;
    code: string;
}
const TaskList = (props: IProps) => {
    const dispatch = useDispatch();
    const intl = useIntl();
    const {list, selectedKey} = props;
    const [taskContent, setTaskContent] = useState<string>('');

    const updateTodoList= async (list: IList[]) => {
        const res: IResponse = await dispatch({
            type: 'data/updateTodoList',
            payload: list
        })
    }
    const deleteItem = (key: string) => {
        const newList = list.filter((item: IList) => item.key!==key);
        updateTodoList(newList);
        dispatch({
            type: 'data/changeTaskList',
            payload: newList
        });
    };
    
    const onChangeStatus = (checkedValues:any) => {
        const newList:any[] = [];
        list.forEach((item: IList) => {
            if(checkedValues.includes(item.key)) {
                newList.push({...item, status: '1'});
            }
            else{
                newList.push({...item, status: '0'});
            }
        })
        updateTodoList(newList);
        dispatch({
            type: 'data/changeTaskList',
            payload: newList
        });
    };

    const addItem = () => {
        const newList = [...list, {content: taskContent, status: '0', key: taskContent}];
        updateTodoList(newList);
        dispatch({
            type: 'data/changeTaskList',
            payload: newList
        });
        setTaskContent('');
    };

    return (
        <>
            <CheckboxGroup
                onChange={onChangeStatus}
                value={selectedKey}
            >
                {list?.map((item: IList) => (
                    <div key={item.key} className={styles.row}>
                        <Checkbox value={item.key} className={styles.checkbox}>
                            {item.content}
                        </Checkbox>
                        <Button
                            className={styles.btn}
                            type='text'
                            onClick={() => deleteItem(item.key)}
                        >
                            {intl.formatMessage({id: 'delete'})}
                        </Button>
                    </div>
                ))}
            </CheckboxGroup>
            <div className={styles.add_box}>
                <Input
                    className={styles.add_box_input}
                    value={taskContent}
                    placeholder={intl.formatMessage({id:'placeholder'})}
                    onChange={e => setTaskContent(e.target.value)}
                />
                <Button
                    className={styles.add_box_btn}
                    type="primary"
                    onClick={() => addItem()}
                >
                    {intl.formatMessage({id: 'addTask'})}
                </Button>
            </div>
        </>
    );
};

export default TaskList;

最終效果如圖


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末塑荒,一起剝皮案震驚了整個濱河市熄赡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌齿税,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炊豪,死亡現(xiàn)場離奇詭異凌箕,居然都是意外死亡,警方通過查閱死者的電腦和手機词渤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門牵舱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缺虐,你說我怎么就攤上這事芜壁。” “怎么了高氮?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵慧妄,是天一觀的道長。 經(jīng)常有香客問我剪芍,道長塞淹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任罪裹,我火速辦了婚禮饱普,結(jié)果婚禮上运挫,老公的妹妹穿的比我還像新娘。我一直安慰自己套耕,他們只是感情好谁帕,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著冯袍,像睡著了一般匈挖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上颠猴,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天关划,我揣著相機與錄音,去河邊找鬼翘瓮。 笑死贮折,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的资盅。 我是一名探鬼主播调榄,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼呵扛!你這毒婦竟也來了每庆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤今穿,失蹤者是張志新(化名)和其女友劉穎缤灵,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蓝晒,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡腮出,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了芝薇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胚嘲。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖洛二,靈堂內(nèi)的尸體忽然破棺而出馋劈,到底是詐尸還是另有隱情,我是刑警寧澤晾嘶,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布妓雾,位于F島的核電站,受9級特大地震影響变擒,放射性物質(zhì)發(fā)生泄漏君珠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一娇斑、第九天 我趴在偏房一處隱蔽的房頂上張望策添。 院中可真熱鬧材部,春花似錦、人聲如沸唯竹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浸颓。三九已至物臂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間产上,已是汗流浹背棵磷。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留晋涣,地道東北人仪媒。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像谢鹊,于是被迫代替她去往敵國和親算吩。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容