• @observer
    • 陷阱: 组件中的间接引用值
    • ES5 支持
    • 无状态函数组件
    • 可观察的局部组件状态
    • 使用 inject 将组件连接到提供的 stores
    • 何时使用 observer?
    • observerPureComponent
    • componentWillReact (生命周期钩子)
    • 优化组件
    • MobX-React-DevTools
    • observer 组件特性
    • 在编译器中启用装饰器

    @observer

    egghead.io 第1课: observable & observer
    在 egghead.io 上观看

    observer 函数/装饰器可以用来将 React 组件转变成响应式组件。它用 mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件。observer 是由单独的 mobx-react 包提供的。

    1. import {observer} from "mobx-react";
    2. var timerData = observable({
    3. secondsPassed: 0
    4. });
    5. setInterval(() => {
    6. timerData.secondsPassed++;
    7. }, 1000);
    8. @observer class Timer extends React.Component {
    9. render() {
    10. return (<span>Seconds passed: { this.props.timerData.secondsPassed } </span> )
    11. }
    12. };
    13. ReactDOM.render(<Timer timerData={timerData} />, document.body);

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

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

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

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

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

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

    ES5 支持

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

    无状态函数组件

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

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

    可观察的局部组件状态

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

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

    1. import {observer} from "mobx-react"
    2. import {observable} from "mobx"
    3. @observer class Timer extends React.Component {
    4. @observable secondsPassed = 0
    5. componentWillMount() {
    6. setInterval(() => {
    7. this.secondsPassed++
    8. }, 1000)
    9. }
    10. render() {
    11. return (<span>Seconds passed: { this.secondsPassed } </span> )
    12. }
    13. }
    14. ReactDOM.render(<Timer />, document.body)

    对于使用可观察的局部组件状态更多的优势,请参见为什么我不再使用 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 的方式已废弃。

    示例:

    1. const colors = observable({
    2. foreground: '#000',
    3. background: '#fff'
    4. });
    5. const App = () =>
    6. <Provider colors={colors}>
    7. <app stuff... />
    8. </Provider>;
    9. const Button = inject("colors")(observer(({ colors, label, onClick }) =>
    10. <button style={{
    11. color: colors.foreground,
    12. backgroundColor: colors.background
    13. }}
    14. onClick={onClick}
    15. >{label}</button>
    16. ));
    17. // 稍后..
    18. colors.foreground = 'blue';
    19. // 所有button都会更新

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

    何时使用 observer?

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

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

    observerPureComponent

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

    componentWillReact (生命周期钩子)

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

    1. import {observer} from "mobx-react";
    2. @observer class TodoView extends React.Component {
    3. componentWillReact() {
    4. console.log("I will re-render, since the todo has changed!");
    5. }
    6. render() {
    7. return <div>this.props.todo.title</div>;
    8. }
    9. }
    • componentWillReact 不接收参数
    • componentWillReact 初始化渲染前不会触发 (使用 componentWillMount 替代)
    • componentWillReact 对于 mobx-react@4+, 当接收新的 props 时并在 setState 调用后会触发此钩子

    优化组件

    请参见相关章节。

    MobX-React-DevTools

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

    observer 组件特性

    • Observer 仅订阅在上次渲染期间活跃使用的数据结构。这意味着你不会订阅不足(under-subscribe)或者过度订阅(over-subscribe)。你甚至可以在渲染方法中使用仅在未来时间段可用的数据。 这是异步加载数据的理想选择。
    • 你不需要声明组件将使用什么数据。相反,依赖关系在运行时会确定并以非常细粒度的方式进行跟踪。
    • 通常,响应式组件没有或很少有状态,因为在与其他组件共享的对象中封装(视图)状态通常更方便。但你仍然可以自由地使用状态。
    • @observer 以和 PureComponent 同样的方式实现了 shouldComponentUpdate,因此子组件可以避免不必要的重新渲染。
    • 响应式组件单方面加载数据,即使子组件要重新渲染,父组件也不会进行不必要地重新渲染。
    • @observer 不依赖于 React 的上下文系统。
    • mobx-react@4+ 中,observer 组件的props 对象和 state 对象都会自动地转变为 observable,这使得创建 @computed 属性更容易,@computed 属性是根据组件内部的 props 推导得到的。如果在 @observer 组件中包含 reaction(例如 autorun) 的话,当 reaction 使用的特定属性不再改变时,reaction 是不会再重新运行的,在 reaction 中使用的特定 props 一定要间接引用(例如 const myProp = props.myProp)。不然,如果你在 reaction 中引用了 props.myProp,那么 props 的任何改变都会导致 reaction 的重新运行。对于 React-Router 的典型用例,请参见这篇文章。

    在编译器中启用装饰器

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

    • 对于 typescript,启用 --experimentalDecorators 编译器标识或者在 tsconfig.json 中把编译器属性 experimentalDecorators 设置为 true (推荐做法)
    • 对于 babel5,确保把 --stage 0 传递给 Babel CLI
    • 对于 babel6,参见此 issue 中建议的示例配置。