Ответ 1
Я не уверен, откуда приходит const App = connect((state) => state)(RepoList)
.
соответствующий пример в документах React Redux имеет уведомление:
Не делай этого! Он убивает любую оптимизацию производительности, потому что TodoApp будет повторно получать после каждого действия. Лучше иметь более подробное соединение() по нескольким компонентам в иерархии вашего представления, каждое из которых послушайте соответствующий срез состояния.
Мы не предлагаем использовать этот шаблон. Скорее, каждый подключает <Repo>
специально, чтобы он считывал свои собственные данные в своем mapStateToProps
. Пример tree-view показывает, как это сделать.
Если вы сделаете форму состояния более нормированной (сейчас ее все вложенное), вы можете отделить repoIds
от reposById
, а затем иметь только RepoList
re-render, если repoIds
изменится. Таким образом, изменения в отдельных репозиториях не будут влиять на сам список, и только соответствующий Repo
получит повторную визуализацию. Этот запрос на перенос может дать вам представление о том, как это может работать. Пример real-world показывает, как вы можете писать редукторы, которые имеют дело с нормализованными данными.
Обратите внимание, что для того, чтобы действительно извлечь выгоду из производительности, предлагаемой путем нормализации дерева, вам нужно сделать то же самое, что этот запрос на перенос делает и передает mapStateToProps()
factory до connect()
:
const makeMapStateToProps = (initialState, initialOwnProps) => {
const { id } = initialOwnProps
const mapStateToProps = (state) => {
const { todos } = state
const todo = todos.byId[id]
return {
todo
}
}
return mapStateToProps
}
export default connect(
makeMapStateToProps
)(TodoItem)
Причина, по которой это важно, состоит в том, что мы знаем, что идентификаторы никогда не меняются. Использование ownProps
имеет ограничение производительности: внутренние реквизиты должны быть пересчитаны в любое время, когда внешние реквизиты меняются. Однако использование initialOwnProps
не несет этого штрафа, потому что оно используется только один раз.
Быстрая версия вашего примера будет выглядеть так:
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore} from 'redux';
import {Provider, connect} from 'react-redux';
import set from 'lodash/fp/set';
import pipe from 'lodash/fp/pipe';
import groupBy from 'lodash/fp/groupBy';
import mapValues from 'lodash/fp/mapValues';
const UPDATE_TAG = 'UPDATE_TAG';
const reposById = pipe(
groupBy('id'),
mapValues(repos => repos[0])
)(require('json!../repos.json'));
const repoIds = Object.keys(reposById);
const store = createStore((state = {repoIds, reposById}, action) => {
switch (action.type) {
case UPDATE_TAG:
return set('reposById.1.tags[0]', {id: 213, text: 'Node.js'}, state);
default:
return state;
}
});
const Repo = ({repo}) => {
const [authorName, repoName] = repo.full_name.split('/');
return (
<li className="repo-item">
<div className="repo-full-name">
<span className="repo-name">{repoName}</span>
<span className="repo-author-name"> / {authorName}</span>
</div>
<ol className="repo-tags">
{repo.tags.map((tag) => <li className="repo-tag-item" key={tag.id}>{tag.text}</li>)}
</ol>
<div className="repo-desc">{repo.description}</div>
</li>
);
}
const ConnectedRepo = connect(
(initialState, initialOwnProps) => (state) => ({
repo: state.reposById[initialOwnProps.repoId]
})
)(Repo);
const RepoList = ({repoIds}) => {
return <ol className="repos">{repoIds.map((id) => <ConnectedRepo repoId={id} key={id}/>)}</ol>;
};
const App = connect(
(state) => ({repoIds: state.repoIds})
)(RepoList);
console.time('INITIAL');
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('app')
);
console.timeEnd('INITIAL');
setTimeout(() => {
console.time('DISPATCH');
store.dispatch({
type: UPDATE_TAG
});
console.timeEnd('DISPATCH');
}, 1000);
Обратите внимание, что я изменил connect()
в ConnectedRepo
, чтобы использовать factory с initialOwnProps
, а не ownProps
. Это позволяет React Redux пропустить всю повторную оценку prop.
Я также удалил ненужный shouldComponentUpdate()
на <Repo>
, потому что React Redux позаботится о его реализации в connect()
.
Этот подход превосходит оба предыдущих подхода в моем тестировании:
one-connect.js: 43.272ms
repo-connect.js before changes: 61.781ms
repo-connect.js after changes: 19.954ms
Наконец, если вам нужно отобразить такую тонну данных, она все равно не поместится на экране. В этом случае лучшим решением является использование виртуализованной таблицы чтобы вы могли отображать тысячи строк без накладных расходов на производительность, фактически отображая их.
Я получил решение, заменив все теги наблюдаемым внутренним редуктором.
Если у него есть побочные эффекты, это не редуктор Redux. Это может сработать, но я предлагаю поставить такой код вне Redux, чтобы избежать путаницы. Редукторы Redux должны быть чистыми функциями, и они не могут называть onNext
для объектов.