observer
函数 / 装饰器可以将React组件转换为可响应MobX的组件。
它将components的render函数使用 MobX.autorun
包裹,以确保任何依赖数据的更新都会触发重渲染。
它是通过独立的 mobx-react
包提供的。
import {observer} from "mobx-react";
var timerData = observable({
secondsPassed: 0
});
setInterval(() => {
timerData.secondsPassed++;
}, 1000);
@observer class Timer extends React.Component {
render() {
return (<span>Seconds passed: { this.props.timerData.secondsPassed } </span> )
}
});
React.render(<Timer timerData={timerData} />, document.body);
提示:当observer
需要与其他装饰器组合使用时,请确保是最里层(第一个被应用)的装饰器,否则不会有任何效果。
注意@observer
装饰器是可选的,使用 observer(class Timer ... { })
具有同样的效果。
MobX 可以做很多事,但是它不能使原始数据可观察(虽然可以把它们包含在一个对象中,见 boxed observables)。所以它观察的不是对象中的 值,而是 属性。这意味着 observer
实际上响应了你解引用值的事实。所以我们在上面的例子中,如果我们如下初始化,则 Timer
组件 不会 做出响应:
React.render(<Timer timerData={timerData.secondsPassed} />, document.body)
在这段代码里,我们只是把 secondsPassed
的当前值传递给 Timer
,这是不可变得值 0
(JS中所有的原始数据都是不可变的)。这个数字在将来不会再发生改变,所以 Timer
将永远不会更新。属性 secondsPassed
将来会发生改变,所以我们需要在组件 中 访问它。换句话说:值需要通过 引用 传递 而非 值 传递。
在 ES5 环境下,observer 组件可以简单的通过使用 observer(React.createClass({ ...
声明。另请参考 syntax guide
上面的 Timer 组件也可以通过给 observer
传递无状态函数进行编写:
import {observer} from "mobx-react";
const Timer = observer(({ timerData }) =>
<span>Seconds passed: { timerData.secondsPassed } </span>
);
就像正常的类一样,你可以在组件中使用 @observable
装饰器引入可观察的属性。这意味着你可以有组件自己的本地状态,而且不需要React的冗余和强制的 setState
机制来管理它,它是非常强大的。响应状态将由 render
接收,而不会显式的调用React的生命周期方法如 componentShouldUpdate
或 componentWillUpdate
。如果你需要她们,只需正常使用React基于 state
的APIs即可。
上面的例子也可以写成:
import {observer} from "mobx-react"
import {observable} from "MobX"
@observer class Timer extends React.Component {
@observable secondsPassed = 0
componentWillMount() {
setInterval(() => {
this.secondsPassed++
}, 1000)
}
render() {
return (<span>Seconds passed: { this.secondsPassed } </span> )
}
})
React.render(<Timer />, document.body)
对于使用可被观察的组件局部状态有很多优点,具体详见 我们为什么要停止使用setState
mobx-react
包也提供了 Provider
组件,可以用于使用React的上下文机制来传递stores。为了连接多个stores,可以传递一个store名称的数组参数给 observer
,它会把这些stores变成props。当使用装饰器(@observer(["store"]) class ...
)或者 observer(["store"], React.createClass({ ...
方法也是支持的。
例如:
const colors = observable({
foreground: '#000',
background: '#fff'
});
const App = () =>
<Provider colors={colors}>
<app stuff... />
</Provider>;
const Button = observer(["colors"], ({ colors, label, onClick }) =>
<button style={{
color: colors.foreground,
backgroundColor: colors.background
}}
onClick={onClick}
>{label}<button>
);
// later..
colors.foreground = 'blue';
// 所有按钮都更新了
更多信息请查阅 mobx-react
文档.
这里有一个简单的原则:所有使用可观察数据渲染的组件。如果你不想把这个组件标记为观察者,例如为了减少通用组件库的依赖,请确保你传递的只是纯粹的数据。
通过使用 @observer
,我们就不需要为了渲染的目的区分智能(smart)组件和木偶(dump)组件。它仍然是一个很好的分离,我们只需关注在哪里处理事件,发起请求等。当它们 自身 的依赖发生变化时,所有的组件都负责更新。它的开销是可以忽略的,它可以确保每当你使用可观察的数据时,组件将会根据它响应。更多信息请参阅 thread
observer
阻止 props 浅改变时的重新渲染,这使得传递到组件的数据是具有响应性的,这是非常有意义的。这个行为和 React PureRender mixin 是非常相似的,除了仍然需要处理状态改变。如果一个组件提供了它自己的 shouldComponentUpdate
函数,那么这个函数的优先级更高。可以参阅这个解释 github issue
React组件通常在一个新的堆栈上渲染,这使得它经常很难弄清楚到底是什么 导致 了组件的重新渲染。当使用mobx-react
时,你可以定义一个新的生命周期钩子,当一个组件准备重新渲染时,componentWillReact
(双关语)将会被触发,因为它观察到的数据已经发生了变化。这使得它很容易的追踪到什么造成了组件的重新渲染。
import {observer} from "mobx-react";
@observer class TodoView extends React.Component {
componentWillReact() {
console.log("我会重渲染, 当todo改变的时候!");
}
render() {
return <div>this.props.todo.title</div>;
}
}
componentWillReact
没有参数componentWillReact
初始化 render 之前不会被触发(可使用componentWillMount
取代)componentWillReact
当接收到新的 props 或调用setState
之后不会被触发(可使用componentWillUpdate
替代)
请参阅相关 章节.
- observer 组件仅仅订阅上次渲染时主动使用的数据结构。这意味着你不能 under-subscribe 或 over-subscribe,你甚至可以在渲染时使用稍后才可用的数据,这是异步加载数据的理想选择。
- 你不需要声明组件将要使用什么数据,而相反,它是在运行时才确定其依赖并进行以精细化的方式跟踪。
- 通常,响应式组件没有或有极少的状态,因为在与其它组件共享的对象中封装(视图)状态通常更方便。但你仍可以自由地使用其状态。
@observer
与PureRenderMixin
实现shouldCompoentUpdate
的方式一样,所以其子元素没有重新进行渲染的必要。- 响应式组件侧向加载数据,所以其父组件如果没必要更新,其就不会重新渲染,即使它的子组件需要重新渲染。
@observer
不依赖于 React 的上下文系统。
装饰器语法默认是不被支持的。
- 如果你使用的是 typescript ,请开启
--experimentalDecorators
这一构建工具标记,并在tsconfig.json
中将experimentalDecorators
设置为true
(推荐)。 - 如果你使用的是 babel5 , 请确保
--stage 0
传给了Babel CLI - 如果你使用的是 babel6 ,见下面这个配置例子