记mobx的一个问题
Realwate opened this issue · 4 comments
问题
以下代码可在这里查看。
const store = observable({
name: 'My Name',
});
// 假设Container 是第三方组件
function Container(props){
return <div>
<h1> Container </h1>
{props.render()}
</div>
}
// App.js
@observer
class App extends React.Component {
renderSome() {
return <div>
{ store.name }
</div>
}
@action
changeName(){
// 用于修改mobx store的action
store.name = Date.now();
}
render() {
return <div>
<Container render={this.renderSome} />
<button onClick={this.changeName}> Change </button>
</div>
};
}
App.js
是我们的业务代码,它会调用一个第三方组件Container
,Container
接受一个render props
用于自定义渲染内容,render props
中会访问mobx
的store
。
Change
按钮用于修改store.name
,但是点击却 不会重新渲染组件 。
原因
使用了observer
装饰的组件,它的render
会被包装,在 render
函数执行时 根据它访问的observable
数据,记录下依赖。一旦依赖的observable
变化,就会调用forceUpdate
更新组件。
但是在父子组件情况下,React
的生命周期相关函数执行顺序如下:
/**
* ------------------ The Life-Cycle of a Composite Component ------------------
*
* - constructor: Initialization of state. The instance is now retained.
* - componentWillMount
* - render
* - [children's constructors]
* - [children's componentWillMount and render]
* - [children's componentDidMount]
* - componentDidMount
*
* Update Phases:
* - componentWillReceiveProps (only called if parent updated)
* - shouldComponentUpdate
* - componentWillUpdate
* - render
* - [children's constructors or receive props phases]
* - componentDidUpdate
*
* - componentWillUnmount
* - [children's componentWillUnmount]
* - [children destroyed]
* - (destroyed): The instance is now blank, released by React and ready for GC.
*
* -----------------------------------------------------------------------------
*/
App
组件是被observer
装饰过的,由以上顺序可知,App
组件的render
执行时 不会调用到Container
的render
,因此renderSome
中访问store.name
,实际上不会被App
组件收集到依赖。
解决
错误的做法(可略过)
一开始我是这样做的,将App
的render
略作改动。
// App.js
@observer
class App extends React.Component {
/* ** */
render() {
// 获取一次name 触发依赖收集
let name = store.name;
return <div>
<Container render={this.renderSome} />
<button onClick={this.changeName}> Change </button>
</div>
};
}
但是并没有彻底解决问题,仍然有以下不足
- 『可读性』。
App render
中并没有使用name
,后面阅读代码的人会产生疑惑。 - 『代码规范』。如果使用了
ESlint
,name
属于no used value
,fix
时可能被干掉。 - 『性能』。依赖
name
的其实是Container
组件(内部的一部分),这种做法使得只要name
变化就会重新render
整个App
,如果App
组件树稍微复杂,就会带来很多不必要的render
。
最佳方案
其实官方文档早已对这种情况做出了说明(多读文档的重要性)。直接看解决方法,代码在这里。
// App.js
import { observer,Observer } from "mobx-react";
// 0.
// Container = observer(Container);
@observer
class App extends React.Component {
renderSome() {
// 修改renderSome。以下两种写法都可以
// 1. 使用oberver函数包装
const SomeComponent = observer(() =>
<div>
{ store.name }
</div>)
return <SomeComponent/>
// 2. Oberver组件形式,接受一个render props
/*
return <Observer>
{
()=> <div>
{ store.name }
</div>
}
</Observer>
*/
}
}
以上三种方案,第0种直接用observer
装饰Container
,可行,但是数据变化的同时,也会重新render
整个Container
组件。
第1,2种是官方推荐的方式,将我们访问Observable
数据的地方视为一个组件(函数), 使用observer
装饰即可。如果不想额外创建组件,可以使用Observer
组件形式。
这两种方案使得store.name
变化时,只会render
相关的这一个小组件,不会产生多余的render
,是最优解。
有 React的生命周期相关函数执行顺序 的引用地址么,想看更多
66666,提个建议,这个名称可以改一下“记mobx的一个问题”这个标题感觉不是很清晰
补充一个资料: mobx何时作出响应