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

最近在看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

1
2
3
4
5
6
7
8
9
// 和Redux一样,mobx也给我们提供了一个Provider,注入所有的应用数据
import { Provider } from 'mobx-react'

ReactDOM.render(
<Provider {...stores}>
<Todo />
</Provider>,
document.getElementById('root')
)

src/store/TodoStore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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,使用起来非常简单,具体大家可以去看官方文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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本来是用来搭ssFQ的,机房在美国,所以使用起来响应会很慢。

部署项目主要做这么几件事情:

  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包,不然是运行不起来的。如果还是不放心,你也可以下载源码包,手动编译

1
2
3
4
5
6
7
8
9
10
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, 添加以下内容:

1
export PATH=~/packages/node-v9.5.0-linux-x64/bin:$PATH

保存并退出,执行 source ~/.bashrc 让配置生效,然后试一下npm -v,成功说明配置生效了

  1. 安装pm2
1
npm install -g pm2
  1. 把代码从github down下来,然后运行 pm2 start app.js

最后,用ip访问试一下,能看到页面的话就大功告成~

     
上一篇:第19章 解释器模式(Interpreter Pattern)


下一篇:Android——待办事项(ToDoList)