MobX版todo案例

807 阅读5分钟

笔记来源:拉勾教育 - 大前端就业集训营

文章内容:学习过程中的笔记、感悟、和经验

提示:项目实战类文章资源无法上传,仅供参考

MobX版todo案例

准备工作

  1. 创建并初始化一个新项目
  2. 创建所需要的组件,拷贝需要HTML代码、拷贝并引入CSS文件
  3. 将三个子组件引入到app组件中

─ src

│ ├─ components(新建)

│ │ ├─ App.js(移动)

│ │ ├─ Foot.js(新建)

│ │ ├─ Header.js(新建)

│ │ └─ Main.js(新建)

│ ├─ index.css(新建)

│ └─ index.js

// src/components/App.js  引入三个子组件

import Foot from './Foot'
import Header from './Header'
import Main from './Main'

function App() {
  return (
    <section className='todoapp'>
      <Header />
      <Main />
      <Foot />
    </section>
  )
}

export default App
// src/index.js  引入全局css样式

import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App'
import './index.css'

ReactDOM.render(<App />, document.getElementById('root'))
// src/components/Header.js  顶部添加任务组件

import React, { Component } from 'react'

export class Header extends Component {
  render() {
    return (
      <header className='header'>
        <h1>todos</h1>
        <input className='new-todo' placeholder='What needs to be done?' />
      </header>
    )
  }
}

export default Header
// src/components/Main.js  任务列表展示组件

import React, { Component } from 'react'

export class Main extends Component {
  render() {
    return (
      <section className='main'>
        <input className='toggle-all' type='checkbox' />
        <ul className='todo-list'>
          <li className='completed'>
            <div className='view'>
              <input className='toggle' type='checkbox' />
              <label>Taste JavaScript</label>
              <button className='destroy'></button>
            </div>
            <input className='edit' />
          </li>
          <li>
            <div className='view'>
              <input className='toggle' type='checkbox' />
              <label>Buy a unicorn</label>
              <button className='destroy'></button>
            </div>
            <input className='edit' />
          </li>
        </ul>
      </section>
    )
  }
}

export default Main
// src/components/Foot.js  底部操作组件

import React, { Component } from 'react'

export class Foot extends Component {
  render() {
    return (
      <footer className='footer'>
        <span className='todo-count'>
          <strong>0</strong> item left
        </span>
        <ul className='filters'>
          <li>
            <button className='selected'>All</button>
          </li>
          <li>
            <button>Active</button>
          </li>
          <li>
            <button>Completed</button>
          </li>
        </ul>

        <button className='clear-completed'>Clear completed</button>
      </footer>
    )
  }
}

export default Foot

构建MobX工作流

  • 安装modx和mobx-react依赖

  • src目录下创建mobx专属目录和文件

  • 打通整个mobx流程,三个子组件都需要

  • 注意vscode编辑器需要配置装饰器语法支持

  • 下面的所有操作都需要注意修饰器的使用

// src/MobX/todo.js  创建mobx仓库

import { observable } from 'mobx'

// mobx类
class todoData {
  // 使用修饰器设置状态未可被观察
  @observable list = 1
}

// 创建实例
const todoList = new todoData()
export default todoList
// src/index.js  获取仓库并传递下去

import { Provider } from 'mobx-react'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App'
import './index.css'
import todoList from './MobX/todo'

ReactDOM.render(
  // 使用 Provider 传递仓库
  <Provider todoList={todoList}>
    <App />
  </Provider>,
  document.getElementById('root')
)
// src/components/  三个子组件引入状态仓库

import { inject, observer } from 'mobx-react'

// 装饰器
@inject('todoList')
@observer
................

实现任务添加功能

  • 顶部添加事件文本框添加键盘抬起事件,判断是否是回车键、是否不为空(trim)

  • 条件满足将任务内容添加到mobx的任务列表状态中

  • 添加完成后清除文本框内容

// src/MobX/todo.js  添加任务列表,添加任务action

import { action, observable } from 'mobx'

// mobx类
class todoData {
  // 使用修饰器设置状态未可被观察
  @observable list = []
  // 添加任务
  @action.bound add(todo) {
    this.list.push(todo)
  }
}

// 创建实例
const todoList = new todoData()
export default todoList
// src/components/Header.js  给文本框添加事件,满足条件调用添加任务action

import { inject, observer } from 'mobx-react'
import React, { Component } from 'react'

// 装饰器
@inject('todoList')
@observer
// 顶部组件
class Header extends Component {
  // 任务输入框键盘抬起事件
  addtodo(e) {
    // 获取任务名
    const name = e.target.value.trim()
    // 触发条件判定
    if (e.key === 'Enter' && name.length > 0) {
      // 调用action,传递参数
      this.props.todoList.add({ name, completed: false })
      // 重置输入框
      e.target.value = ''
    }
  }
  render() {
    return (
      <header className='header'>
        <h1>todos</h1>
        <input
          className='new-todo'
          placeholder='书写您要添加的任务'
          // 添加键盘抬起事件
          onKeyUp={e => this.addtodo(e)}
        />
      </header>
    )
  }
}

export default Header

实现任务列表展示功能

  • 可以先书写一些虚拟数据

  • 列表组件获取全部任务,进行数据遍历绑定

// src/MobX/todo.js  书写一些测试数据

import { action, observable } from 'mobx'

// mobx类
class todoData {
  // 使用修饰器设置状态未可被观察,写一些虚拟数据
  @observable list = [
    { name: '吃饭', completed: false },
    { name: '睡觉', completed: false },
    { name: '打豆豆', completed: true },
  ]
  // 添加任务
  @action.bound add(todo) {
    this.list.push(todo)
  }
}

// 创建实例
const todoList = new todoData()
export default todoList
// src/components/Main.js  利用获取到的状态数据,遍历创建结构展示出来

import { inject, observer } from 'mobx-react'
import React, { Component } from 'react'

// 装饰器
@inject('todoList')
@observer
// 中间列表组件
class Main extends Component {
  render() {
    // 解构对象
    const { todoList } = this.props
    return (
      <section className='main'>
        <input className='toggle-all' type='checkbox' />
        <ul className='todo-list'>
          {/* 遍历全部任务常见结构 */}
          {todoList.list.map((todo, index) => (
            // 动态设置类名
            <li className={todo.completed ? 'completed' : ''} key={index}>
              <div className='view'>
                <input className='toggle' type='checkbox' />
                <label>{todo.name}</label>
                <button className='destroy'></button>
              </div>
              <input className='edit' />
            </li>
          ))}
        </ul>
      </section>
    )
  }
}

export default Main

实现删除任务功能

  • 点击删除按钮后触发事件,调用删除任务的action,并传递参数
// src/MobX/todo.js  添加删除时间action
// 删除任务
  @action.bound remove(index) {
    this.list.splice(index, 1)
  }


// src/components/Main.js 给删除按钮添加点击事件,直接触发删除action
<button
  className='destroy'
  // 添加点击事件删除任务,直接调用action
  onClick={() => todoList.remove(index)}>
</button>

实现切换任务完成状态功能

  • 当点击任务前面的单选框时触发事件,调用修改状态的action,传递参数
// src/MobX/todo.js  添加修改任务状态action
// 修改任务状态
@action.bound changeCompleted(index, state) {
  this.list[index].completed = state
}

// src/components/Main.js  设置动态展示任务选中状态,添加状态修改事件直接触发action
<input
  className='toggle'
  type='checkbox'
  // 动态设置选中状态
  defaultChecked={todo.completed ? true : false}
  // 添加事件,直接触发action
  onChange={e =>
    todoList.changeCompleted(index, e.target.checked)
  }
/>

计算未完成任务数量

  • 使用mobx的计算值,计算未完成任务数量,并绑定给底部数量统计
// src/MobX/todo.js  添加计算未完成任务的计算值
// 统计未完成任务
@computed get completedNum() {
  return this.list.filter(item => item.completed === false).length
}

// src/components/Foot.js  绑定计算值数据
<span className='todo-count'>
  {/* 绑定mobx计算值 */}
  <strong>{this.props.todoList.completedNum}</strong> item left
</span>

实现任务筛选功能

  • 同样需要使用mobx计算值功能,添加一个筛选标记状态

  • 点击筛选条件时触发action修改条件

  • 根据筛选标记筛选出相应的展示列表

  • 最后把原来展示任务的列表数据换绑为筛选后的计算值

  • 三个筛选条件按钮根据筛选条件状态设置类名

// src/MobX/todo.js  添加筛选计算值、更晒筛选条件action和筛选条件状态值

import { action, computed, observable } from 'mobx'

// mobx类
class todoData {
  // 使用修饰器设置状态未可被观察,写一些虚拟数据
  @observable list = [
    { name: '吃饭', completed: false },
    { name: '睡觉', completed: false },
    { name: '打豆豆', completed: true },
  ]
  // 筛选条件
  @observable filterName = 'All'
  // 添加任务
  @action.bound add(todo) {
    this.list.push(todo)
  }
  // 删除任务
  @action.bound remove(index) {
    this.list.splice(index, 1)
  }
  // 修改任务状态
  @action.bound changeCompleted(index, state) {
    this.list[index].completed = state
  }
  // 统计未完成任务
  @computed get completedNum() {
    return this.list.filter(item => item.completed === false).length
  }
  // 计算值 - 按条件筛选
  @computed get filterList() {
    switch (this.filterName) {
      case 'all':
        return this.list
      case 'Active':
        return this.list.filter(item => item.completed === false)
      case 'Completed':
        return this.list.filter(item => item.completed === true)
      default:
        return this.list
    }
  }
  // 修改筛选条件
  @action.bound changeFilterName(name) {
    this.filterName = name
  }
}

// 创建实例
const todoList = new todoData()
export default todoList
// src/components/Foot.js  三个筛选添加点击事件并动态设置类名

import { inject, observer } from 'mobx-react'
import React, { Component } from 'react'

// 装饰器
@inject('todoList')
@observer
// 底部组件
class Foot extends Component {
  render() {
    return (
      <footer className='footer'>
        <span className='todo-count'>
          {/* 绑定mobx计算值 */}
          <strong>{this.props.todoList.completedNum}</strong> item left
        </span>
        <ul className='filters'>
          {/* 三个筛选按钮添加点击事件。直接触发action修改筛选条件 */}
          <li onClick={() => this.props.todoList.changeFilterName('All')}>
            <button
              // 三个按钮动态设置类名
              className={
                this.props.todoList.filterName === 'All' ? 'selected' : ''
              }>
              All
            </button>
          </li>
          <li onClick={() => this.props.todoList.changeFilterName('Active')}>
            <button
              className={
                this.props.todoList.filterName === 'Active' ? 'selected' : ''
              }>
              Active
            </button>
          </li>
          <li onClick={() => this.props.todoList.changeFilterName('Completed')}>
            <button
              className={
                this.props.todoList.filterName === 'Completed' ? 'selected' : ''
              }>
              Completed
            </button>
          </li>
        </ul>
        <button className='clear-completed'>Clear completed</button>
      </footer>
    )
  }
}

export default Foot
// src/components/Main.js  展示数据更改为筛选后的数据 - 计算值

import { inject, observer } from 'mobx-react'
import React, { Component } from 'react'

// 装饰器
@inject('todoList')
@observer
// 中间列表组件
class Main extends Component {
  render() {
    // 解构对象
    const { todoList } = this.props
    return (
      <section className='main'>
        <input className='toggle-all' type='checkbox' />
        <ul className='todo-list'>
          {/* 遍历全部任务常见结构,这里遍历筛选后的数据,而不是筛选前的数据 */}
          {todoList.filterList.map((todo, index) => (
            // 动态设置类名
            <li className={todo.completed ? 'completed' : ''} key={index}>
              <div className='view'>
                <input
                  className='toggle'
                  type='checkbox'
                  // 动态设置选中状态
                  defaultChecked={todo.completed ? true : false}
                  // 添加事件直接触发action
                  onChange={e =>
                    todoList.changeCompleted(index, e.target.checked)
                  }
                />
                <label>{todo.name}</label>
                <button
                  className='destroy'
                  // 添加点击事件删除任务直接调用action
                  onClick={() => todoList.remove(index)}></button>
              </div>
              <input className='edit' />
            </li>
          ))}
        </ul>
      </section>
    )
  }
}

export default Main

结尾

课程项目结束,但是还有一些bug可以解决,例如非All的筛选条件下更改状态会出现错误