JNUfeatherwit / blog

6 stars 0 forks source link

Mobx总结 #10

Open JNUfeatherwit opened 5 years ago

JNUfeatherwit commented 5 years ago

1、observable vs @observable

先来看下在class里两种定义observable对象的方式:

myObservableObject = observable({a:'1'})
@observable
myObservableObject = {a:'1'}

这两种有什么区别呢?我们尝试一下改变myObservableObject的a属性

myObservableObject.a='2'

运行之后结果是两种定义方式都会触发render 再来,我们这次改变一下myObservableObject引用的对象

myObservableObject={b:'1'}

结果是什么样的呢?只有第二种写法,即用了装饰符定义的对象,会触发render,这是为什么呢?因为

@observable不仅会将observable对象里的所有属性值变为可观察的,还会在class的实例将我们定义的当前对象变为可观察的

而observable({...})只会将属性值变为可观察

2、传递给子组件的props尽量延迟解构

使用 mobx-react 时,推荐尽可能晚的使用间接引用值。 这是因为当使用 observable 间接引用值时 MobX 会自动重新渲染组件。 如果间接引用值发生在组件树的层级越深,那么需要重新渲染的组件就越少。

快的:

<DisplayName person={person} />

慢的:

<DisplayName name={person.name} />.

后者并没有什么错,但是当 name 属性变化时,第一种情况只会触发 DisplayName 组件重新渲染,而第二种情况组件的拥有者需要重新渲染.

3、在专用组件中渲染列表

这点在渲染大型数据集合时尤为重要。 React 在渲染大型数据集合时表现非常糟糕,因为协调器必须评估每个集合变化的集合所产生的组件。 因此,建议使用专门的组件来映射集合并渲染这个组件,且不再渲染其他组件:

不好的:

@observer class MyComponent extends Component {
    render() {
        const {todos, user} = this.props;
        return (<div>
            {user.name}
            <ul>
                {todos.map(todo => <TodoView todo={todo} key={todo.id} />)}
            </ul>
        </div>)
    }
}

在上面的示例中,当 user.name 改变时,React 会不必要地协调所有的 TodoView 组件。尽管TodoView 组件不会重新渲染,但是协调的过程本身是非常昂贵的。

好的:

@observer class MyComponent extends Component {
    render() {
        const {todos, user} = this.props;
        return (<div>
            {user.name}
            <TodosView todos={todos} />
        </div>)
    }
}
@observer class TodosView extends Component {
    render() {
        const {todos} = this.props;
        return <ul>
            {todos.map(todo => <TodoView todo={todo} key={todo.id} />)}
        </ul>)
    }
}

4、MobX 只会为数据是直接通过 render 存取的 observer 组件进行数据追踪

比如:

class MyCompoent extend React.Component{
   @observable text=''
    render(){
       return <FlatList data={this.data} ListHeaderComponent={()=><Text>{this.text}</Text>}/>
    }
}

起初看上去是没什么问题,但是结果是text的改变不会引起重新渲染,因为Text组件不是由MyComponent渲染的,而是由Flatlist渲染的.所以应该确保Flatlist也是一个observer组件,但是Flatlist是来源于外部库,所以可以用到mobx-react 内置的一个组件,Observer 组件不接受参数,只需要单个的 render 函数作为子节点

class MyCompoent extend React.Component{
   @observable text=''
    render(){
       return <FlatList data={this.data}  ListHeaderComponent={()=><Observer>()=><Text>{this.text}</Text></Observer>}/>
    }
}

或者直接

renderFooter={<Text>{this.text}</Text>}

这两种方式都可以保证目标组件是在当前组件渲染。而如果使用函数的形式同时组件没包裹在里,这样由于函数是由Flatlist在本身的组件里调用,函数返回的目标组件相当于是在Flatlist里渲染,而因为Flatlist不是observer组件(,所以不响应observable值的变化,同理,renderItem,ListFooterComponent也是如此

5、不要吝啬使用@observer

记住,几乎所有子组件都可以加上@observer,不仅不会对性能有影响,还会减少组件的渲染(类似pureCompoent的效果),不然可能会遇到各种坑,比如:

@observer
 class Parent extends Component{
   @observable data=[]
  render(){ return
   <View>
       <Button onPress={()=>{this.data.push('a')}}></Button>
       <A data={this.data}/>
   </View>
  }
}
class A extends Compoent{
    render(){
    return <Text>{this.props.data}</Text> 
    }
}

点击一下button,会发生什么?你以为A组件会重新render?不,太天真了,Parent和A都不会render,因为Parent只监测data引用的改变,而data的引用并没有改变,所以如果换成

this.data=['a']

parent组件就会重新渲染,子组件自然也跟着重新渲染了(注意,只要父组件重新渲染,子组件都会通过shouldUpdateComponent()去判断是否需要渲染),data内部的数组元素的是由A监测,但是A组件不是observer组件所以不会响应变化,必须给A组件加上@observer之后才可以响应变化。

类似< Flatlist /> 这种无法给子组件加@observer的外部库,可以使用data=[this.data.slice()]这种方式

6、使用可深层次的检测结构变化的computed.struct

computed是采用浅比较的方式判断是否响应更新,所以返回值是数组、对象这样的结构,可以使用computed.struct进行深比较来减少更新次数