优化 React 组件渲染 {🚀}
¥Optimizing React component rendering {🚀}
MobX 非常快,通常甚至比 Redux 还要快,但这里有一些充分利用 React 和 MobX 的技巧。大多数适用于一般的 React,而不是特定于 MobX。请注意,虽然了解这些模式是件好事,但即使你根本不担心它们,通常你的应用也会足够快。
¥MobX is very fast, often even faster than Redux, but here are some tips to get most out of React and MobX. Most apply to React in general and are not specific to MobX. Note that while it's good to be aware of these patterns, usually your application will be fast enough even if you don't worry about them at all.
仅当出现实际问题时才优先考虑性能!
¥Prioritize performance only when it's an actual issue!
使用许多小组件
¥Use many small components
observer
组件将跟踪它们使用的所有值,并在其中任何一个发生变化时重新渲染。因此,你的组件越小,它们必须重新渲染的更改就越小。这意味着用户界面的更多部分可以彼此独立地渲染。
¥observer
components will track all values they use and re-render if any of them changes.
So the smaller your components are, the smaller the change they have to re-render. It means that more parts of your user interface have the possibility to render independently of each other.
专用组件中的渲染列表
¥Render lists in dedicated components
在渲染大型集合时,上述情况尤其如此。众所周知,React 不擅长渲染大型集合,因为协调器必须在每次集合更改时评估集合生成的组件。因此,建议使用仅映射集合并渲染它的组件,而不渲染任何其他内容。
¥The above is especially true when rendering big collections. React is notoriously bad at rendering large collections as the reconciler has to evaluate the components produced by a collection on each collection change. It is therefore recommended to have components that just map over a collection and render it, and render nothing else.
坏的:
¥Bad:
const MyComponent = observer(({ todos, user }) => (
<div>
{user.name}
<ul>
{todos.map(todo => (
<TodoView todo={todo} key={todo.id} />
))}
</ul>
</div>
))
在上面的清单中,当 user.name
更改时,React 不需要协调所有 TodoView
组件。它们不会重新渲染,但协调过程本身就很昂贵。
¥In the above listing React will unnecessarily need to reconcile all TodoView
components when the user.name
changes. They won't re-render, but the reconcile process is expensive in itself.
好的:
¥Good:
const MyComponent = observer(({ todos, user }) => (
<div>
{user.name}
<TodosView todos={todos} />
</div>
))
const TodosView = observer(({ todos }) => (
<ul>
{todos.map(todo => (
<TodoView todo={todo} key={todo.id} />
))}
</ul>
))
不要使用数组索引作为键
¥Don't use array indexes as keys
不要使用数组索引或任何将来可能更改的值作为键。如果需要,为你的对象生成 id。看看这个 博客文章。
¥Don't use array indexes or any value that might change in the future as key. Generate ids for your objects if needed. Check out this blog post.
延迟取消引用值
¥Dereference values late
使用 mobx-react
时,建议尽可能晚地取消引用值。这是因为 MobX 将重新渲染自动取消引用可观察值的组件。如果这种情况发生在组件树的更深处,则需要重新渲染的组件就会减少。
¥When using mobx-react
it is recommended to dereference values as late as possible.
This is because MobX will re-render components that dereference observable values automatically.
If this happens deeper in your component tree, less components have to re-render.
慢点:
¥Slower:
<DisplayName name={person.name} />
快点:
¥Faster:
<DisplayName person={person} />
在较快的示例中,name
属性的更改仅触发 DisplayName
重新渲染,而在较慢的示例中,组件的所有者也必须重新渲染。这没有什么问题,如果所属组件的渲染足够快(通常是这样!),那么这种方法就可以很好地工作。
¥In the faster example, a change in the name
property triggers only DisplayName
to re-render, while in the slower one the owner of the component has to re-render as well. There is nothing wrong with that, and if rendering of the owning component is fast enough (usually it is!), then this approach works well.
函数属性 {🚀}
¥Function props {🚀}
你可能会注意到,要稍后取消引用值,你必须创建许多小型观察者组件,其中每个组件都经过自定义以渲染数据的不同部分,例如:
¥You may notice that to dereference values late, you have to create lots of small observer components where each is customized to render a different part of data, for example:
const PersonNameDisplayer = observer(({ person }) => <DisplayName name={person.name} />)
const CarNameDisplayer = observer(({ car }) => <DisplayName name={car.model} />)
const ManufacturerNameDisplayer = observer(({ car }) =>
<DisplayName name={car.manufacturer.name} />
)
如果你有大量不同形状的数据,这很快就会变得乏味。另一种方法是使用返回你希望 *Displayer
渲染的数据的函数:
¥This quickly becomes tedious if you have lots of data of different shape. An alternative is to use a function that returns the data that you want your *Displayer
to render:
const GenericNameDisplayer = observer(({ getName }) => <DisplayName name={getName()} />)
然后,你可以像这样使用该组件:
¥Then, you can use the component like this:
const MyComponent = ({ person, car }) => (
<>
<GenericNameDisplayer getName={() => person.name} />
<GenericNameDisplayer getName={() => car.model} />
<GenericNameDisplayer getName={() => car.manufacturer.name} />
</>
)
这种方法将允许 GenericNameDisplayer
在整个应用中重复使用以渲染任何名称,并且你仍然可以将组件重新渲染保持在最低限度。
¥This approach will allow GenericNameDisplayer
to be reused throughout your application to render any name, and you still keep component re-rendering
to a minimum.