@observer
observer
方法/装饰器可以用于将ReactJS组件转换为响应式组件。它将组件中的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);
提示:当obserer
需要和其它装饰器或者高阶组件组合时,确保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 支持
在 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
将observer
连接到 stores
mobx-react
包也提供了Provider
组件,可以用于使用React的上下文机制来传递stores。为了连接多个stores,可以传递一个store名称的数组参数给observer
,它会把这些stores变成props。当使用装饰器(@observer(["store"]) class ...
)或者observer(["store"], React.createClass({ ...
方法也是支持的。
Example:
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';
// all buttons updated
有关更多信息,请参阅 mobx-react
docs。
何时使用observer
?
这里有一个简单的原则:所有使用可观察数据渲染的组件。如果你不想把这个组件标记为可观察的,例如为了减少通用组件库的依赖,请确保你传递的只是纯粹的数据。
通过使用@observer
,我们就不需要为了渲染的目的区分 'smart' 组件和 'dump' 组件。它仍然是一个很好的分离,我们只需关注在哪里处理事件,发起请求等。当它们 自身 的依赖发生变化时,所有的组件都负责更新。它的开销是可以忽略的,它可以确保每当你使用可观察的数据时,组件将会根据它响应。更多信息请参阅 thread
observer
和PureRenderMixin
observer
也是阻止 props 浅改变时的重新渲染,这使得传递到组件的数据是具有响应性的,这是非常有意义的。这个行为和 React PureRender mixin是非常相似的,除了仍然还是要处理状态改变。如果一个组件提供了它自己的shouldComponentUpdate
,那么它的优先级更高。可以参阅这个解释 github issue
componentWillReact
(生命周期钩子)
React组件通常在一个新的堆栈上渲染,这使得它经常很难弄清楚到底是什么 导致 了组件的重新渲染。当使用mobx-react
时,你可以定义一个新的生命周期钩子,当一个组件将要计划重新渲染是,componentWillReact
(双关语)将会被触发,应为它观察到的数据已经发生了变化。这使得它很容易的追踪到什么造成了组件的重新渲染。
import {observer} from "mobx-react";
@observer class TodoView extends React.Component {
componentWillReact() {
console.log("I will re-render, since the todo has changed!");
}
render() {
return <div>this.props.todo.title</div>;
}
}
componentWillReact
没有参数componentWillReact
初始化render之前不会被触发(使用componentWillMount
取代)componentWillReact
当接收到新的 props 或代用setState
之后不会被触发(使用componentWillUpdate
替代)
优化 components
请参阅相关 章节.
MobX-React-DevTools
你可以结合@observer
使用 Mobx-React-DevTools,它可以展示出你的组件何时重新渲染,并且你还可以查看你的组件依赖哪些数据。详情请参阅 DevTools 章节.
observer 组件的特征
- Observer 仅仅订阅上次组件渲染时主动使用的数据结构。这意味着你不能 under-subscribe 或 over-subscribe,你甚至可以在渲染时使用稍后才可用的数据,这是异步加载数据的理想选择。
- 你不需要声明组件将要使用什么数据,而相反,它是在运行时才确定其依赖并进行以精细化的方式跟踪。
- 通常,响应式组件没有或有极少的状态,因为在与其它组件共享的对象中封装(视图)状态通常更方便。但你仍可以自用的使用其状态。
@observer
与PureRenderMixin
实现shouldCompoentUpdate
的方式一样,所以其子元素没有重新进行渲染的必要。- 响应式组件侧向加载数据,所以其父组件如果没必要更新,其就不会重新渲染,即使它的子组件需要重新渲染。
@observer
不依赖于 React 的上下文系统。
在你的编译器中启用 ES6 装饰器语法支持
在默认情况下,使用 TypeScript 或 Babel等待 ES 标准中的定义时,装饰器语法是不被支持的。
- 对于 typescript, 启用
--experimentalDecorators
编译标志或配置编译选项experimentalDecorators
为true
在tsconfig.json
中(推荐) - 对于 babel5, 确保把
--stage 0
传递给 Babel CLI - 对于 babel6, 请参阅此处建议的实例配置 issue