React

功能强大的状态管理工具

Mobx

  1. 利用getter和setter来收集组件的数据依赖关系
  2. 从而在数据变化的时候精确地知道哪些组件需要重绘
  3. 在界面的规模变大的时候,往往会有很多细粒度的更新

代码示例:

// src/App.jsx

import React from "react";
import {createRoot} from "react-dom/client";
import App from "./mobx/App";

createRoot(document.getElementById('root')).render(<App/>)
// src/mobx/App.jsx

import React from "react";
import {observable, autorun} from 'mobx'

// let obsNumber = observable.box(1)  // 普通数据类型的监听
//
// autorun(() => {  // 第一次会执行一次,之后每次跟函数体有关的改变时也会执行
//     console.log(obsNumber.get())
// })
//
// setTimeout(() => {
//     obsNumber.set(2)
// }, 1000)

// let obj = observable.map({  // 复杂数据类型的监听
//     name: 'Foo',
//     age: 24
// })
//
// autorun(() => {
//     console.log(obj.get('name'))  // 仅跟name有关的
// })
// setTimeout(() => {
//     obj.set('age', 18)
// }, 1000)

// 第二种写法
let obj = observable({  // 复杂数据类型的监听
    name: 'Foo',
    age: 24
})

autorun(() => {
    console.log(obj.name)  // 仅跟name有关的
})
setTimeout(() => {
    obj.name = 'Bar'
}, 1000)

export default function App() {
    return (
        <div>
            App
        </div>
    )
}
// src/App.jsx

import React from "react";
import {createRoot} from "react-dom/client";
import App from "./App";
import {Provider} from "react-redux";
import {store, persistor} from "./redux/store";
import {PersistGate} from "redux-persist/integration/react";

createRoot(document.getElementById('root')).render(
    <Provider store={store}>
        <PersistGate loading={null} persistor={persistor}>
            <App/>
        </PersistGate>
    </Provider>
)
// src/redux/mobx/store.js

// 异步环境下修改状态会被Mobx的严格模式认为是在外界修改状态,需要使用runInAction
/*
async getList() {
    let list = await axios(...).then(res => {
        return res.data.films
    })

    runInAction(() => {
        store.list = list
    })
}
*/
import {observable, configure, action, runInAction} from 'mobx'
import axios from "axios";

// 严格模式:不允许在action外部修改可观察对象
configure({
    enforceActions: 'always'  // always:任何时候都要应用严格模式,never:不启用
})

const store = observable({
    isTabbarShow: true,
    list: [],
    cityName: '幻想乡',
    changeShow() {
        store.isTabbarShow = true
    },
    changeHide() {
        store.isTabbarShow = false
    },
    getList() {
        axios.get(`/test.json`).then(res => {
            runInAction(() => {
                console.log(res.data.films)
                store.list = res.data.films
            })
        }).catch(err => console.log(err))
    }
}, {
    changeShow: action,  // 标记方法以专门修改可观察的对象
    changeHide: action,
    getList: action
})

export default store
// src/views/Cinemas.jsx

import React, {useEffect, useState} from "react";
import {useNavigate} from "react-router-dom";
import {NavBar} from 'antd-mobile'
import {SearchOutline} from 'antd-mobile-icons'
import store from "../redux/mobx/store";
import {autorun} from "mobx";

export default function Cinemas() {
    const [state, setState] = useState([])
    const navigate = useNavigate()

    useEffect(() => {
        if (store.list.length === 0) {
            store.getList()
        }
        let au = autorun(() => {
            console.log(store.list)
            setState(store.list)
        })

        return () => {
            au()  // 相当于取消订阅(否则会导致叠加创建)
        }
    }, [])

    return (
        <div>
            <NavBar right={<SearchOutline onClick={() => {
                navigate(`/cinemas/search`)
            }}/>} left={<div onClick={() => {
                navigate(`/city`)
            }
            }>{store.cityName}</div>} back={null}>
                影院内容
            </NavBar>
            {
                state.map(item =>
                    <dl key={item.filmId} style={{padding: '10px'}}>
                        <dt>{item.name}</dt>
                        <dd style={{fontSize: '12px', color: 'gray'}}>{item.synopsis}</dd>
                    </dl>
                )
            }
        </div>
    )
}
// src/views/Detail.jsx

import React, {useEffect} from "react";
import store from "../redux/mobx/store";

export default function Detail() {
    useEffect(() => {
        // store.isTabbarShow = false
        store.changeHide()

        return () => {
            // store.isTabbarShow = true
            store.changeShow()
        }
    }, [])

    return (
        <div>
            Detail
        </div>
    )
}
// src/components/Tabber.jsx

import React, {useEffect, useState} from "react";
import style from '../css/Tabber.module.css'
import {TabBar} from 'antd-mobile'
import {
    AppOutline,
    UnorderedListOutline,
    UserOutline,
} from 'antd-mobile-icons'
import {useLocation, useNavigate} from "react-router-dom";
import {autorun} from "mobx";
import store from "../redux/mobx/store";

export default function Tabber() {
    const [state, setState] = useState({
        isShow: false  // 第一次的时候会被修改
    })

    useEffect(() => {
        autorun(() => {
            setState({
                isShow: store.isTabbarShow
            })
        })
    }, [])

    const tabs = [
        {
            key: 'films',
            title: '首页',
            icon: <AppOutline/>,
        },
        {
            key: 'cinemas',
            title: '内容',
            icon: <UnorderedListOutline/>,
            badge: '5',
        },
        {
            key: 'centers',
            title: '我的',
            icon: <UserOutline/>,
        },
    ]
    const navigate = useNavigate()
    let location = useLocation()

    return (
        state.isShow && <footer className={style.tabbar}>
            <TabBar onChange={(value) => {
                navigate(value)
            }} activeKey={location.pathname.split('/', 2)[1]}>
                {tabs.map(item => (
                    <TabBar.Item key={item.key} icon={item.icon} title={item.title}/>
                ))}
            </TabBar>
        </footer>
    )
}

Mobx-React

不用自己订阅与取消订阅

代码示例:

// src/App.jsx

import React from "react";
import {createRoot} from "react-dom/client";
import App from "./App";
import {Provider} from "mobx-react";
import {persistor} from "./redux/store";
import {PersistGate} from "redux-persist/integration/react";
import store from './redux/mobx/store'

createRoot(document.getElementById('root')).render(
    <Provider store={store}>
        <PersistGate loading={null} persistor={persistor}>
            <App/>
        </PersistGate>
    </Provider>
)
// src/components/Tabber.jsx

import style from '../css/Tabber.module.css'
import {TabBar} from 'antd-mobile'
import {
    AppOutline,
    UnorderedListOutline,
    UserOutline,
} from 'antd-mobile-icons'
import {inject, observer} from "mobx-react";
import {useLocation, useNavigate} from "react-router-dom";

let Tabber = inject('store')(  // 函数式注入
    observer(
        function Tabber(props) {
            const tabs = [
                {
                    key: 'films',
                    title: '首页',
                    icon: <AppOutline/>,
                },
                {
                    key: 'cinemas',
                    title: '内容',
                    icon: <UnorderedListOutline/>,
                    badge: '5',
                },
                {
                    key: 'centers',
                    title: '我的',
                    icon: <UserOutline/>,
                },
            ]
            const navigate = useNavigate()
            let location = useLocation()

            return (
                props.store.isTabbarShow && <footer className={style.tabbar}>
                    <TabBar onChange={(value) => {
                        navigate(value)
                    }} activeKey={location.pathname.split('/', 2)[1]}>
                        {tabs.map(item => (
                            <TabBar.Item key={item.key} icon={item.icon} title={item.title}/>
                        ))}
                    </TabBar>
                </footer>
            )
        }
    )
)

export default Tabber
// src/views/Cinemas.jsx

import React, {useEffect} from "react";
import {useNavigate} from "react-router-dom";
import {NavBar} from 'antd-mobile'
import {SearchOutline} from 'antd-mobile-icons'
import store from "../redux/mobx/store";
import {Observer} from 'mobx-react'

export default function Cinemas() {
    const navigate = useNavigate()

    useEffect(() => {
        if (store.list.length === 0) {
            store.getList()
        }
    }, [])

    return (
        <div>
            <Observer>
                {
                    () => {
                        return (
                            <div>
                                <NavBar right={<SearchOutline onClick={() => {
                                    navigate(`/cinemas/search`)
                                }}/>} left={<div onClick={() => {
                                    navigate(`/city`)
                                }
                                }>{store.cityName}</div>} back={null}>
                                    影院内容
                                </NavBar>
                                {
                                    store.list.map(item =>
                                        <dl key={item.filmId} style={{padding: '10px'}}>
                                            <dt>{item.name}</dt>
                                            <dd style={{fontSize: '12px', color: 'gray'}}>{item.synopsis}</dd>
                                        </dl>
                                    )
                                }
                            </div>
                        )
                    }
                }
            </Observer>
        </div>
    )
}
// src/views/City.jsx

import React, {useState} from "react";
import {useNavigate} from "react-router-dom";
import store from "../redux/mobx/store";
import {Observer} from 'mobx-react'

export default function City() {
    const navigate = useNavigate()
    const [list] = useState(['红魔馆', '博丽神社', '白玉楼', '地灵殿'])

    return (
        <div>
            <Observer>
                {
                    () => {
                        return (
                            <ul>
                                {
                                    list.map(item =>
                                        <li key={item} onClick={() => {
                                            store.changeName(item)
                                            navigate(`/cinemas`)
                                        }}>{item}
                                        </li>
                                    )
                                }
                            </ul>
                        )
                    }
                }
            </Observer>
        </div>
    )
}
// src/redux/mobx/store.js

import {observable, configure, action, runInAction} from 'mobx'
import axios from "axios";

configure({
    enforceActions: 'always'
})

const store = observable({
    isTabbarShow: true,
    list: [],
    cityName: '幻想乡',
    changeShow() {
        store.isTabbarShow = true
    },
    changeHide() {
        store.isTabbarShow = false
    },
    getList() {
        axios.get(`/test.json`).then(res => {
            runInAction(() => {
                console.log(res.data.films)
                store.list = res.data.films
            })
        }).catch(err => console.log(err))
    },
    changeName(value) {
        store.cityName = value
    }
}, {
    changeShow: action,
    changeHide: action,
    getList: action,
    changeName: action
})

export default store