usar react para método medium funciona estado create componentes como app javascript reactjs redux react-redux

javascript - método - ¿Cómo optimizar las pequeñas actualizaciones para los accesorios del componente anidado en React+Redux?



react router (1)

Código de ejemplo: https://github.com/d6u/example-redux-update-nested-props/blob/master/one-connect/index.js

Ver demo en vivo: http://d6u.github.io/example-redux-update-nested-props/one-connect.html

¿Cómo optimizar las pequeñas actualizaciones para los accesorios del componente anidado?

Tengo los componentes anteriores, Repo y RepoList. Quiero actualizar la etiqueta del primer repositorio ( Línea 14 ). Así que UPDATE_TAG una acción UPDATE_TAG . Antes de implementar shouldComponentUpdate , el despacho tarda unos 200 ms, lo cual se espera, ya que estamos desperdiciando mucho tiempo difiriendo <Repo/> s que no han cambiado.

Después de agregar shouldComponentUpdate , el despacho tarda unos 30 ms. Después de la creación de producción de React.js, las actualizaciones solo cuestan alrededor de 17 ms. Esto es mucho mejor, pero la vista de línea de tiempo en la consola de desarrollo de Chrome todavía indica un marco de jank (más de 16.6ms).

Imagine que si tenemos muchas actualizaciones como esta, o <Repo/> es más complicado que el actual, no podremos mantener 60fps.

Mi pregunta es, para tales pequeñas actualizaciones a los accesorios de un componente anidado, ¿hay una manera más eficiente y canónica de actualizar el contenido? ¿Puedo seguir usando Redux?

Obtuve una solución al reemplazar cada tags con un reductor interior observable. Algo como

// inside reducer when handling UPDATE_TAG action // repos[0].tags of state is already replaced with a Rx.BehaviorSubject get(''repos[0].tags'', state).onNext([{ id: 213, text: ''Node.js'' }]);

Luego me suscribo a sus valores dentro del componente Repo usando https://github.com/jayphelps/react-observable-subscribe . Esto funcionó muy bien. Cada envío solo cuesta 5 ms, incluso con la compilación de desarrollo de React.js. Pero siento que esto es un antipatrón en Redux.

Actualización 1

Seguí la recomendación en la respuesta de Dan Abramov y normalicé mi estado y actualicé los componentes de conexión

La nueva forma de estado es:

{ repoIds: [''1'', ''2'', ''3'', ...], reposById: { ''1'': {...}, ''2'': {...} } }

ReactDOM.render alrededor de ReactDOM.render para ReactDOM.render la representación inicial .

Sin embargo, el rendimiento es peor que antes (tanto la representación inicial como la actualización). (Fuente: https://github.com/d6u/example-redux-update-nested-props/blob/master/repo-connect/index.js , demostración en vivo: http://d6u.github.io/example-redux-update-nested-props/repo-connect.html )

// With dev build INITIAL: 520.208ms DISPATCH: 40.782ms // With prod build INITIAL: 138.872ms DISPATCH: 23.054ms

Creo que conectarse en cada <Repo/> tiene muchos gastos generales.

Actualización 2

Basándonos en la respuesta actualizada de Dan, tenemos que devolver los argumentos de mapStateToProps devolver una función. Puedes ver la respuesta de Dan. También actualicé las demostraciones .

A continuación, el rendimiento es mucho mejor en mi computadora. Y solo por diversión, también agregué el efecto secundario en el enfoque de reducción que hablé ( source , demo ) (en serio, no lo use, es solo para experimentos ).

// in prod build (not average, very small sample) // one connect at root INITIAL: 83.789ms DISPATCH: 17.332ms // connect at every <Repo/> INITIAL: 126.557ms DISPATCH: 22.573ms // connect at every <Repo/> with memorization INITIAL: 125.115ms DISPATCH: 9.784ms // observables + side effect in reducers (don''t use!) INITIAL: 163.923ms DISPATCH: 4.383ms

Actualización 3

Acaba de agregar un ejemplo de reacción virtualizada basado en "conectar en cada uno con la memorización"

INITIAL: 31.878ms DISPATCH: 4.549ms


No estoy seguro de dónde const App = connect((state) => state)(RepoList) .
El ejemplo correspondiente en los documentos de React Redux tiene un aviso :

No hagas esto! Elimina cualquier optimización de rendimiento porque TodoApp se reintegrará después de cada acción. Es mejor tener connect (gran) más granular en varios componentes en su jerarquía de vista que cada solo escuche un sector relevante del estado.

No sugerimos usar este patrón. Por el contrario, cada uno conecta <Repo> específicamente para que lea sus propios datos en su mapStateToProps . El ejemplo " tree-view " muestra cómo hacerlo.

Si hace que la forma del estado esté más normalized (en este momento está anidada), puede separar los repoIds de reposById y luego solo volver a hacer su repoIds si los repoIds cambian. De esta forma, los cambios en los repos individuales no afectarán a la lista en sí, y solo se volverá a Repo el Repo correspondiente. Esta solicitud de extracción podría darle una idea de cómo podría funcionar. El ejemplo del " real-world " muestra cómo puede escribir reductores que se ocupen de datos normalizados.

Tenga en cuenta que para beneficiarse realmente del rendimiento que ofrece la normalización del árbol, debe hacer exactamente lo mismo que esta solicitud de extracción y pasar una fábrica de mapStateToProps() para 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)

La razón por la cual esto es importante es porque sabemos que las identificaciones nunca cambian. Usar ownProps viene con una penalización de rendimiento: los accesorios internos deben recalcularse cada vez que cambian los accesorios externos. Sin embargo, el uso de initialOwnProps no implica esta penalización porque solo se usa una vez.

Una versión rápida de tu ejemplo se vería así:

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);

Tenga en cuenta que he cambiado connect() en ConnectedRepo para usar una fábrica con initialOwnProps lugar de ownProps . Esto permite a React Redux omitir toda la reevaluación de prop.

También shouldComponentUpdate() el innecesario shouldComponentUpdate() en el <Repo> porque React Redux se encarga de implementarlo en connect() .

Este enfoque supera a ambos enfoques anteriores en mi prueba:

one-connect.js: 43.272ms repo-connect.js before changes: 61.781ms repo-connect.js after changes: 19.954ms

Finalmente, si necesita mostrar una gran cantidad de datos, de todos modos no cabe en la pantalla. En este caso, una mejor solución es utilizar una tabla virtualizada para que pueda renderizar miles de filas sin la sobrecarga de rendimiento de mostrarlas realmente.

Obtuve una solución al reemplazar cada etiqueta con un reductor interior observable.

Si tiene efectos secundarios, no es un reductor Redux. Puede funcionar, pero sugiero poner un código como este fuera de Redux para evitar confusiones. Los reductores de Redux deben ser funciones puras, y es posible que no llamen a onNext en temas.