代码最佳实践(React+Mobx)
以下内容均以 react 作为实践
- ES6 的 class 封装
- 第三方库 mobx-react-lite
- 第三方库和 class 封装结合
ES6 的 class 封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
import { observable, action, runInAction } from 'mobx'
export class BaseItem { @observable id: string @observable name: string }
export class Item extends BaseItem { constructor(props?: Partial<BaseItem>) { super()
if (props) { this.update(props) } }
@action update(props: Partial<BaseItem>) { Object.assign(this, props) }
fetch = async () => { const data = { id: '1', name: 'eric' }
runInAction(() => { this.update(data) }) } }
|
这样一个 mobx 类就声明好了,在使用时,我们只需要这样:
1 2 3 4 5 6
| import { Item } from './item'
const item = new Item()
item.fetch()
|
再这样直接使用就可以啦:
1 2 3 4 5
| const demo = () => { return <div>我是{item.name}<div> }
|
当然这时最简单的模式啦,一般我们在 react 中的数据不会是这么简单的对吧?比如对象、数组,在这里我以表格数据为例,示范以下 mobx 类是如何实现对表格数据进行管理的
假设我们需要管理的数据类型是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
interface User{ id:string name:string }
interface Page{ page_size:number, page_index:number }
interface UserGroup { list:item[] total:number page:Page }
|
这样的话,我们可以先这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import { observable, action, runInAction } from 'mobx'
export class BaseItem { @observable id: string @observable name: string }
export class Item extends BaseItem { constructor(props?: Partial<BaseItem>) { super()
if (props) { this.update(props) } }
@action update(props: Partial<BaseItem>) { Object.assign(this, props) }
fetch = async () => { const data = { id: '1', name: 'eric' }
runInAction(() => { this.update(data) }) } }
|
再这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
import { observable, action, runInAction } from 'mobx' import { Item, BaseItem } from './Item'
export class BaseModel { @observable list: Item[] = [] @observable page_ctx: { index: number size: number } = { index: 1, size: 10, } @observable total:number }
type IRequest = Omit<BaseModel, 'list'> & { list: BaseItem[] }
export type FetchParams = { page_index: number page_size: number }
export class Model extends BaseModel { @action update({ list, ...props }: Partial<IRequest>) { Object.assign(this, props)
if (list) { this.list = list.map(item => new Item(item)) } }
fetch = async ({ key, page_index, page_size }: FetchParams) => { const data = await http.post('/test')
runInAction(() => { this.update(data) }) } }
|
这里是对获取到的表格数据的每一项都针对性的使用 mobx 管理起来,对于需要数据响应的表格还是很有效果的。并且 mobx 会自动跟踪状态和衍生之间的关系,你可以免费获得参照完整性。
mobx 类可以作为 react 项目的全局属性使用,也可在组件内部作为私有属性使用
第三方库 mobx-react-lite
详细使用方式可以看看这个npm mobx-react-lite
这里我们使用该库的 useLocalStore 作为页面级别的状态管理
1 2 3
| useLocalStore<T, S>(initializer: () => T, source?: S): T (deprecated)
|
这里贴一个简单例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function Measurement({ unit }) { const state = useLocalObservable(() => ({ unit, setUnit(val) { this.unit = val }, length: 0, get lengthWithUnit() { return this.unit === 'inch' ? `${this.length * 2.54} inch` : `${this.length} cm` } }))
useEffect(() => { state.unit = unit }, [unit])
return <h1>{state.lengthWithUnit}</h1> }
|
第三方库和 class 封装结合
这里的使用场景为:
先提供一个创建 context 的工厂函数,该工程提供 Provider,context,以及一个 useContext 的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import React, { createContext, useContext } from 'react'
export function createStore<T extends (...args: any) => any>( useExternalStore: T ) { const Context = createContext<ReturnType<T>>(null) function Provider({ children }) { const store = useExternalStore() return <Context.Provider value={store}>{children}</Context.Provider> }
return { Provider, Context, useStore: function useStore() { return useContext(Context) }, } }
|
在 TestPage/store/index.ts 文件中使用 useLocalStore 提供全局状态管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { createStore } from '@/utils/store' import { useLocalStore } from 'mobx-react-lite'
export function useModel() { const store = useLocalStore(() => ({ model: new Model(), loading: false, setLoading(val) { this.loading = val } }))
return store }
const store = createStore(useModel)
export const Provider = store.Provider export const Context = store.Context export const useStore = store.useStore
|
此时的 store 已经声明好了,其包含 Provider、Context、useStore,此时最后一步,我们在 TestPage 的首页 index.ts 使用 context 的属性,为 TestPage 提供全局数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React from 'react' import styled from 'styled-components' import { observer } from 'mobx-react-lite' import { Context, useModel, useStore } from './store'
const StyledLayout = styled.div``
export const Component = observer(function Component() { return <StyledLayout></StyledLayout> })
export default function Mobx() { const model = useModel()
return ( <Context.Provider value={model}> <Component /> </Context.Provider> ) }
|
做完这些,我们的 TestPage 就拥有了一个管理整个组件的状态机,在子页面中使用时,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import React from 'react' import styled from 'styled-components' import { observer } from 'mobx-react-lite' import { Context, useModel, useStore } from './store'
const StyledLayout = styled.div``
export const Component = observer(function Component() { const store = useStore() return <StyledLayout></StyledLayout> })
|