umi实现todoList

最近新项目用的umi框架,看完官网api后,打算运用umi+antd pro+dva上手实现个简单的todoList练习一下

在网上找到了一个效果示例


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

搭建项目:

前置条件:需要安装node环境及node版本10.13以上
查看node版本:
node -v

先找个地方建个空目录
mkdir myProject-umi&& cd myProject-umi
通过官方工具创建项目
npx @umijs/create-umi-app

项目创建成功,目录结构如下


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),默认的脚手架内置了 @umijs/preset-react,包含布局、权限、国际化、dva、简易数据流等常用功能。比如想要 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文件已经加上了.umi),他们会在 umi dev 和 umi build 时被删除并重新生成。

在浏览器中输入地址打开网页,可以看到默认的page页面


image.png

如果想要antd pro的布局,可以在.umirc.ts中配置layout:{},或新建layout文件夹下的自定义布局

考虑到实现一个todoList 需要状态管理 国际化等,目录结构改造如下

├── package.json
├── .umirc.ts //umi 配置,同 config/config.js,二选一,umi内置功能的配置和插件的配置,由于项目比较简单这里选用.umirc.ts
├── mock //mock数据
  └── todoList.ts
└── src
  ├── .umi //自动生成的临时文件
  ├── components //公共组件
    └── TodoList.tsx
  ├── pages // 所有路由组件在这里
    └── todoList //todoList页面
      ├── index.less
      └── index.tsx
  ├── model //状态管理
    └── model.ts
  ├── locales //国际化
    ├── en-US.ts
    └── zn-CN.ts
  ├── service //请求服务
    └── index.ts

配置

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

  • 页面路由:配置routes,格式为路由信息的数组。
{ 
  name: 'dashboard',
  icon: 'dashboard',
  hideInMenu: true,
  hideChildrenInMenu: true,
  hideInBreadcrumb: true,
  authority: ['admin'], 
}
  • name : 当前路由在菜单和面包屑中的名称,注意这里是国际化配置的 key ,具体展示菜单名可以在 /src/locales/zh-CN.js 进行配置。

  • icon : 当前路由在菜单下的图标名。

  • hideInMenu : 当前路由在菜单中不展现,默认 false

  • hideChildrenInMenu : 当前路由的子级在菜单中不展现,默认 false

  • hideInBreadcrumb : 当前路由在面包屑中不展现,默认 false

  • authority : 允许展示的权限,不设则都可见,详见:权限管理

  • 路由用按需引入的方式,配置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: {
  } //动态引入
});

mock

todoList主要的操作就是增删改查,
所以这里加了两个接口,查询list的接口,和更新list数据的接口(增删改都调用这个接口),并用setTimeout来模拟异步请求

  • getTodoList:请求初始todolist数据
  • updateTodoList:更新todolist数据
// 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: '初始任务1', status: '0', key: '初始任务1'},
                    {content: '初始任务2', status: '1', key: '初始任务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': '添加任务',
    'delete': '删除',
    'allTask': '所有任务',
    'completedTask': '已完成任务',
    'uncompletedTask': '待办任务'
};

model

model 作状态管理,包含

  • namespace: 表示在全局 state 上的 key
  • state:状态数据
  • reducers :管理同步方法,必须是纯函数
  • effects :管理异步操作,采用了 generator 的相关概念
  • subscriptions:订阅数据源

在 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;
    };
    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同步更新数据,一个方法就是将数据存储在model,页面从state中拿到最新数据

我们知道可以运用两种方式关联state和view
1、connect:需要用 dva 或 umi 中导出 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 (
        
{tabList.map(item => )}
); }; 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('');

    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 (
        <>
            
                {list?.map((item: IList) => (
                    
{item.content}
))}
setTaskContent(e.target.value)} />
); }; export default TaskList;

最终效果如图


image.png

你可能感兴趣的:(umi实现todoList)