在《mobx系列(二)-mobx主要概念》中解释过mobx对什么作出反应,如果对这一部分内容理解不清楚,开发中经常会遇到store中状态更新了,但是组件没有重新渲染的问题。本文简单列举几种情况及解决方法。
1、缓存observables 属性并存储在本地
这是文档中的一个示例,指的是从 observable 属性中提取数据并存储,这样的数据是不会被追踪的:
class User {
@observable name
}
class Profile extends React.Component {
name
componentWillMount() {
// 错误的
// 这会间接引用 user.name 并只拷贝值一次!未来的更新不会被追踪,因为生命周期钩子不是响应的
// 像这样的赋值会创建冗余数据
this.name = this.props.user.name
}
render() {
return <div>{this.name}</div>
}
}
这样name的变化不会触发Profile组件的更新。正确的方法通过不将 observables 的值存储在本地(显然,上面的示例很简单,但却是有意为之的):
class User {
@observable name
}
class Profile extends React.Component {
render() {
return <div>{this.props.user.name}</div>
}
}
这样name的变化不会触发Profile组件的更新。正确的方法通过不将 observables 的值存储在本地(显然,上面的示例很简单,但却是有意为之的):
class User {
@observable name
}
class Profile extends React.Component {
render() {
return <div>{this.props.user.name}</div>
}
}
或通过将其定义为计算属性:
class User {
@observable name
}
class Profile extends React.Component {
@computed get name() {
// 正确的; 计算属性会追踪 `user.name` 属性
return this.props.user.name
}
render() {
return <div>{this.name}</div>
}
}
2、将一个object传递给子组件:
有如下observable状态:
@observable testObj=[{id: '1', value: '1'}];
在组件中的使用为:
import React, {Component} from 'react';
import {Table } from 'antd';
import {inject, observer} from 'mobx-react';
@inject('configureStore')
@observer
export default class EditableTable extends Component {
constructor(props) {
super(props);
}
getColumns(){
// return columns;
}
render() {
const {testObj} = this.props.configureStore;
const columns = this.getColumns(dataSource);
return (
<Table
dataSource={testObj}
columns={columns}
/> );
}
}
EditableTable组件没有观察testObj 中的属性值,直接将testObj传递给Table组件,Table组件并不会因为testObj中属性值的改变而re-render,此时的解决方法为:
a、让子组件作为观察者,在子组件的render方法中通过
const {testObj} = this.props.configureStore;
获取并读取testObj的可观察属性,以此达到子组件可根据observable state值的改变而re-render的效果。
当然在上例中该方法并不适用,因为antd的Table组件不会成为观察者。
b、在上例中读取testObj后,父组件直接读取其中的可观察状态,比如:
const dataSource = testObj.map(item => ({...item}));
上例可修改为:
import React, {Component} from 'react';
import {Table } from 'antd';
import {inject, observer} from 'mobx-react';
@inject('configureStore')
@observer
export default class EditableTable extends Component {
constructor(props) {
super(props);
}
getColumns(){
// return columns;
}
render() {
const {testObj} = this.props.configureStore;
const dataSource = testObj.map(item => ({...item}));
const columns = this.getColumns(dataSource);
return (
<Table
dataSource={dataSource}
columns={columns}
/> );
}
}
这样可以达到让子组件根据observable state改变而re-render的目的,这种方式只是普通的React组件渲染规则,父组件根据observable state的改变而重新渲染,触发子组件重新渲染。
3、间接引用值使用错误
import React, {Component} from 'react';
import {inject, observer} from 'mobx-react';
import Timer from '../Timer';
import './style.css';
@inject( 'timeStore')
@observer
export default class User extends Component{
constructor(props){
super(props);
this.state={};
}
render(){
const {timerData} = this.props.timeStore;
return(
<div className='user'>
<Timer secondsPassed={timerData.secondsPassed} />
</div>
);
}
}
在上面例子中,Timer组件不会对secondsPassed的修改作出反应,因为mobx中值不是 observable,对象的属性才是。
所以,上面例子中,只是 secondsPassed 的当前值传递给了 Timer,这个值是不可变值 ,值永远都不会改变,所以 Timer 组件也永远不会更新。
正确的使用方式应该是Timer组件做如下修改:
import React, {Component} from 'react';
import {inject, observer} from 'mobx-react';
@inject('timeStore')
@observer
export default class Timer extends Component{
constructor(props){
super(props);
this.state = {};
}
render(){
const {timerData} = this.props.timeStore;
return(
<div className='time_content'>
<div>Seconds passed:{timerData.secondsPassed}</div>
</div>
);
}
}
这个例子说明mobx中间接引用值应该尽可能的使用。
4、观察者的父子组件re-render并没有直接联系
可能我们已经习惯了React中父组件re-render会影响子组件也跟着re-render,在mobx中,这并不是必然的,观察者组件只会对它观察的observable state作出反应,这是非常高效的,可以避免不必要的组件re-render。但在一些特殊需求面前,也是需要特殊处理的。
先看一个需求,如下图:
要求配置修改后,保存前发布和导出配置为不可操作状态,即:
简化后的代码结构为:
<TabLayout>
<ToolBar />
<Configure />
</TabLayout>
即Configure组件的状态集activeItemConfigureData修改后,ToolBar组件需要做出相应的反应,因为ToolBar是一个通用组件,只接收类似于下面的通用配置数据,
[{id: 'release', title: '发布', text: '发布', icon: 'icon_release', onClick: this.handleRelease, disabled: !hasEditPri},
{id: 'save', title: '保存', text: '保存', icon: 'icon_save', onClick: this.handleSave, disabled: !hasEditPri}]
所以能否操作的控制应该在TabLayout中做修改,所以就将activeItemConfigureData stringify之后传递给ToolBar组件,ToolBar不是观察者组件,props更改后会触发ToolBar组件的re-render。
// 将activeData stringify额外传入ToolBar组件,仅仅为了各Type组件修改值后该组件会渲染,
// 会影响效率,但是保证ToolBar组件的联动
const activeItemDataString = JSON.stringify(activeItemConfigureData);
<ToolBar
activeItemData={activeItemDataString}
/>
这种解决方法能保证实现上述需求。
5、一种不推荐的解决方案
网上可以查到一种强制re-render的方法,主要思想是添加一个比如count的计数器,每个需要强制更新的action中触发另外一个action,使count+1,在需要更新的组件中调用一下count,以此来触发组件的re-render。
上面这几个问题只是我在使用mobx开发时遇到的问题,这里记录一下解决方案。在后面使用时可能还会遇到其它问题,会持续更新,也欢迎补充。