# 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 (
)
}
}
```
`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`