React+Mobx+Koa2+LeanCloud 搭建個人版TodoList

Z_Xu發表於2018-02-05

最近在看Mobx和Koa相關的內容,實踐初出真知,我們來做一個小專案實踐一下。最容易想到的就是Todolist了,這次我們做得稍微實用一點,放到自己的vps上日常用用也是不錯的~

用到的裝備:

  • React
  • Mobx
  • Koa2
  • LeanCloud
  • pm2

因為專案比較簡單,bundler我用的是parcel,一行配置都不用寫確實很爽,編譯速度也非常快。資料儲存我用的是LeanCloud,有開發版可以免費試用,就是請求數量會有一定的限制。當然你也可以使用MongoDB,這邊用雲端儲存主要是為了方便。

最終效果圖如下:

React+Mobx+Koa2+LeanCloud 搭建個人版TodoList
React+Mobx+Koa2+LeanCloud 搭建個人版TodoList

前端專案

https://github.com/zebrallel/Todolist

src/app.js

// 和Redux一樣,mobx也給我們提供了一個Provider,注入所有的應用資料
import { Provider } from 'mobx-react'

ReactDOM.render(
    <Provider {...stores}>
        <Todo />
    </Provider>,
    document.getElementById('root')
)
複製程式碼

src/store/TodoStore

import { observable, action } from 'mobx'

// 這裡就類似於Redux裡的Reducer了
class TodoStore {
    @observable todos
    @observable currMonth
    @observable currDay

    constructor() {
        const today = new Date()

        this.todos = []
        this.currMonth = today.getMonth()
        this.currDay = today.getDate()
    }

    // 載入todolist
    @action
    loadTodos = nextTodos => {
        this.todos.clear()
        nextTodos.forEach(item => {
            this.todos.push(item)
        })
    }

    // 更新月份
    @action
    updateCurrMonth = nextMonth => {
        this.currMonth = nextMonth
    }
    
    // 更新日期
    @action
    updateCurrDay = nextDay => {
        this.currDay = nextDay
    }

    // 新增一條todo
    @action
    insertNewItem = item => {
        this.todos.push(item)
    }
}

export default new TodoStore()
複製程式碼

接下來是action src/actions/TodoAction

import todoStore from '../../store/TodoStore'
import ApiService from '../../service/ApiService'

const loadTodos = async (date) => {
    const res = await ApiService.get(`/api/todos/query/${date}`)

    if(res.code === 0){
        todoStore.loadTodos(res.data)
    }
}

const updateMonth = month => {
    todoStore.updateCurrMonth(month)
}

const updateDay = day => {
    todoStore.updateCurrDay(day)
}

const insertNewItem = (item) => {
    todoStore.insertNewItem(item)
}

const updateItem = async (nextItem) => {
    await ApiService.post('/api/todos/update', nextItem)
}

export default {
    loadTodos,
    updateMonth,
    updateDay,
    updateItem,
    insertNewItem
}
複製程式碼

store, action都有了,最後就是我們的component了 src/components/Todo

import { todoActions as actions } from '../../actions'
import { observable, action } from 'mobx'
import { observer, inject } from 'mobx-react'

// 我們用inject把資料和元件連線起來,類似connect
// observer幫助我們實現資料變化後元件能夠實時重新整理
@inject(store => ({ ...store.todoStore }))
@observer
export default class TodoList extends React.Component {
    @observable addModalVisible = false
    @observable newItemValue = ''

    @action
    changeVisible = visible => {
        this.addModalVisible = visible
    }

    @action
    onNewItemValueChange = value => {
        this.newItemValue = value
    }

    @action
    clearNewItemValue = () => {
        this.newItemValue = ''
    }
    
    // ...
}
複製程式碼

Koa專案

https://github.com/zebrallel/Minos

入口 src/app.js

const Koa = require('koa')
const app = new Koa()
const convert = require('koa-convert')
const initStorage = require('./modules/storage')
const hbs = require('koa-hbs')
const rootRouter = require('./routes')
const bodyParser = require('koa-bodyparser')

const port = process.env.PORT || 4000

app.use(bodyParser())

// access log
app.use(async (ctx, next) => {
    const date = new Date().toLocaleString()
    const body = ctx.method.toLowerCase() === 'get' ? ctx.querystring : JSON.stringify(ctx.request.body)

    console.log(`[${date}]:::${ctx.method}:::${ctx.url}:::${body}`)

    await next()
})

// init view engine
app.use(
    hbs.middleware({
        viewPath: `${__dirname}/views`
    })
)

// router entry
app.use(rootRouter.routes())
app.use(rootRouter.allowedMethods())

// final router
app.use(async ctx => {
    switch (ctx.method.toLowerCase()) {
        case 'get':
            await ctx.render('pages/404')
            break
        case 'post':
            ctx.body = { code: -1, message: 'request path can not match!' }
            break
    }
})

app.listen(port, '127.0.0.1', null, () => {
    console.log(`Server is running on ${port}`)
})
複製程式碼

CURD src/routes/todos

這邊的CURD操作都是用了LeanCloud提供的Api,使用起來非常簡單,具體大家可以去看官方文件

const Router = require('koa-router')
const router = new Router()
const AV = require('../../modules/storage')

// 儲存一條新資料
router.post('/save', async (ctx, next) => {
    const { date, content } = ctx.request.body
    const TodoModel = AV.Object.extend('TodoModel')
    const item = new TodoModel()

    item.set('date', date)
    item.set('content', content)
    item.set('completed', false)

    try {
        const res = await item.save()

        ctx.body = { code: 0, message: 'success', data: { id: res.id } }
    } catch (error) {
        ctx.body = { code: -1, message: error.rawMessage, error }
    }
})

// 查詢
router.get('/query/:year-:month-:day', async (ctx, next) => {
    const { year, month, day } = ctx.params
    const dateQuery = new AV.Query('TodoModel')

    dateQuery.equalTo('date', `${year}-${month}-${day}`)

    try {
        const results = await dateQuery.find()
        const data = results.map(item => {
            const { date, content, completed } = item.attributes

            return {
                id: item.id,
                date,
                content,
                completed
            }
        })

        ctx.body = { code: 0, message: 'success', data }
    } catch (error) {
        ctx.body = { code: -1, message: error.rawMessage, error }
    }
})

// 更新
router.post('/update', async (ctx, next) => {
    const { id, content, date, completed } = ctx.request.body
    const todo = AV.Object.createWithoutData('TodoModel', id)

    todo.set('content', content)
    todo.set('completed', completed === 'true')
    todo.set('date', date)

    try {
        await todo.save()
        ctx.body = { code: 0, message: 'success' }
    } catch (error) {
        ctx.body = { code: -1, message: error.rawMessage, error }
    }
})

module.exports = router
複製程式碼

好了,程式碼都有了,最後一步就是將我們的程式碼部署到vps上了

如果大家有國內的雲主機是最好的,響應速度最快,我的vps本來是用來搭ss翻牆的,機房在美國,所以使用起來響應會很慢。

部署專案主要做這麼幾件事情:

  1. 安裝nginx
  2. 上傳前端靜態資源,交給nginx託管
  3. 配置nginx反向代理,將前端發出的非同步請求代理到node service埠
  4. 安裝Node
  5. 安裝pm2
  6. 執行node service

下面以安裝Node過程舉例說明一下,nginx同理,具體可以看這裡

  1. 登陸你的雲主機

下載Node最新版,這裡提醒一下,先用uname看一下機器的架構,是x86還是x64的,下載對應的32位或者64位binary包,不然是執行不起來的。如果還是不放心,你也可以下載原始碼包,手動編譯

cd ~
mkdir packages
cd packages
wget https://nodejs.org/dist/v9.5.0/node-v9.5.0-linux-x64.tar.xz
tar xvf node-v9.5.0-linux-x64.tar.xz
cd node-v9.5.0-linux-x64
cd bin
./node -v

// v9.5.0  // 看到這行就安裝成功了
複製程式碼
  1. 把bin目錄新增到PATH

修改~/.bashrc, 新增以下內容:

export PATH=~/packages/node-v9.5.0-linux-x64/bin:$PATH
複製程式碼

儲存並退出,執行 source ~/.bashrc 讓配置生效,然後試一下npm -v,成功說明配置生效了

  1. 安裝pm2
npm install -g pm2
複製程式碼
  1. 把程式碼從github down下來,然後執行 pm2 start app.js

最後,用ip訪問試一下,能看到頁面的話就大功告成~

相關文章