dva+typescript+react 實戰

基於最新版本 dva+react+typescript 的簡單Demo


前言: 本文寫給想學 ts 卻無從下手, 學了 ts 想融入到 react 開發中卻找不到練手demo的新手, 一個 測試鼠標和鍵盤點擊速度的App, 本項目雖然有不少類似版本, 可是不符合各類依賴的版本更替,而且沒有 typescript 的實現版本, 因此崩崩以爲仍是有必要實現如下這個小東西, 給新手一些參考...... 具體效果以下圖: 圖片描述

Ps: 這是一個測試鼠標點擊速度的 App,記錄 1 秒內用戶能最多點幾回。頂部的 Highest Record 紀錄最高速度;中間的是當前速度,給予即時反饋,讓用戶更有參與感;下方是供點擊的按鈕。


下面和我一塊兒完成這個小demo吧


1. 項目目錄結構

圖片描述


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 插件相結合, 使用起來很是方便html

而咱們的項目路由則約定在 ./src/pages 目錄下, 因此新建 example 文件夾 ./src/pages/example
接着測試咱們的路由, 在 ./src/pages/example 下新建 page.tsx(./src/pages/example/page.tsx),node


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.


轉載請註明出處

相關文章
相關標籤/搜索