React + MobX + Electron + Node.js + MongoDB 全栈项目开发实践(一)

丰富功能

继续参照上一篇中介绍的知乎文章,借鉴一些想法,又加入自己的一些想法,开始初步完成一个应用的闭环。

任务拆分

一开始只是实现了非常简单的加分减分功能,这样虽然方便自己给自己记分,但是限制太少,也没有具体的目标。因此,为了做出相对完整的功能,开始细化任务及其数据结构。

设想了一下每日任务打卡的场景,画了一些流程草图,逐步定下来了任务的相关数据:


任务 id (暂时先用任务标题) 用户 id (目前是单用户,默认为 0) 标题(任务名称) 描述 类型(是任务还是欲望) 分数(根据类型决定加分还是减分) 每天最多使用次数 创建时间 最近更新

目前,任务以 JSON 格式存储在一个 JS 文件中。之后会将其存入数据库中。

任务的相关功能:
– 添加任务
– 查询任务
– 修改任务
– 删除任务
– 打卡

无非就是数据库的 CURD 工作。

组件/页面设计

数据(或者 state)要由组件来承载。现在构思到的组件是:分数显示组件、任务组件以及导航组件。

导航组件贯穿整个 APP,首页显示分数组件,通过导航进入任务/欲望界面,并在该界面显示相应的任务/欲望。

在第 0 篇中,因为刚写出来的乞丐乞丐版是真·SPA,所以只需要用 local state,加上 localStorage 来缓存数据以便之后读取。

代码长这样:

import React, { Component } from 'react';
import './App.css';
import { Row, Button, Icon } from 'antd';

class App extends Component {
  constructor(props){
    super(props);
    this.state = {
      score: localStorage.getItem('score') ? parseInt(localStorage.getItem('score')) : 0
    };
    this.increase = this.increase.bind(this);
    this.decrease = this.decrease.bind(this);
    this.clearScore = this.clearScore.bind(this);

  }

  increase(){
    this.setState({
      score: this.state.score + 1
    });
  }

  decrease(){
    this.setState({
      score: this.state.score - 1
    });
    console.log(this.state.score);
  }

  clearScore(){
    this.setState({
      score: 0
    });
  }

  componentDidUpdate() {
    localStorage.setItem('score', this.state.score);
  }

  render() {
    return (
      <div className="App">
        <Row>
          <Button className="operation" onClick={this.increase}>
            <Icon type="plus" />
          </Button>
        </Row>
        <Row>
          <Button className="operation" onClick={this.decrease}>
            <Icon type="minus" />
          </Button>
        </Row>
        <div className="score">
          <h2>{this.state.score}</h2>
        </div>
        <div className="clear">
          <Button onClick={this.clearScore}>Clear</Button>
        </div>
      </div>
    );
  }
}

export default App;

local storage 有个小坑,就是存进去的数值被转化成字符串了。因此每次读取的时候都需要转回数字。

有了页面、组件以后,更新分数的方法被绑在了 task 组件上,这样不能更新到上一级页面的分数。所以每次点击完都需要刷新页面,让任务页面组件读取本地缓存。

// ListPage 作为所有项目的容器
import React, { Component } from 'react';
import { Row, Col, Button } from 'antd';
import "./ListPage.css";

class ListPage extends Component {
  constructor(props) {
    super(props);
    this.state = {
      score: localStorage.getItem('score') ? parseInt(localStorage.getItem('score')) : 0
    };
  }

  clearScore() {
    this.setState({
      score: 0
    });
  }

  componentDidUpdate() {
    localStorage.setItem('score', this.state.score);
  }

  render() {
    const { type } = this.props.match.params;
    console.log(type);
    return (
      <div>
        <Row>
          <div className="score-list">
            <h2>{this.state.score}</h2>
          </div>
        </Row>
      </div>
    );
  }
}

export default ListPage;

// 容器中读取 list 数据,每一个数据对应一个 ListItem
import React, { Component } from 'react';
import { Card, Row, Col, Button } from 'antd';

import "./ListItem.css";

class ListItem extends Component {
  constructor(props){
    super(props)
    this.state = {
      buttonVisible: false
    }
  }
  onClickListItem(itemInfo) {
    console.log(itemInfo.name, itemInfo.type)
    this.setState({
      buttonVisible: !this.state.buttonVisible
    })
  }
  changeScore(itemInfo) {
    let score = parseInt(localStorage.getItem("score"))
    if(itemInfo.type === "task") {
      score += itemInfo.score
    } else {
      score -= itemInfo.score
    }
    localStorage.setItem("score", score)
  }
  render(){
    const { itemInfo } = this.props
    return (
      <Card className="list-item" onClick={() => {this.onClickListItem(itemInfo)}}>
        <Row>
          <Col span={16} className="list-item-info">
            <h3>{itemInfo.name}</h3>
            <p>{itemInfo.desc}</p>
          </Col>
          <Col span={8}>
            {itemInfo.type==="task" && <div className="item-score">+{itemInfo.score}</div>}
            {itemInfo.type==="desire" && <div className="item-score-desire">-{itemInfo.score}</div>}
          </Col>
        </Row>
        {this.state.buttonVisible && (<Row>
          <Button onClick={() => {this.changeScore(itemInfo)}}>打卡</Button>
        </Row>)}
      </Card>
    )
  }
}

export default ListItem;

这时就需要一个全局的状态管理。
(是否有更简单暴力的方法呢?比如自动刷新页面?或许可以,但是这会影响使用体验,且不利于以后增加其他功能,早晚会造成混乱。)

曾经使用过 Redux,刚入门的时候感觉到非常痛苦。又要 action,action types,reducer,store。虽然最后大致搞明白了,但是还是留下了一些坑,还有一个更新数据会赋值为对象的一个奇怪 bug。所以,接下来的文章中,我会边学习 MobX 边总结 Redux 的思想,将两者进行对比。

发表评论

电子邮件地址不会被公开。 必填项已用*标注