React + Antd + Mobx改进之前简单的todolist

2,260 阅读3分钟

之前用redux改进了todolist,用redux对应用状态进行管理,今天换成mobx。项目地址

1. 在create-react-app中使用装饰器

  1. 使用react-app-rewired
yarn add customize-cra react-app-rewired @babel/plugin-proposal-decorators --save
  1. 项目根目录新建config-overrides.js文件加入以下代码
const { override, addDecoratorsLegacy } = require('customize-cra');
module.exports = override(
 addDecoratorsLegacy()
 );
  1. 修改package.json文件如下
"scripts": {
 "start": "react-app-rewired start",
 "build": "react-app-rewired build",
 "test": "react-app-rewired test",
 "eject": "react-app-rewired eject"
  },

2. 了解mobx

可以先学习官方文档熟悉基本概念

  1. 按照上面改造create-react-app创建的项目后,引入mobx
yarn add mobx mobx-react
  1. 修改APP.js实现一个简单的domo
  • @observable 可以在实例字段和属性 getter 上使用, 对于对象的哪部分需要成为可观察的
  • @action 用来修改observables状态
  • @computed可以根据现有的状态或其它计算值衍生出的值,可以在任意类属性的 getter 上使用 @computed 装饰器来声明式的创建计算属性
  • @observer mobx与react结合的桥梁
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// import App from './App';
import * as serviceWorker from './serviceWorker';
import {observable, action, computed} from 'mobx'
import {observer} from 'mobx-react'

class Store {
  @observable timer = 0
  @computed get doubleTimer () {
    return this.timer * 2
  }
  @action.bound add () {
    this.timer ++
  }
}

@observer
class App extends React.Component {
  render () {
    const {doubleTimer, add} = this.props.store
    return (
      <div>
        <span>{doubleTimer}</span>
        <button onClick={() => add()}>+</button>
      </div>
    )
  }
}

ReactDOM.render(<App store={new Store()}/>, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

3. 使用mobx管理todolist的状态

  1. 修改之前的项目react-antd-todolist-simple,添加mobx相关包
yarn add mobx mobx-react
  1. 按照上面的步骤修改使其支持装饰器
  2. 新建store/index.js
import {observable, action} from 'mobx'
import list from '../data'

class Store {
  @observable todos = list

  @action addTodo = (todo) => { // 增
    this.todos = [...this.todos, todo]
  }

  @action deleteTodo = (id) => { // 删
    this.todos = this.todos.filter(item => {
      return item.id !== id
    })
  }

  @action completeTodo = (id) => { // 改
    this.todos = this.todos.map(item => {
      let obj = item.id === id ? {...item, isComplete: !item.isComplete} : item
      return obj
    })
  }

  @action searchTodo = (content) => { // 查
    if (content === '') {
      this.todos = list
    } else {
      this.todos = this.todos.filter(item => {
        return item.content.indexOf(content) !== -1
      })
    }
  }
}

export default new Store()
  1. 新建Provider组件作用和react-redux里面的Provider类似,父子组件之间通过context将store传递下去
import React, {Component} from 'react';
import PropTypes from 'prop-types'

export default class Provider extends Component {

  static propTypes = {
    store: PropTypes.object.isRequired,
  };

  static childContextTypes = {
    store: PropTypes.object,
  };

  getChildContext() {
    return {
      store: this.props.store
    }
  }

  render() {
    return this.props.children;
  }
}
  1. 改造components/TodoItem.js
import React from 'react';
import { Typography, Button } from 'antd';
import PropTypes from 'prop-types'
import {observer} from 'mobx-react'
import './TodoItem.less'

const { Text } = Typography;

@observer
class TodoItem extends React.Component {
  static contextTypes = {
    store: PropTypes.object,
  };

  render () {
    let { content, isComplete, id } = this.props
    let { completeTodo, deleteTodo } = this.context.store
    return (
      <div className="item-container" onDoubleClick={() => completeTodo(id)} style={{cursor: 'pointer'}}>
        <Text delete={isComplete}>{content}</Text>
        <Button type="primary" icon="delete" onClick={() => deleteTodo(id)}></Button>
      </div>
    )
  }
}

export default TodoItem;
  1. 改造components/DataList.js
import React from 'react';
import {observer} from 'mobx-react'
import TodoItem from './TodoItem'
import PropTypes from 'prop-types'
import { List } from 'antd';

@observer
class DataList extends React.Component {
  static contextTypes = {
    store: PropTypes.object,
  };
  render () {
    const {todos} = this.context.store
    return (
      <List
        bordered
        dataSource={todos}
        renderItem={item => (
            <List.Item>
              <TodoItem {...item}/>
            </List.Item>
        )}
      />
    )
  }
}

export default DataList;

7.改造components/Form.js

import React from 'react';
import {observer} from 'mobx-react'
import PropTypes from 'prop-types'
import { Input } from 'antd';

const Search = Input.Search;

@observer
class Form extends React.Component {
  static contextTypes = {
    store: PropTypes.object,
  };

  render () {
    const {searchTodo} = this.context.store
    return (
      <Search
        placeholder="请输入搜索内容"
        style={{marginBottom: '8px'}}
        onSearch={value => searchTodo(value)}
        enterButton
      />
    )
  }
}

export default Form;
  1. 改造componets/Footer.js
import React, { Component } from 'react';
import {observer} from 'mobx-react'
import { Button, Input } from 'antd';
import PropTypes from 'prop-types'

@observer
class Footer extends Component {
  static contextTypes = {
    store: PropTypes.object,
  };

  constructor(props) {
    super(props)
    this.state = {
      isAdd: false,
      addTodo: {}
    }
  }

  handleClick () {
    this.setState({
      isAdd: true
    })
  }

  handleChange (e) {
    this.setState({
      addTodo: {
        content: e.target.value,
        id: new Date(),
        completed: false
      }
    })
  }

  handleConfirm () {
    this.context.store.addTodo(this.state.addTodo)
    this.setState({
      isAdd: false
    })
  }

  render () {
    let addBtn = <Button type="primary" onClick={this.handleClick.bind(this)}>新增</Button>
    let addComponent = <div style={{display: 'flex'}}>
        <Input onChange={e => this.handleChange(e)} style={{marginRight: '10px'}}/>
        <Button type="primary" onClick={this.handleConfirm.bind(this)}>确认</Button>
      </div>
    let component = this.state.isAdd ? addComponent : addBtn
    return (
      <div style={{marginTop: '10px'}}>
        {component}
      </div>
    )
  }
}

export default Footer;
  1. 将所有改过的组件都引入到todoList中
import React from 'react'
import {observer} from 'mobx-react'
import { Layout } from 'antd';
import Form from './components/Form'
import DataList from './components/DataList'
import Footer from './components/Footer'
import './todolist.less'
import store from './store'
import Provider from './components/Provider'

const { Header, Content} = Layout;

const TodoListApp = observer(() => {
  return (
   <Provider store={store}>
      <Layout className="todolist-layout">
      <Header>
        <h3 className="logo">TodoList</h3>
      </Header>
      <Content className="todolist-content">
        <Form />
        <DataList />
        <Footer />
      </Content>
    </Layout>
   </Provider>
  )
})

export default TodoListApp

重新运行yarn start,至此使用mobx对todolist改造完毕。

相关:React + Antd + Redux改进之前简单的todolist

React + Antd + Redux改进之前简单的todolist