mobx怎么对可观察对象作出反应

MobX 通常会对你期望的东西做出反应。 这意味着在90%的场景下,mobx “都可以工作”。 然而,在某些时候,你会遇到一个情况,它可能不会像你所期望的那样工作。 在这个时候理解 MobX 如何确定对什么有反应就显得尤为重要。

MobX 会对在追踪函数执行过程读取现存的可观察属性做出反应。
  • “读取” 是对象属性的间接引用,可以用过 . (例如 user.name) 或者 [] (例如 user['name']) 的形式完成。
  • “追踪函数”computed 表达式、observer 组件的 render() 方法和 whenreactionautorun 的第一个入参函数。
  • “过程(during)” 意味着只追踪那些在函数执行时被读取的 observable 。这些值是否由追踪函数直接或间接使用并不重要

换句话说,MobX 不会对其作出反应:

  • observable 获取的值,但是在追踪函数之外(比如,可观察属性是否由observable组件的render函数渲染)
  • 在异步调用的代码块中读取的 observable

MobX 追踪属性访问,而不是值
假设你有如下的 observable 数据结构(默认情况下 observable 会递归应用,所以本示例中的所有字段都是可观察的)。

`let message = observable({
    title: "Foo",
    author: {
        name: "Michel"
    },
    likes: [
        "John", "Sara"
    ]
})` Copy

在内存中看起来像下面这样。 绿色框表示可观察属性。 请注意, 本身是不可观察的!
image.png
现在 MobX 基本上所做的是记录你在函数中使用的是哪个箭头。之后,只要这些箭头中的其中一个改变了(它们开始引用别的东西了),它就会重新运行。

Mobx没有对可观察属性做出响应有以下集中场景:

没有封装成observer 组件

容器组件

import Change from './Change';
import Father from './Father';

const Main = (props: any) => {
  return (
    <div>
      <Father></Father>
      <Change></Change>
    </div>
  )
}

export default Main;

Father组件

import { inject } from 'mobx-react';
import React from 'react';
import Store from '../../store/store';

interface IProps {
  store?: Store;
}

const Father = (props: IProps) => {
  const { store } = props;
  const { message } = store as Store;

  return <div>
    <div>title: {message.title}</div>
    <div>author: {message.author.name}</div>
    <div>likes: {message.likes[0]}</div>
  </div>
}

export default inject('store')(Father);

Change组件

import { Divider } from 'antd';
import { inject, observer } from 'mobx-react';
import React from 'react';
import Store from '../../store/store';

interface IProps {
  store?: Store;
}

const Change = (props: IProps) => {
  const { store } = props;

  const { setName, setTitle, setLikes } = store as Store;

  return <div>
    <button onClick={() => setTitle('spin')}>改变title</button>
    <button onClick={() => setName('tom')}>改变name</button>
    <button onClick={() => setLikes('john')}>改变likes</button>
  </div>
}

export default inject('store')(observer(Change));

store

import { observable, action } from 'mobx';

class Store {
  @observable message = {
    title: 'Bar',
    author: {
      name: 'Susan'
    },
    likes: ['Michel']
  }

  @action
  setTitle = (title: string) => {
    this.message.title = title;
  }
  
  @action
  setName = (name: string) => {
    this.message.author.name = name;
  }

  @action
  setLikes = (target: string) => {
    this.message.likes[0] = target;
  }
}

export default Store;

当点击改变title、name、likes按钮时,store中观察对象message的属性值改变了,但是Father组件并没有重新渲染。因为Father组件并不是observer组件,只有封装成observer组件,mobx才会对render函数(函数组件理解为return返回的ReactNode)中读取现存的可观察属性做出反应。

因此,只需要将Father组件封装成observer组件就可以解决

export default inject('store')(Father);
改后
export default inject('store')(observer(Father));

MobX 只会为数据是直接通过 render 存取的 observer 组件进行数据追踪

Father组件

const Father = (props: IProps) => {
  const { store } = props;
  const { message } = store as Store;

  return <div>
    <div>title: {message.title}</div>
    <Child title={() => <div>{message.author.name}</div>}></Child>
    <div>likes: {message.likes[0]}</div>
  </div>
}

export default inject('store')(observer(Father));

Child组件

interface IProps {
  title: () => React.ReactNode;
}

const Child = (props: IProps) => {
  const { title } = props;

  return <div>{title()}</div>
}

export default Child;

当改变storemessage.author.name时,页面并不会重新渲染。因为div实际上不是由 Father(有追踪的渲染) 渲染的,而是 Child。 所以要确保 Childtitle 可以正确对新的 message.author.name 作出反应,Child 应该也是一个 observer

如果 Child 来源于外部库的话,这通常不在你的掌控之中。在这种场景下,你可以用自己的无状态 observer 组件来包裹 div 解决此问题,或通过利用 <Observer>组件:

// 将Child改成observer组件
const Child = (props: IProps) => {
  const { title } = props;

  return <div>{title()}</div>
}

export default observer(Child);

另外一种方法可以使用 mobx-react 内置的 Observer 组件,它不接受参数,只需要单个的 render 函数作为子节点:

const Father = (props: IProps) => {
  const { store } = props;
  const { message } = store as Store;

  return <div>
    <div>title: {message.title}</div>
    <Child title={() => <Observer>{() => <div>{message.author.name}</div>}</Observer>}></Child>
    <div>likes: {message.likes[0]}</div>
  </div>
}

在本地字段中缓存 observable

一个常见的错误就是把间接引用的 observable 存储到本地变量,然后认为组件会作出反应。举例来说:

@inject('store')
@observer
class Father extends React.Component<IProps, any> {
  author: { name: string; } | undefined;
  likes: string[] | undefined;
  title: string | undefined;

  constructor(props: IProps) {
    super(props);
    this.title = props.store?.message.title;
    this.author = props.store?.message.author;
    this.likes = props.store?.message.likes;
    console.log('title: ' ,this.title, 'author: ' ,this.author, 'likes: ', this.likes)
  }

  render() {
    return <div>
      <div>title: {this.title}</div>
      <div>name: {this.author?.name}</div>
      <div>likes: {this.likes && this.likes[0]}</div>
    </div>
  }
}

export default Father;

组件会对authorlikes做出反应,不会对title做出反应,因为this.title = props.store?.message.title;是赋值,而this.author = props.store?.message.author;是赋引用,这个引用也是一个observable对象
image.png

换成函数组件

const Father = (props: IProps) => {
  const { store } = props;
  const { message } = store as Store;

  const title = message.title;
  const author = message.author;
  const likes = message.likes;

  return <div>
    <div>title: {title}</div>
    <Child title={() => <Observer>{() => <div>{author.name}</div>}</Observer>}></Child>
    <div>likes: {likes[0]}</div>
  </div>
}

export default inject('store')(observer(Father));

发现组件即会对authorlikes做出反应,也会对title做出反应


记得要微笑
1.9k 声望4.5k 粉丝

知不足而奋进,望远山而前行,卯足劲,不减热爱。