@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 的生命周期方法,除了 componentWillUpdate
和 componentDidUpdate
。 如果你需要用到其他 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
(推荐做法)