Mobx最佳实践

参考:
mobx官方文档:https://mobx.js.org/react-integration.html

1. 了解mobx收集依赖的机制

MobX 采用"运行时依赖追踪"策略,其核心规则可概括为:只有在当前执行上下文中实际被读取的 observable 数据才会建立依赖关系。(使用在function中的数据也不会被收集)
对于 React 组件而言,这种依赖收集主要发生在组件的 render 阶段。

1.1 render中引用的observable数据才会被收集

🌰:component组件无法监听子组件中render的数据变更。

这个问题主要在component组件中。(FC组件中数据在当前执行上下文里的)

有这种场景:页面Index组件使用的是component组件,其中有很多的子组件,子组件中有一个状态a,父组件Index需要监听状态a的变化,回调其他的方法。

然后就会有一个问题:状态a变动时,componentDidUpdate不会被触发。

是因为状态a没有收集到页面组件Index的依赖。

这种情况使用reaction解决即可,但是反应出来的mobx自动收集依赖的机制需要了解。

1.2 最小粒度的引用,精准地更新

mobx的依赖收集以最小粒度为准
比如:
你可以尝试一下,点击按钮修改user.email不会触发重渲染,修改user.age哪怕没有在return中也会触发重渲染,修改user.name同样。

const user = observable({
  name: 'test',
  age: 1,
  email: 'xxx',
});

const UserFC = observer(() => {
  // ✅ 被解构里面实际使用的字段才会建立依赖,变动响应
  // ❌ 如果其他地方改变了 user.email 字段,不会触发更新
  const { name, age } = user;
  console.log('重新渲染了');
  return (
    <Button onClick={action(() => (user.email = '22222'))}>
      {name}
    </Button>
  );
});

1.3 利用收集机制优化性能,减少重渲染次数

还是上面的例子,建议父组件传递observable数据不必细致到最小粒度,下方组件Child需要渲染user.name,但是父组件传的是user,而不是user.name
如果点击Button,会发现Child渲染了,但是Parent不会重渲染,这样节省了一个父组件的渲染性能。

const user = observable({
  name: 'test',
  age: 1,
  email: 'xxx',
});

const Child = observer(({user})=>{
  console.log('Child渲染',user.name)
  return <div>{JSON.stringify(user.name)}</div>
})

const UserFC = observer(() => {
  console.log('Parent渲染');
  return (
    <Button onClick={action(() => (user.name = '22222'))}>
      <Child user={user} />
    </Button>
  );
});

2. 状态修改通过 @action

设置configure({ enforceActions: "always" });保证在除了action以外的其他地方不允许直接修改store的值。

2.1 使用runInAction合并多个action操作

runInAction可以用来合并多个 action 操作,确保这些操作在同一个事务中执行,避免不必要的中间渲染。
推荐在需要修改多个可观察属性的场景使用,可以避免每次属性变更时触发的多次重渲染。

2.2 action操作对象遵循纯函数规则,使用不可变数据

即遵循redux中的reducer思路。纯函数规则和使用不可变数据是函数式编程的良好实践,能让代码更加可预测和易于维护。
如果我们直接写在action里面,就不用每次都拷贝一次了。

对于reducer的实现处理有如下建议:

  1. 简单对象和不复杂的数据结构:使用展开运算符 {…}即可。
  2. 复杂对象、深层嵌套的对象的不可变处理,推荐使用immer,能够带来书写时更好的体验。
import {produce} from 'immer';

@action
addTodo = (text) => {
    this.todos = produce(this.todos, draft => {
    draft.push({ test:111 });
    });
}

3. 使用多种不同的@observable监听方式

参考:https://mobx.js.org/observable-state.html

@observable 、@observable.deep

描述:
深度递归比对

observable.ref

描述:
观察对象是否被替换,而不会深度监听对象内部属性的变化。
*适用于不可变数据,只有对象的引用地址变更才会触发监听。

@observable.ref
test={
    shallow_test:1,
    shallow_obj:{
        test1:{test2:'11'}
    }
}

observable.struct

描述:
比较新旧值的结构是否相同(用于深度不可变场景)
*不能用在大型对象上,使用 observable.struct 会导致对比耗时
*可用于解决相同数据重复渲染的问题

使用:
和ref有点像,修改内部的值它是不会变的,只有对象的引用地址变更+内部数据有变化才会触发监听。
⬇️ 比如下面的例子,因为地址变更但是内部数据不变,所以不会触发组件重渲染。

 @observable.struct
  test={
    shallow_test:1,
    shallow_obj:{
        test1:{test2:'11'}
    }
  }

  @action
  setTest = () => {
    this.test = {
      shallow_test:1,
      shallow_obj:{
          test1:{test2:'11'}
      }
    }
  };

observable.shallow

描述:
只监听对象第一层属性(类似浅层监听)

使用:
比如下面的对象,只有shallow_test、shallow_obj变化时才会触发监听,shallow_obj.test1不会触发监听。

@observable.shallow
test={
    shallow_test:1,
    shallow_obj:{
        test1:{test2:'11'}
    }
}

observable.array

专为array设计,在引用地址不变的情况下,push / pop也可以触发变化监听

observable.map

专为对象设计,在引用地址不变的情况下修改内部值,也可以触发变化监听

4. 使用@computed自动计算

mobx会把store中的getter自动转为@computed,使用@computed的场景主要在多数据依赖、需要缓存的时候,会比较方便。

5. 💡监听特定数据使用reaction、autorun、when

请遵循以下原则:

  1. 监听数据并且做业务动作,使用reaction。(即:reaction和autorun都能用的时候选择reaction)
  2. debug需要跟踪值变化时可以使用autorun。
  3. when用于监听到某个条件时触发。
  4. 监听完毕后需要清除监听。
// autorun
// 自动收集observable数据的变动。
const dispose = autorun(() => {console.log(store.count)};
dispose();

// reaction
const dispose = reaction(
    () => store.count,
    (newCount) => {console.log(newCount);
});
dispose()

// when
const dispose = when(
    () => store.count === 3,
    () => {console.log('等于3时触发');
});
dispose()

6. mobx-react中可以使用mobx-react-lite的方法

扩展帮助:https://juejin.cn/post/6844904147922190349
[图片]

注:根据mobx-react-lite的readme,useObserver、useLocalStore、useAsObservableSource、ObserverBatching都已经弃用。

useLocalObservable

在组件内创建一个简易的Store状态管理,以替代useState、useEfffect,(Mobx可自动收集依赖,无需像useEffect那样需要手动输入),使用get属性时mobx将自动转化为computed。

  • 注意:我们的组件一般用不到这么复杂的状态管理工具,从功能上来说和React提供的工具方法有重合,并且自动收集依赖项而不是自己控制可能存在风险。

observer

将一个组件转化为可观察组件,mobx将自动收集组件内的变化数据,将此组件加入响应式的依赖内。
observer自动应用memo,因此observer组件永远不需要包裹在memo内。

import { observer } from "mobx-react-lite" // Or "mobx-react".

const MyComponent = observer(props => ReactElement)

Observer

mobx的github索引:<Observer />
其实也是创建了一个observer包裹的组件。
❗️但是不确定匿名函数的创建是否会在外层组件重渲染时重新创建,导致不必要的组件开销。

const TodoView = observer(({ todo }: { todo: Todo }) => {
    // CORRECT: wrap the callback rendering in Observer to be able to detect changes.
    return <GridRow onRender={() => <Observer>{() => <td>{todo.title}</td>}</Observer>} />
})

7. eslint规则:eslint-plugin-mobx

参考网址:https://www.npmjs.com/package/eslint-plugin-mobx

扩展问题:

1. observer包裹导出的高阶组件部分rules-of-hooks无法生效

const Child = Parent(() => {
  if (window.test === 1) return;
  
// 不会触发eslint:react-hooks/rules-of-hooks报错
  useEffect(() => {}, []);
  
  return (
    <div>
      <TopBar />
    </div>
  );
});

const Parent = (Component) => {
  return <Component />;
};

参考:

  1. eslint-plugin-react-hooks的代码:https://github.com/facebook/react/blob/e1378902bbb322aa1fe1953780f4b2b5f80d26b1/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js
  2. 类似issue:
  • https://github.com/facebook/react/issues/26072
  • https://github.com/facebook/react/pull/27546

原因:

eslint认可的高阶匿名函数组件只有Reaact官方提供的forwardRef和memo。
我们自定义的高阶组件和mobx的observer 包装后的组件都无法被Eslint认为是一个函数组件。
在这里插入图片描述

解决方案

  1. 推荐:使用非匿名函数:function Component (){}
// ✅ Child先定义好名称,再传入MobxWrap中。
const Component = () => { ... }
export const DecoratedComponent = observer(Component);

// ❌ 会引发eslint: prefer-arrow-callback错误
const _FC = MobxWrap(['app'],function Index(){
    // 会触发 prefer-arrow-callback 报错
})
  1. 函数式
// 使用hook+Observer
const Child = (props:{test:number})=>{
  const {app} = useMobx(['app'])
  if(window.test === 1) return;

  useEffect(()=>{

  },[])

  return <Observer>{()=>(<div />)}</Observer>
}
  1. 使用eslint认可的方法(forwardRef、memo)包裹,使eslint可识别这是一个组件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值