优化建议

MobX 和 React 结合使用时,有一些常用的优化技巧可以帮助提高组件的渲染效率。

尽可能多地使用小组件

observer/@observer 包装的组件会追踪 render 方法中使用的所有可观测对象,所以组件越小,组件追踪的对象越少,引起组件重新渲染的可能性也越小。

在单独的组件中渲染列表数据

列表数据的渲染是比较耗费性能的,尤其是在列表数据量大的情况下,例如:

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 的改变会导致重新创建一个 TodoView 元素的数组,虽然这并不会导致重复渲染这些 TodoView,但 React 比较新旧 TodoView 元素的过程本身也很耗费性能。所以,更好的写法是:

class MyComponent extends Component {
    render() {
        const { todos, user } = this.props;
        return (
            <div>
                {user.name}
                <TodosView todos={todos} />
            </div>
        );
    }
}

class TodosView extends Component {
    render() {
        const { todos } = this.props;
        return <ul>{todos.map(todo => <TodoView todo={todo} key={todo.id} />)}</ul>;
    }
}

尽可能晚地解引用(dereference)对象属性

MobX 通过追踪对象属性的访问来追踪值的变化,所以在层级越低的组件中解引用对象属性,由这个属性的变化导致的重新渲染的组件的数量就越少。(只有解引用对象属性的组件及其子组件会重新渲染)。例如:

// 方式1

class DisplayName extends Component {
    render() {
        const {person} = this.props
        return <div>{person.name}</div>
    }
}

class MyComponent extends Component {
    render() {
        const {person} = this.props
        return <DisplayName person={person} />
    }
}

// 方式2
@observer
class DisplayName extends Component {
    render() {
        const {name} = this.props
        return <div>{name}</div>
    }
}

class MyComponent extends Component {
    render() {
        const {person} = this.props
        return <DisplayName name={person.name} />
    }
}

person 是一个可观测对象,对于方式 1,当 person 的属性 name 发生变化时,DisplayName 会自动重新渲染,而不需要通过父组件 MyComponent 的重新渲染来触发。对于方式 2,DisplayName 使用的是 name 这个值,是不可观测的,因此,要想让 DisplayName 重新渲染,首先必须让 DisplayName 的父组件 MyComponent 重新渲染,这样就导致更多组件会发生重复渲染。

但方式 2 更容易理解,对于 DisplayName 组件仅需要接收 name 作为属性就足够了,接收 person 作为属性反而有些多余。为了兼顾效率和可读性,可以这样实现:

const PersonNameDisplayer = observer(({props}) => <DisplayName name={props.person.name} />)

这里新增了一个组件 PersonNameDisplayer,由这个组件负责渲染 DisplayName,PersonNameDisplayer 会自动响应 name 的变化,重新渲染 DisplayName 组件,同时 DisplayName 组件仍然只需要接收 name 属性即可。本质上还是使用小组件的思路进行优化。

提前绑定函数

例如:

@observer
class MyComponent extends Component {
    render() {
        return <MyWidget onClick={() => { alert('hi')}} />
    }
}

这种写法,MyComponent 的 render 方法每次被调用时,MyWidget 的 onClick 属性的值都是一个新的函数,导致 MyWidget 的 render 方法一定会被重新调用,而无论其他属性是否发生变化,MobX 对组件渲染做的优化工作都会浪费。更好的写法是:

@observer
class MyComponent extends Component {
    handleClick = () => {
        alert('hi')
    }

    render() {
        return <MyWidget onClick={this.handleClick} />
    }
}

本节介绍的这几种优化方法虽然看似很基础,但读者真正掌握并理解这些优化方法后,相信会对 MobX 有更加深入的理解。