Dva+Antd-mobile專案實踐

後排的風過發表於2019-03-04

1.專案簡介

1.1 介紹

本專案為生成日曆器,使用者輸入資訊及選擇圖片後即可生成日曆。

1.2 專案地址

原始碼:github.com/Wind1ike/Ca…

DEMO:wind1ike.github.io/Calendar/

1.3 截圖

Dva+Antd-mobile專案實踐

Dva+Antd-mobile專案實踐

2.安裝與配置

2.1 安裝

(安裝請使用科學上網,不然安裝速度很慢)

npm i dva-cli -g
dva new calendar
cd calendar
npm i babel-import-plugin antd-mobile prop-types -D
複製程式碼

2.2 配置

.webpackrc

{
    "extraBabelPlugins": [
         ["import", { "libraryName": "antd-mobile", "style": true }]  //按需載入antd-mobile樣式檔案
    ]
}
複製程式碼

3.開發

3.1 編寫入口檔案

/scr/index.js

import dva from `dva`;
import createHistory from `history/createBrowserHistory`;
import `./index.css`;

// 1. Initialize
const app = dva({
    history: createHistory()
});

// 2. Plugins
// app.use({});    //外掛

// 3. Model
app.model(require(`./models/calendar`).default);    //載入資料model檔案

// 4. Router
app.router(require(`./router`).default);    //載入路由檔案

// 5. Start
app.start(`#root`);     //啟動程式

複製程式碼

3.2 編寫路由檔案

該專案可分為兩個路由頁面,一個為使用者填寫資訊的頁面,另一個為生成的日曆頁面。

/src/router.js

import React from `react`;
import { Router, Route, Switch } from `dva/router`;
import IndexPage from `./routes/IndexPage`;
import Canvas from `./routes/Canvas`;

function RouterConfig({ history, text }) {
  return (
    <Router history={history}>
      <Switch>
        <Route path="/" exact component={IndexPage} />
        <Route path="/canvas" exact component={Canvas} />
      </Switch>
    </Router>
  );
}

export default RouterConfig;

複製程式碼

3.3 編寫元件佈局

/src/routes/IndexPage.jsx

import React from `react`;
import Header from `../components/IndexPage/Header`;
import InputBox from `../components/IndexPage/InputBox`;

function IndexPage() {
  return (
    <div>
      <Header></Header>
      <InputBox></InputBox>
    </div>
  );
}

IndexPage.propTypes = {
};

export default IndexPage;

複製程式碼

/src/components/IndexPage/Header.jsx

import { NavBar } from `antd-mobile`;
import styles from `./Header.css`;

function Header() {
    return (
        <NavBar
        mode="light"
        className={styles[`single-top-nav-bar`]}
        >
            <p className={styles[`am-navbar-title`]}>日曆生成器</p>
        </NavBar>
    )
}

export default Header;

複製程式碼

/src/components/IndexPage/InputBox.jsx

import { List, TextareaItem, InputItem, DatePickerView, ImagePicker, Button, Modal } from `antd-mobile`;
import { connect } from `dva`;
import { routerRedux } from `dva/router`;
import React from `react`;

class InputBox extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            text: ``,
            todo: ``,
            notTodo: ``,
            date: 0,
            files: [],
            picture: null,
            isShowModal: false
        }
    }
    
    render() {
        return (
            <React.Fragment>
                <List renderHeader={()=> `文字資訊`}>
                    <TextareaItem
                    title="內容"
                    placeholder=""
                    data-seed="logId"
                    rows="5"
                    value={this.state.text}
                    onChange={(text)=> this.setState({text})}></TextareaItem>
                    <InputItem
                    value={this.state.todo}
                    onChange={(text)=> this.setState({todo: text})}>宜</InputItem>
                    <InputItem
                    value={this.state.notTodo}
                    onChange={(text)=> this.setState({notTodo: text})}>忌</InputItem>
                </List>
                <List renderHeader={()=> `選擇日期`}>
                    <DatePickerView
                    mode="date" 
                    value={this.state.date}
                    onChange={(date)=> this.setState({date: date})}/>
                </List>
                <List renderHeader={()=> `選擇圖片`}>
                    <ImagePicker 
                    files={this.state.files}
                    selectable={this.state.files.length < 1}
                    onChange={(files)=> this.setState({picture: files[0], files})}/>
                </List>
                <Modal
                visible={this.state.isShowModal}
                transparent
                maskClosable={false}
                title="檢查是否為填寫內容或未選擇圖片!"
                footer={[{ text: `Ok`, onPress: () => { this.setState({isShowModal: false}) } }]}
                wrapProps={{ onTouchStart: this.onWrapTouchStart }}
                />
                <Button
                type="primary"
                onClick={()=> this.onSubmit()}>生成圖片</Button>
            </React.Fragment>
        )
    }

    onSubmit() {
        if(!(this.state.text && this.state.picture)) {
            this.setState({isShowModal: true});
            return ;
        }

        this.props.dispatch(routerRedux.push(`/canvas`));   //切換路由
    }
}


export default InputBox;

複製程式碼

/scr/routes/Canvas.jsx

import React from `react`;

class Canvas extends React.Component {

    componentDidMount() {
        this.canvas.height = document.body.scrollHeight;
        this.canvas.width = document.body.scrollWidth;

        this.cxt = this.canvas.getContext(`2d`);
    }

    render() {
        return (
            <canvas 
            ref={(el)=> this.canvas = el}></canvas>
        )
    }

    
}

export default Canvas;
複製程式碼

3.4 編寫model檔案

Redux

Dva中採用Redux來管理資料模型,只有一個Store來儲存資料。

Store:儲存資料的地方,只能通過Reducers返回新的狀態

Action:元件通過connect函式包裝後會有this.props.dispatch方法,呼叫該方法來觸發相應的Action。Action中通過呼叫put函式呼叫相應的Reducers

Reducers:Reducers從Store中獲取舊狀態,並根據Action中傳過來的資料組成新的狀態並返回。

/src/models/calendar.js

export default {

  namespace: `calendar`,

  state: {  // 狀態
    text: ``,
    todo: ``,
    notTodo: ``,
    date: 0,
    picture: null
  },

  subscriptions: {
    setup({ dispatch, history }) {  // 監聽路由觸發動作
    },
  },

  effects: {    // actions
    *submit({ payload }, { call, put }) {  
      yield put({
        type: `save` ,  //呼叫reducers中save函式
        payload,
      });
    },
  },

  reducers: {
    save(state, action) {
      return { ...state, ...action.payload };   //返回新的State,不修改舊的State
    },
  },

};
複製程式碼

修改 /src/components/IndexPage/InputBox.jsx

...
import { connect } from `dva`;
...
class InputBox extends React.Component {
   constructor(props) {
        super(props);
        let date;
        
        if(this.props.date) {
            date = new Date(this.props.date);
        } else {
            date = new Date();
        }
        console.log(this.props)
        this.state = {
            text: this.props.text,
            todo: this.props.todo,
            notTodo: this.props.notTodo,
            date: date,
            files: [],
            picture: null,
            isShowModal: false
        }
    }

    ...

    onSubmit() {
        if(!(this.state.text && this.state.picture)) {
            this.setState({isShowModal: true});
            return ;
        }

        const { text, todo, notTodo, date, picture } = this.state;

        this.props.dispatch({
            type: `calendar/submit`,
            payload: { text, todo, notTodo, picture, date: date.getTime() }
        });     //觸發名稱空間中submit的Actions

        this.props.dispatch(routerRedux.push(`/canvas`));   //切換路由
    }
}

function mapStateToProps(state) {   //該函式用來把Store中的state轉換成元件的props
    return state.calendar;
}

export default connect(mapStateToProps)(InputBox);  //通過connnect函式把state注入元件的props中

複製程式碼

修改 /src/routes/Canvas.jsx

...
import { connect } from `dva`;
...
class Canvas extends React.Component {
    componentDidMount() {
        this.canvas.height = document.body.scrollHeight;
        this.canvas.width = document.body.scrollWidth;

        this.cxt = this.canvas.getContext(`2d`);
        this.drawImage();
        this.drawDate();
        this.drawText();
        this.drawEvent();
    }
    ...
    (大部分程式碼為操作canvas程式碼,不貼出來,有需要可以去看原始碼)
}
function mapStateToProps(state) {   //該函式用來把Store中的state轉換成元件的props
    return state.calendar;
}

export default connect(mapStateToProps)(Canvas);    //通過connnect函式把state注入元件的props中
複製程式碼

4.總結

本專案通過觸發同步Actions來進行互動,沒有網路請求等非同步操作,實現較為簡單。

相關文章