在线文档教程

@observer

@observer

egghead.io 第1课: observable & observer

observer 函数/装饰器可以用来将 React 组件转变成响应式组件。 它用 mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件。observer 是由单独的 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> ) } }; ReactDOM.render(<Timer timerData={timerData} />, document.body Copy

小贴士: 当 observer 需要组合其它装饰器或高阶组件时,请确保 observer 是最深处(第一个应用)的装饰器,否则它可能什么都不做。

注意,使用 @observer 装饰器是可选的,它和 observer(class Timer ... { }) 达到的效果是一样的。

陷阱: 组件中的间接引用值

MobX 可以做很多事,但是它无法使原始数据类型转变成可观察的(尽管它可以用对象来包装它们,参见 boxed observables)。 所以是不可观察的,但是对象的属性可以。这意味着 @observer 实际上是对间接引用(dereference)的反应。 那么在上面的示例中,如果是用下面这种方式初始化的,Timer 组件是不会有反应的:

React.render(<Timer timerData={timerData.secondsPassed} />, document.body) Copy

在这个代码片段只是把 secondsPassed 的当前值传递给了 Timer 组件,这个值是不可变值0(JS所有的原始类型值都是不可变的)。 这个数值永远都不会改变,因此 Timer 组件不会更新。secondsPassed的值将来会发生改变, 所以我们需要在组件访问它。或者换句话说: 值需要通过引用来传递而不是通过(字面量)值来传递。

ES5 支持

在ES5环境中,可以简单地使用 observer(React.createClass{ ... 来定义观察者组件。还可以参见语法指南。

无状态函数组件

上面的 Timer 组件还可以通过使用 observer 传递的无状态函数组件来编写:

import {observer} from "mobx-react"; const Timer = observer({ timerData }) => <span>Seconds passed: { timerData.secondsPassed } </span> Copy

可观察的局部组件状态

就像普通类一样,你可以通过使用 @observable 装饰器在React组件上引入可观察属性。 这意味着你可以在组件中拥有功能同样强大的本地状态(local state),而不需要通过 React 的冗长和强制性的 setState 机制来管理。 响应式状态会被 render 提取调用,但不会调用其它 React 的生命周期方法,除了 componentWillUpdatecomponentDidUpdate 。 如果你需要用到其他 React 生命周期方法 ,只需使用基于 state 的常规 React API 即可。

上面的例子还可以这样写:

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> ) } } ReactDOM.render(<Timer />, document.body) Copy

对于使用可观察的局部组件状态更多的优势,请参见为什么我不再使用 setState 的三个理由。

使用 inject 将组件连接到提供的 stores

egghead.io 第8课: 使用 Provider 注入 stores

mobx-react 包还提供了 Provider 组件,它使用了 React 的上下文(context)机制,可以用来向下传递 stores。 要连接到这些 stores,需要传递一个 stores 名称的列表给 inject,这使得 stores 可以作为组件的 props 使用。

注意: 从 mobx-react 4开始,注入 stores 的语法发生了变化,应该一直使用 inject(stores)(component) @inject(stores) class Component...。 直接传递 store 名称给 observer 的方式已废弃。

示例:

const colors = observable{ foreground: '#000', background: '#fff' } const App = () => <Provider colors={colors}> <app stuff... /> </Provider>; const Button = inject("colors")(observer({ colors, label, onClick }) => <button style={{ color: colors.foreground, backgroundColor: colors.background }} onClick={onClick} >{label}<button> ) // 稍后.. colors.foreground = 'blue'; // 所有button都会更新 Copy

更多资料,请参见 mobx-react 文档。

何时使用 observer?

简单来说: 所有渲染 observable 数据的组件。 如果你不想将组件标记为 observer,例如为了减少通用组件包的依赖,请确保只传递普通数据。

使用 @observer 的话,不再需要从渲染目的上来区分是“智能组件”还是“无脑”组件。 在组件的事件处理、发起请求等方面,它也是一个很好的分离关注点。 当所有组件它们自己的依赖项有变化时,组件自己会响应更新。 而它的计算开销是可以忽略的,并且它会确保不管何时,只要当你开始使用 observable 数据时,组件都将会响应它的变化。 更多详情,请参见 这里。

observer 和 PureComponent

如果传递给组件的数据是响应式的,observer 还可以防止当组件的 props 只是浅改变时的重新渲染,这是很有意义的。 这个行为与 React PureComponent 相似,不同在于这里的 state 的更改仍然会被处理。 如果一个组件提供了它自己的 shouldComponentUpdate,这个方法会被优先调用。 想要更详细的解释,请参见这个 github issue。

componentWillReact (生命周期钩子)

React 组件通常在新的堆栈上渲染,这使得通常很难弄清楚是什么导致组件的重新渲染。 当使用 mobx-react 时可以定义一个新的生命周期钩子函数 componentWillReact(一语双关)。当组件因为它观察的数据发生了改变,它会安排重新渲染,这个时候 componentWillReact 会被触发。这使得它很容易追溯渲染并找到导致渲染的操作(action)。

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>; } } Copy

  • componentWillReact 不接收参数

优化组件

请参见相关章节

MobX-React-DevTools

结合 @observer,可以使用 MobX-React-DevTools ,它精确地显示了何时重新渲染组件,并且可以检查组件的数据依赖关系。 详情请参见 开发者工具 。

observer 组件特性

  • Observer 仅订阅在上次渲染期间活跃使用的数据结构。这意味着你不会订阅不足(under-subscribe)或者过度订阅(over-subscribe)。你甚至可以在渲染方法中使用仅在未来时间段可用的数据。 这是异步加载数据的理想选择。

在编译器中启用装饰器

在使用 TypeScript 或 Babel 这些等待ES标准定义的编译器时,默认情况下是不支持装饰器的。

  • 对于 typescript,启用 --experimentalDecorators 编译器标识或者在 tsconfig.json 中把编译器属性 experimentalDecorators 设置为 true (推荐做法)