mobx怎么对可观察对象作出反应
MobX
通常会对你期望的东西做出反应。 这意味着在90%的场景下,mobx
“都可以工作”。 然而,在某些时候,你会遇到一个情况,它可能不会像你所期望的那样工作。 在这个时候理解 MobX
如何确定对什么有反应就显得尤为重要。
MobX 会对在追踪函数执行过程中读取现存的可观察属性做出反应。
- “读取” 是对象属性的间接引用,可以用过
.
(例如user.name
) 或者[]
(例如user['name']
) 的形式完成。 - “追踪函数” 是
computed
表达式、observer 组件的render()
方法和when
、reaction
和autorun
的第一个入参函数。 - “过程(during)” 意味着只追踪那些在函数执行时被读取的 observable 。这些值是否由追踪函数直接或间接使用并不重要。
换句话说,MobX
不会对其作出反应:
- 从
observable
获取的值,但是在追踪函数之外(比如,可观察属性是否由observable组件的render函数渲染) - 在异步调用的代码块中读取的
observable
MobX 追踪属性访问,而不是值
假设你有如下的 observable
数据结构(默认情况下 observable
会递归应用,所以本示例中的所有字段都是可观察的)。
`let message = observable({
title: "Foo",
author: {
name: "Michel"
},
likes: [
"John", "Sara"
]
})` Copy
在内存中看起来像下面这样。 绿色框表示可观察属性。 请注意,值 本身是不可观察的!
现在 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;
当改变store
中message.author.name
时,页面并不会重新渲染。因为div
实际上不是由 Father
(有追踪的渲染) 渲染的,而是 Child
。 所以要确保 Child
的 title
可以正确对新的 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;
组件会对author
和likes
做出反应,不会对title
做出反应,因为this.title = props.store?.message.title;
是赋值,而this.author = props.store?.message.author;
是赋引用,这个引用也是一个observable
对象
换成函数组件
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));
发现组件即会对author
和likes
做出反应,也会对title
做出反应
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。