# mobx5 **Repository Path**: ifercarly/mobx5 ## Basic Information - **Project Name**: mobx5 - **Description**: Mobx 使用全流程 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 2 - **Created**: 2022-04-15 - **Last Updated**: 2022-09-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: 分享 ## README ## 目标 这次:Mobx5 + class 组件。 下次:Mobx6 + 函数 / Hooks 组件。 ## 静态结构 [代码](https://gitee.com/ifercarly/redux-study/tree/01_%E9%9D%99%E6%80%81%E7%BB%93%E6%9E%84/) ## 拆分组件 ## 支持装饰器 ```bash yarn add -D react-app-rewired customize-cra @babel/plugin-proposal-decorators ``` `config-overrides.js` ```js const { override, addDecoratorsLegacy } = require('customize-cra') module.exports = override(addDecoratorsLegacy()) ``` `.babelrc` https://www.npmjs.com/package/babel-plugin-transform-decorators-legacy ```js { "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ] } ``` `package.json` ```json { "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-scripts eject" } } ``` ## 跑通 Mobx ```bash yarn add mobx@5 mobx-react@5 ``` `src/store/todo.js` ```js import { observable } from 'mobx' class MainStore { @observable todos = [ { id: 1, name: '吃饭', done: false }, { id: 2, name: '睡觉', done: true }, ] constructor(rootStore) { this.rootStore = rootStore } } export default MainStore ``` `src/store/index.js` ```js import MainStore from './todo' class RootStore { constructor() { this.MainStore = new MainStore(this) } } export default RootStore ``` `src/index.js` ```js import React from 'react' import ReactDOM from 'react-dom' import './styles/base.css' import './styles/index.css' import App from './App' import { Provider } from 'mobx-react' import RootStore from './store' ReactDOM.render( , document.querySelector('#root') ) ``` `src/components/Main/index.js` ```js import { inject, observer } from 'mobx-react' import React, { Component } from 'react' @inject('MainStore') @observer export default class TodoMain extends Component { render() { const { MainStore } = this.props console.log(MainStore.todos) return (
) } } ``` ## 列表渲染 `data.json` ```json { "todos": [ { "id": 1, "name": "吃饭", "done": false }, { "id": 2, "name": "睡觉", "done": true } ] } ``` `src/components/Main/index.js` ```js import { inject, observer } from 'mobx-react' import React, { Component } from 'react' import classNames from 'classnames' @inject('MainStore') @observer export default class TodoMain extends Component { componentDidMount() { this.props.MainStore.getTodos() } render() { const { MainStore: { todos }, } = this.props return (
) } } ``` `src/store/todo.js` ```js import { observable, action } from 'mobx' import request from '../utils/request' class MainStore { @observable todos = [] constructor(rootStore) { this.rootStore = rootStore } async getTodos() { const { data: todos } = await request.get('/') this.setTodos(todos) } @action.bound setTodos(todos) { this.todos = todos } } export default MainStore ``` `src/utils/request.js` ```js import axios from 'axios' export default axios.create({ baseURL: 'http://localhost:8888/todos', timeout: 5000, }) ``` ## 删除数据 `src/components/Main/index.js` ```js import { inject, observer } from 'mobx-react' import React, { Component } from 'react' import classNames from 'classnames' @inject('MainStore') @observer export default class TodoMain extends Component { componentDidMount() { this.props.MainStore.getTodos() } render() { const { MainStore: { todos, delTodo }, } = this.props return (
) } } ``` `src/store/todo.js` ```js import { observable, action, configure } from 'mobx' import request from '../utils/request' class MainStore { @observable todos = [] constructor(rootStore) { this.rootStore = rootStore } @action.bound async getTodos() { const { data: todos } = await request.get('/') this.setTodos(todos) } @action.bound setTodos(todos) { this.todos = todos } @action.bound async delTodo(id) { await request.delete(`/${id}`) await this.getTodos() } } export default MainStore ``` ## 状态切换 `src/store/todo.js` ```js import { observable, action, configure } from 'mobx' import request from '../utils/request' configure({ enforceActions: 'observed' }) class MainStore { @observable todos = [] constructor(rootStore) { this.rootStore = rootStore } @action.bound async getTodos() { const { data: todos } = await request.get('/') this.setTodos(todos) } @action.bound setTodos(todos) { this.todos = todos } @action.bound async delTodo(id) { await request.delete(`/${id}`) await this.getTodos() } @action.bound async changeTodoStatus({ id, done }) { await request.patch(`${id}`, { done: !done }) await this.getTodos() } } export default MainStore ``` `src/components/Main/index.js` ```js import { inject, observer } from 'mobx-react' import React, { Component } from 'react' import classNames from 'classnames' @inject('MainStore') @observer export default class TodoMain extends Component { componentDidMount() { this.props.MainStore.getTodos() } render() { const { MainStore: { todos, delTodo, changeTodoStatus }, } = this.props return (
) } } ``` ## 添加数据 `src/components/Header/index.js` ```js import { inject, observer } from 'mobx-react' import React, { Component } from 'react' @inject('MainStore') @observer export default class TodoHeader extends Component { state = { name: '' } handleChangeName = (e) => { this.setState({ name: e.target.value }) } handleKeyUpName = (e) => { if (e.key === 'Enter') { if (this.state.name.trim() === '') return alert('内容不能为空') this.props.MainStore.addTodo(this.state.name) this.setState({ name: '' }) } } render() { return (

todos

) } } ``` `src/store/todo.js` ```js import { observable, action, configure } from 'mobx' import request from '../utils/request' configure({ enforceActions: 'observed' }) class MainStore { @observable todos = [] constructor(rootStore) { this.rootStore = rootStore } @action.bound async getTodos() { const { data: todos } = await request.get('/') this.setTodos(todos) } @action.bound setTodos(todos) { this.todos = todos } @action.bound async delTodo(id) { await request.delete(`/${id}`) await this.getTodos() } @action.bound async changeTodoStatus({ id, done }) { await request.patch(`${id}`, { done: !done }) await this.getTodos() } @action.bound async addTodo(name) { await request.post('/', { name, done: false }) await this.getTodos() } } export default MainStore ``` ## 全选功能 `src/store/todo.js` ```js import { observable, action, configure, computed } from 'mobx' import request from '../utils/request' configure({ enforceActions: 'observed' }) class MainStore { @observable todos = [] constructor(rootStore) { this.rootStore = rootStore } @action.bound async getTodos() { const { data: todos } = await request.get('/') this.setTodos(todos) } @action.bound setTodos(todos) { this.todos = todos } @action.bound async delTodo(id) { await request.delete(`/${id}`) await this.getTodos() } @action.bound async changeTodoStatus({ id, done }) { await request.patch(`${id}`, { done: !done }) await this.getTodos() } @action.bound async addTodo(name) { await request.post('/', { name, done: false }) await this.getTodos() } @action.bound async updateTodo(done) { const arrPromise = this.todos.map((todo) => { return request.patch(`/${todo.id}`, { done }) }) await Promise.all(arrPromise) await this.getTodos() } @computed get allStatus() { return this.todos.every((item) => item.done) } } export default MainStore ``` `src/components/Main/index.js` ```js import { inject, observer } from 'mobx-react' import React, { Component } from 'react' import classNames from 'classnames' @inject('MainStore') @observer export default class TodoMain extends Component { componentDidMount() { this.props.MainStore.getTodos() } handleChangeAll = (e) => { this.props.MainStore.updateTodo(e.target.checked) } render() { const { MainStore: { todos, delTodo, changeTodoStatus, allStatus }, } = this.props return (
) } } ``` ## 双击显示编辑框 `src/components/Item/index.js` ```js import React, { Component } from 'react' import classNames from 'classnames' import { inject, observer } from 'mobx-react' @inject('MainStore') @observer export default class TodoItem extends Component { state = { isEditing: false, } handleDblClick = () => { this.setState({ isEditing: !this.state.isEditing, }) } render() { const { item, MainStore: { delTodo, changeTodoStatus }, } = this.props const { isEditing } = this.state return (
  • changeTodoStatus(item)} />
    {}} />
  • ) } } ``` ## 双击聚焦输入框 `src/components/Item/index.js` ```js import React, { Component, createRef } from 'react' import classNames from 'classnames' import { inject, observer } from 'mobx-react' @inject('MainStore') @observer export default class TodoItem extends Component { state = { isEditing: false, } inputRef = createRef() handleDblClick = () => { this.setState( { isEditing: !this.state.isEditing, }, () => { this.inputRef.current.focus() } ) } handleBlur = () => { this.setState({ isEditing: false }) } render() { const { item, MainStore: { delTodo, changeTodoStatus }, } = this.props const { isEditing } = this.state return (
  • changeTodoStatus(item)} />
    {}} ref={this.inputRef} onBlur={this.handleBlur} />
  • ) } } ``` ## 双击回显数据 `src/components/Item/index.js` ```js import React, { Component, createRef } from 'react' import classNames from 'classnames' import { inject, observer } from 'mobx-react' @inject('MainStore') @observer export default class TodoItem extends Component { state = { isEditing: false, // #1 currentName: '', } inputRef = createRef() handleDblClick = () => { this.setState( { isEditing: !this.state.isEditing, // #2 currentName: this.props.item.name, }, () => { this.inputRef.current.focus() } ) } handleBlur = () => { this.setState({ isEditing: false }) } render() { const { item, MainStore: { delTodo, changeTodoStatus }, } = this.props const { isEditing } = this.state return (
  • changeTodoStatus(item)} />
    {/* #3 */} this.setState(e.target.value)} ref={this.inputRef} onBlur={this.handleBlur} />
  • ) } } ``` ## 修改数据 `src/store/todo.js` ```js import { observable, action, configure, computed } from 'mobx' import request from '../utils/request' configure({ enforceActions: 'observed' }) class MainStore { @observable todos = [] constructor(rootStore) { this.rootStore = rootStore } @action.bound async getTodos() { const { data: todos } = await request.get('/') this.setTodos(todos) } @action.bound setTodos(todos) { this.todos = todos } @action.bound async delTodo(id) { await request.delete(`/${id}`) await this.getTodos() } @action.bound async changeTodoStatus({ id, done }) { await request.patch(`${id}`, { done: !done }) await this.getTodos() } @action.bound async addTodo(name) { await request.post('/', { name, done: false }) await this.getTodos() } // name => upateTodo all, 可以支持所有的 @action.bound async updateTodo(done) { const arrPromise = this.todos.map((todo) => { return request.patch(`/${todo.id}`, { done }) }) await Promise.all(arrPromise) await this.getTodos() } @computed get allStatus() { return this.todos.every((item) => item.done) } @action.bound async updateT({ id, key, value }) { await request.patch(`/${id}`, { [key]: value }) await this.getTodos() } } export default MainStore ``` `src/components/Item/index.js` ```js import React, { Component, createRef } from 'react' import classNames from 'classnames' import { inject, observer } from 'mobx-react' @inject('MainStore') @observer export default class TodoItem extends Component { state = { isEditing: false, // #1 currentName: '', } inputRef = createRef() handleDblClick = () => { this.setState( { isEditing: !this.state.isEditing, // #2 currentName: this.props.item.name, }, () => { this.inputRef.current.focus() } ) } handleBlur = () => { this.setState({ isEditing: false, currentName: '' }) } handleKeyUp = (e) => { if (e.key === 'Enter') { if (this.state.currentName === '') return const { id } = this.props.item this.props.MainStore.updateT({ id, key: 'name', value: this.state.currentName }) this.handleBlur() } } render() { const { item, MainStore: { delTodo, changeTodoStatus }, } = this.props const { isEditing } = this.state return (
  • changeTodoStatus(item)} />
    {/* #3 */} this.setState({ currentName: e.target.value })} ref={this.inputRef} onBlur={this.handleBlur} onKeyUp={this.handleKeyUp} />
  • ) } } ``` ==TODO:== ESC 清除。 ## 清空已完成 `src/store/todo.js` ```js import { observable, action, configure, computed } from 'mobx' import request from '../utils/request' configure({ enforceActions: 'observed' }) class MainStore { @observable todos = [] constructor(rootStore) { this.rootStore = rootStore } @action.bound async getTodos() { const { data: todos } = await request.get('/') this.setTodos(todos) } @action.bound setTodos(todos) { this.todos = todos } @action.bound async delTodo(id) { await request.delete(`/${id}`) await this.getTodos() } @action.bound async changeTodoStatus({ id, done }) { await request.patch(`${id}`, { done: !done }) await this.getTodos() } @action.bound async addTodo(name) { await request.post('/', { name, done: false }) await this.getTodos() } // name => upateTodo all, 可以支持所有的 @action.bound async updateTodo(done) { const arrPromise = this.todos.map((todo) => { return request.patch(`/${todo.id}`, { done }) }) await Promise.all(arrPromise) await this.getTodos() } @computed get allStatus() { return this.todos.every((item) => item.done) } @action.bound async updateT({ id, key, value }) { await request.patch(`/${id}`, { [key]: value }) await this.getTodos() } @computed get doned() { return this.todos.filter((item) => item.done) } @action.bound async clearCompleted() { const arr = this.doned.map((item) => { return request.delete(`/${item.id}`) }) await Promise.all(arr) await this.getTodos() } } export default MainStore ``` `src/components/Footer/index.js` ```js import { inject, observer } from 'mobx-react' import React, { Component } from 'react' @inject('MainStore') @observer export default class TodoFooter extends Component { render() { const { MainStore } = this.props return ( ) } } ``` ## 剩余数量的统计 `src/store/todo.js` ```js import { observable, action, configure, computed } from 'mobx' import request from '../utils/request' configure({ enforceActions: 'observed' }) class MainStore { @observable todos = [] constructor(rootStore) { this.rootStore = rootStore } @action.bound async getTodos() { const { data: todos } = await request.get('/') this.setTodos(todos) } @action.bound setTodos(todos) { this.todos = todos } @action.bound async delTodo(id) { await request.delete(`/${id}`) await this.getTodos() } @action.bound async changeTodoStatus({ id, done }) { await request.patch(`${id}`, { done: !done }) await this.getTodos() } @action.bound async addTodo(name) { await request.post('/', { name, done: false }) await this.getTodos() } // name => upateTodo all, 可以支持所有的 @action.bound async updateTodo(done) { const arrPromise = this.todos.map((todo) => { return request.patch(`/${todo.id}`, { done }) }) await Promise.all(arrPromise) await this.getTodos() } @computed get allStatus() { return this.todos.every((item) => item.done) } @action.bound async updateT({ id, key, value }) { await request.patch(`/${id}`, { [key]: value }) await this.getTodos() } @computed get doned() { return this.todos.filter((item) => item.done) } @computed get unDoned() { return this.todos.filter((item) => !item.done) } @action.bound async clearCompleted() { const arr = this.doned.map((item) => { return request.delete(`/${item.id}`) }) await Promise.all(arr) await this.getTodos() } } export default MainStore ``` `src/components/Footer/index.js` ```js import { inject, observer } from 'mobx-react' import React, { Component } from 'react' @inject('MainStore') @observer export default class TodoFooter extends Component { render() { const { MainStore } = this.props return ( ) } } ``` ## 点击底部按钮高亮 `src/components/Footer/index.js` ```js import { inject, observer } from 'mobx-react' import React, { Component } from 'react' import classNames from 'classnames' @inject('MainStore', 'footerStore') @observer export default class TodoFooter extends Component { handleClick = (active) => { this.setState({ active }) } componentDidMount() { const hs = window.location.hash /* let active = 'All' if (hs === '' || hs === '#/') { } else if (hs === '#/Active') { active = 'Active' } else if (hs === '#/Completed') { active = 'Completed' } */ const active = hs === '#/Active' || hs === '#/Completed' ? hs.slice(2) : 'All' this.props.footerStore.updateActive(active) } render() { const { MainStore, footerStore: { footerData: { tabs, active }, updateActive, }, } = this.props return ( ) } } ``` `src/store/filter.js` ```js import { observable, configure, action } from 'mobx' configure({ enforceActions: 'observed' }) class FooterStore { @observable footerData = { tabs: ['All', 'Active', 'Completed'], active: 'All', } constructor(rootStore) { this.rootStore = rootStore } @action.bound updateActive(active) { this.footerData.active = active } } export default FooterStore ``` ## 切换功能完成 `src/store/todo.js` ```js import { observable, action, configure, computed } from 'mobx' import request from '../utils/request' configure({ enforceActions: 'observed' }) class MainStore { @observable todos = [] constructor(rootStore) { this.rootStore = rootStore } @action.bound async getTodos() { const { data: todos } = await request.get('/') this.setTodos(todos) } @action.bound setTodos(todos) { this.todos = todos } @action.bound async delTodo(id) { await request.delete(`/${id}`) await this.getTodos() } @action.bound async changeTodoStatus({ id, done }) { await request.patch(`${id}`, { done: !done }) await this.getTodos() } @action.bound async addTodo(name) { await request.post('/', { name, done: false }) await this.getTodos() } // name => upateTodo all, 可以支持所有的 @action.bound async updateTodo(done) { const arrPromise = this.todos.map((todo) => { return request.patch(`/${todo.id}`, { done }) }) await Promise.all(arrPromise) await this.getTodos() } @computed get allStatus() { return this.todos.every((item) => item.done) } @action.bound async updateT({ id, key, value }) { await request.patch(`/${id}`, { [key]: value }) await this.getTodos() } @computed get doned() { return this.todos.filter((item) => item.done) } @computed get unDoned() { return this.todos.filter((item) => !item.done) } @action.bound async clearCompleted() { const arr = this.doned.map((item) => { return request.delete(`/${item.id}`) }) await Promise.all(arr) await this.getTodos() } @computed get renderList() { const active = this.rootStore.footerStore.footerData.active /* if (active === 'Active') { return this.unDoned } else if (active === 'Completed') { return this.doned } else { return this.todos } */ return active === 'Active' ? this.unDoned : active === 'Completed' ? this.doned : this.todos } } export default MainStore ``` `src/components/Main/index.js` ```js import { inject, observer } from 'mobx-react' import React, { Component } from 'react' import TodoItem from '../Item' @inject('MainStore') @observer export default class TodoMain extends Component { componentDidMount() { this.props.MainStore.getTodos() } handleChangeAll = (e) => { this.props.MainStore.updateTodo(e.target.checked) } render() { const { MainStore: { allStatus, renderList }, } = this.props return (
    ) } } ``` ## 改造 MobX 配置 ```bash yarn add mobx mobx-react-lite ``` `store/index.js` ```js import { createContext, useContext } from 'react' import FooterStore from './footerStore' import MainStore from './mainStore' class RootStore { constructor() { this.footerStore = new FooterStore(this) this.mainStore = new MainStore(this) } } const Context = createContext(new RootStore()) export default function useStore() { return useContext(Context) } ``` `store/mainStore.js`、`store/footerStore.js` `src/index.js` ## 改造类组件为函数组件 `src/App.js` `src/components/TodoFooter` `src/components/TodoMain` `src/components/TodoItem` `src/components/TodoHeader`