React
功能强大的状态管理工具
Mobx
- 利用getter和setter来收集组件的数据依赖关系
- 从而在数据变化的时候精确地知道哪些组件需要重绘
- 在界面的规模变大的时候,往往会有很多细粒度的更新
代码示例:
// 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