dva+typescript+react 实战

郑天寿
• 阅读 9435

基于最新版本 dva+react+typescript 的简单Demo


前言: 本文写给想学 ts 却无从下手, 学了 ts 想融入到 react 开发中却找不到练手demo的新手, 一个 测试鼠标和键盘点击速度的App, 本项目虽然有很多相似版本, 但是不符合各种依赖的版本更替,并且没有 typescript 的实现版本, 所以崩崩觉得还是有必要实现以下这个小东西, 给新手一些参考...... 具体效果如下图: dva+typescript+react 实战

Ps: 这是一个测试鼠标点击速度的 App,记录 1 秒内用户能最多点几次。顶部的 Highest Record 纪录最高速度;中间的是当前速度,给予即时反馈,让用户更有参与感;下方是供点击的按钮。


下面和我一起完成这个小demo吧


1. 项目目录结构

dva+typescript+react 实战


2. 安装依赖

npm install -g typescript
npm install --g dva-cli@next     // 最新版本 dva脚手架, 目前为 1.0.0-beta.4

npm install --save-dev @types/react @types/react-dom
npm install --save-dev ts-loader tslint-react

npm install --save antd
npm install --save keymaster   // 键盘事件依赖, 后面会用到

3. 创建项目

dva new 07-dva_calculator_count_example & cd 07-dva_...

4. 配置 typescript, tslint

具体相关配置详见 崩崩 的第一篇文章: 链接描述

5. 定义项目路由

由于 dvaumi 通过 umi-plugin-dva 插件相结合, 使用起来非常方便

而我们的项目路由则约定在 ./src/pages 目录下, 所以新建 example 文件夹 ./src/pages/example
接着测试我们的路由, 在 ./src/pages/example 下新建 page.tsx(./src/pages/example/page.tsx),


6. 测试路由

./src/pages/example 新建page.tsx, 写入测试的react组件
import * as React from 'react';

export interface ICounterProps {
    
};

const Counter: React.SFC<ICounterProps> = (): JSX.Element => {
    return (
        <h1>This is example page.</h1>
    );
};

export default Counter;
接着, 命令行键入 npm start, 在Chrome地址栏接上example, 可以看到结果了, 路由成功!

7. 策划model

  • 首先新建 ./src/utils/delay.tsx, 这是model中的延迟函数, 比较简单
// 定义 Promise 接口
interface IDelayPromise {
    Promise: (resolve?: () => {}, reject?: () => {}) => void;
};

const delay = (time: number): IDelayPromise => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
};

export default delay;

  • 现在正式开始书写我们的 model, model 是一个项目的灵魂,
  • 所以我放在第一步来做...... 新建 ./src/pages/example/models/counter.tsx, 代码如下:
import key from 'keymaster';            // 键盘相关事件

import delay from '../../../utils/delay';     // 延迟函数

export default {
    
    namespace: 'counter',
    
    state: {
        current: 0,        // 即时显示的数字
        record: 0,         // 最高纪录
    },
    
    /*
        回想我们的需求
            1. 鼠标点击, 数字增加, 纪录一秒内最高次数
            2. 一秒后, 数字递减, 变为初始数字
            
        而 reducers 是唯一可以改变 state 的地方, 
        
        这个需求里,我们需要定义两个 reducer,                     
        count/add 和 count/minus ,分别用于计数的增和减。要注意的是 count/add 时 record 的 
        逻辑,他只在有更高的记录时才会被记录, 这个用一句 三元判断即可
    */
    
    reducers: {
        add (state) {
            const newCurrent = state.current + 1;
            return {
                ...state,
                record: newCurrent > state.record ? newCurrent: state.record,
                current: newCurrent,
            };
        },
        
        minus (state) {
            const newCurrent = state.current - 1;
            return {
                ...state,
                current: newCurrent,
            };
        },
    },
    
    /*
        接着, 由于我们要实现, 延迟并返回初始状态,
        所以, 我们在 effects 中定义 addRemote 方法
        
        注: 这里的 call, put, 是大佬封装好的方法, 直接使用即可
               call: 用于调用异步处理函数
               put:  调用 reducers 
    */
    
    effects: {
        *addRemote ({ payload }, { call, put }) {
            yield put({ type: 'add' });
            yield call({ delay, 1000 });    // delay 函数, 
            yield put({ type: 'minus' }); 
        },
    },
    
    
    /*
        最后, 我们可以再加一点功能, 通过订阅键盘来获取键盘敲击次数
        当然, 按键也是随意更改的, 通过 subscriptions
    */
    subscriptions: {
        keyboardWather ({ dispatch }) {
            key('space', () => {
                dispatch({ type: 'addRemote' });
            });
        },
    },
};

OK, 到这里, 我们的 model 部分已经完成, 这是一个项目最重要的部分, 对类似 put, call, subscriptions 这些 api 不太熟悉的可以看一下这里: 链接描述

8. 书写 components

  • 现在开始项目组件的编写, 因为所有的状态都被存到了 dva 中,
  • 所以书写 简单美观函数组件(SFC) 是不二之选

1. 在 ./src/pages/example/page.tsx,这是大的路由组件, 里面包含类似 Header, Nav, Footer, 等类似的 容器组件, 在其内写入如下代码:

    import * as React from 'react';
    
    import Counter from './components/Counter';   // 导入 Counter 组件, 这是整个项目的容器
    
    const styles: NodeRequire = require('./index.less');    // 整个项目的样式文件, 等会儿会一一列出
    
    export interface IAppProps {};
    
    const App: React.SFC<IAppProps> = (): JSX.Element => {
        return (
            <div className={styles['app-container']}>
                <div className={styles['app-content']}>
                    <Counter />
                </div>
            </div>
        );
    };
    
    export default App;
    

2. 新建 ./src/pages/example/index.less,写入样式

    .app-container {}
    
    .app-content {
        box-sizing: border-box;
        overflow: hidden;
        width: 400px;
        height: 400px;
        margin: 50px auto;
        border: 1px solid #ccc;
        box-shadow: 0 0 4px 4px #ddd;
    }
    
    .counterbox {
        display: flex;
        flex-direction: column;
        box-sizing: border-box;
        height: 100%;
        padding: 10px;
    }
    
    .counterbox .counter-show {
        flex: 1;
        line-height: 1.5;
        font-size: 20px;
        color: #666;
    }
    
    .counterbox .counter-currentcount {
        line-height: 100px;
        font-size: 30px;
    }
    
    .counterbox .counter-button {
        flex: 3;
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        margin-top: 10px;
    }

3. 新建 ./src/pages/example/components/Counter.tsx, 这是整个项目的工作地

import * as React from 'react';

import { connect } from 'dva';            // 将dva中的 state 转化为组件的 props
import { Dispatch } from '../../../node_modules/redux';    // 类型限制, 

const styles: NodeRequire = require('../index.less');

import CounterButton from './CounterButton';    // 这是点击的按钮, 我将它分成了一个单独的组件,


// 定义接口
export interface ICounterProps {
    current: number;    // 即时数字
    record?: number;    // 最高纪录
    dispatch: Dispatch<{type: string, payload?: any}>;    // tip: 这里类型定义Dispatch, vscode 会自动帮我引入 Dispatch, 很智能
};


const Counter: React.SFC<ICounterProps> = (props: ICounterProps): JSX.Element => {
    const { current, record, dispatch } = props;
    
    // 按钮点击事件, 处理函数
    const handleClick: React.ReactEventHandler<HTMLButtonElement> = (event: React.MouseEvent<HTMLButtonElement>): void => {
        console.log(event.currentTarget);        // button Element
        dispatch({
            type: 'counter/addRemote',         // 触发 action
        });
    };
    
    return (
        <div className={styles['counterbox']}>
            <p className={styles['counter-show']}>The highest count is: { record }</p>
            <div className={styles['counter-currentcount']}>
                Current count is: { current }
            </div>
            <div className={styles['counter-button']}>
                <CounterButton onBtnClick={handleClick} />
            </div>
        </div>
    );
};

// 将 state => props
const mapStateToProps = (state): {current: number, record?: number} => {
    const { current, record } = state.counter;
    
    return {
        current,
        record,
    };
};


export default connect(mapStateToProps)(Counter);

3. 书写 Button按钮 组件

import * as React from 'react';

import { Button } from 'antd';


export interface ICounterButtonProps {
    onBtnClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
};


const CounterButton: React.SFC<ICounterButtonProps> = (props: ICounterButtonProps): JSX.Element => {
    return (
        <Button
            type = "primary"
            onClick = {props.onBtnClick}
        >
            Please Click Me 
        </Button>
    );
};

export default CounterButton;

9. 大功告成

保存, 刷新浏览器, 应该就OK啦

崩崩结语

    项目不难, 但是对于 新手 来说, 如何使用 ts 进行类型定义? ,以及懂得如何去梳理脉络, 我觉得是至关重要的.

    对代码有不懂的同学可以下方评论,或者加扣: 1766083035, 深入讨论

自学不易, 100% of the effort to change back to the tears of graduation.


转载请注明出处

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
4年前
VBox 启动虚拟机失败
在Vbox(5.0.8版本)启动Ubuntu的虚拟机时,遇到错误信息:NtCreateFile(\\Device\\VBoxDrvStub)failed:0xc000000034STATUS\_OBJECT\_NAME\_NOT\_FOUND(0retries) (rc101)Makesurethekern
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
4年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
4年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Python进阶者 Python进阶者
2年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
郑天寿
郑天寿
Lv1
想要飞行,想要落在屋顶。
文章
3
粉丝
0
获赞
0