script react helmet performance backbone.js mobile reactjs redux

performance - helmet - ¿Puede una aplicación React-Redux realmente escalar tan bien como, por ejemplo, Backbone? Incluso con reselect. En movil



react helmet sync (2)

Esta puede ser una respuesta más general de la que está buscando, pero en términos generales:

  1. La recomendación de los documentos de Redux es conectar los componentes React bastante altos en la jerarquía de componentes. Ver esta sección. . Esto mantiene el número de conexiones manejables, y luego puede pasar accesorios actualizados a los componentes secundarios.
  2. Parte del poder y la escalabilidad de React proviene de evitar la representación de componentes invisibles. Por ejemplo, en lugar de establecer una clase invisible en un elemento DOM, en React simplemente no representamos el componente en absoluto. La devolución de componentes que no han cambiado tampoco es un problema, ya que el proceso de diferenciación de DOM virtual optimiza las interacciones de DOM de bajo nivel.

En Redux, cada cambio en la tienda activa una notify en todos los componentes conectados. Esto hace las cosas muy simples para el desarrollador, pero ¿qué pasa si tiene una aplicación con N componentes conectados y N es muy grande?

Cada cambio en la tienda, incluso si no está relacionado con el componente, aún ejecuta un shouldComponentUpdate con una simple prueba === en las rutas de acceso shouldComponentUpdate de la tienda. Eso es rápido, ¿verdad? Claro, tal vez una vez. Pero N veces, por cada cambio? Este cambio fundamental en el diseño me hace cuestionar la verdadera escalabilidad de Redux.

Como una optimización adicional, uno puede _.debounce todas las llamadas de notify usando _.debounce . Aun así, tener N === pruebas para cada cambio de tienda y manejar otra lógica, por ejemplo, ver lógica, parece un medio para un fin.

Estoy trabajando en una aplicación híbrida web móvil de salud y bienestar social con millones de usuarios y estoy haciendo la transición de Backbone a Redux . En esta aplicación, se presenta al usuario una interfaz deslizable que le permite navegar entre diferentes pilas de vistas, similar a Snapchat, excepto que cada pila tiene una profundidad infinita. En el tipo de vista más popular, un desplazador sin fin maneja de manera eficiente la carga, el renderizado, la fijación y la separación de elementos de alimentación, como una publicación. Para un usuario comprometido, no es raro desplazarse por cientos o miles de publicaciones, luego ingresar el feed de un usuario, luego el feed de otro usuario, etc. Incluso con una gran optimización, la cantidad de componentes conectados puede ser muy grande.

Ahora, por otro lado, el diseño de Backbone permite que cada vista escuche con precisión los modelos que lo afectan, reduciendo N a una constante.

¿Me estoy perdiendo algo o Redux tiene fallas fundamentales en una aplicación grande?


Este no es un problema inherente a Redux en mi humilde opinión.

Por cierto, en lugar de intentar renderizar 100k componentes al mismo tiempo, debe intentar simularlo con una lib como react-infinite o algo similar, y solo renderizar los elementos visibles (o cercanos) de su lista. Incluso si logra renderizar y actualizar una lista de 100k, todavía no es eficiente y requiere mucha memoria. Aquí hay algunos consejos de LinkedIn

Esta respuesta considerará que todavía intentas representar 100k elementos actualizables en tu DOM, y que no quieres que se store.subscribe() 100k oyentes ( store.subscribe() ) en cada cambio.

2 escuelas

Al desarrollar una aplicación de interfaz de usuario de una manera funcional, básicamente tiene 2 opciones:

Siempre render desde la parte superior

Funciona bien pero implica más repetitivo. No es exactamente la forma sugerida de Redux, pero se puede lograr, con algunos drawbacks . Tenga en cuenta que incluso si logra tener una única conexión redux, todavía tiene que llamar a una gran cantidad de shouldComponentUpdate en muchos lugares. Si tiene una pila infinita de vistas (como una recursión), también tendrá que representar como dom virtual todas las vistas intermedias y shouldComponentUpdate llamarse shouldComponentUpdate en muchas de ellas. Por lo tanto, esto no es realmente más eficiente, incluso si tiene una sola conexión.

Si no planea usar los métodos React lifecycle pero solo usa funciones de render puro, entonces probablemente debería considerar otras opciones similares que solo se centrarán en ese trabajo, como deku (que se puede usar con Redux)

En mi propia experiencia, hacerlo con React no es lo suficientemente eficaz en dispositivos móviles más antiguos (como mi Nexus4), particularmente si vincula entradas de texto a su estado atómico.

Conectar datos a componentes secundarios

Esto es lo que react-redux sugiere al usar connect . Entonces, cuando el estado cambia y solo está relacionado con un elemento secundario más profundo, solo representa ese elemento secundario y no tiene que representar componentes de nivel superior cada vez como los proveedores de contexto (redux / intl / custom ...) ni el diseño principal de la aplicación. También evita llamar a shouldComponentUpdate en otros niños porque ya está shouldComponentUpdate en el oyente. Llamar a muchos oyentes muy rápidos es probablemente más rápido que renderizar cada vez que los componentes de reacción intermedios, y también permite reducir una gran cantidad de accesorios que pasan, por lo que para mí tiene sentido cuando se usa con React.

También tenga en cuenta que la comparación de identidad es muy rápida y puede hacer muchas de ellas fácilmente en cada cambio. Recuerde la comprobación sucia de Angular: ¡algunas personas lograron construir aplicaciones reales con eso! Y la comparación de identidad es mucho más rápida.

Entendiendo tu problema

No estoy seguro de entender todo su problema perfectamente, pero entiendo que tiene vistas con 100k elementos similares y se pregunta si debería usar connect con todos esos 100k items porque llamar a 100k oyentes en cada cambio parece costoso.

Este problema parece inherente a la naturaleza de la programación funcional con la interfaz de usuario: la lista se actualizó, por lo que debe volver a procesar la lista, pero desafortunadamente es una lista muy larga y parece ineficiente ... Con Backbone podría hackear algo que solo rinde al niño. Incluso si representa a ese hijo con React, activaría la representación de forma imperativa en lugar de declarar "cuando la lista cambie, vuelva a representarla".

Resolviendo tu problema

Obviamente, la conexión de los elementos de la lista de 100k parece conveniente, pero no es eficaz debido a que llama a los oyentes reactk-redux de 100k, incluso si son rápidos.

Ahora, si conecta la gran lista de 100k elementos en lugar de cada elemento individualmente, solo llama a un único oyente react-redux, y luego tiene que representar esa lista de manera eficiente.

Solución ingenua

Iterando sobre los 100k elementos para renderizarlos, lo que lleva a 99999 elementos que devuelven false en shouldComponentUpdate y un solo renderizado:

list.map(item => this.renderItem(item))

Solución de rendimiento 1: connect personalizada + potenciador de tienda

El método de connect de React-Redux es solo un Componente de orden superior (HOC) que inyecta los datos en el componente envuelto. Para hacerlo, registra un store.subscribe(...) para cada componente conectado.

Si desea conectar 100k elementos de una sola lista, es una ruta crítica de su aplicación que vale la pena optimizar. En lugar de utilizar la connect predeterminada, puede crear la suya propia.

  1. Potenciador de la tienda

Exponer un método adicional store.subscribeItem(itemId,listener)

Envíe el dispatch modo que siempre que se envíe una acción relacionada con un elemento, llame a los oyentes registrados de ese elemento.

Una buena fuente de inspiración para esta implementación puede ser redux-batched-subscribe .

  1. Conexión personalizada

Cree un componente de orden superior con una API como:

Item = connectItem(Item)

El HOC puede esperar una propiedad itemId . Puede usar la tienda mejorada de Redux desde el contexto React y luego registrar su escucha: store.subscribeItem(itemId,callback) . El código fuente de la connect original puede servir como inspiración base.

  1. El HOC solo activará una nueva representación si el elemento cambia

Respuesta relacionada: https://.com/a/34991164/82609

Problema relacionado con react-redux: https://github.com/rackt/react-redux/issues/269

Solución de rendimiento 2: escuchar eventos dentro de componentes secundarios

También puede ser posible escuchar las acciones de Redux directamente en los componentes, usando redux-dispatch-subscribe o algo similar, de modo que después de la primera representación de la lista, escuche las actualizaciones directamente en el componente del elemento y anule los datos originales de la lista principal.

class MyItemComponent extends Component { state = { itemUpdated: undefined, // Will store the local }; componentDidMount() { this.unsubscribe = this.props.store.addDispatchListener(action => { const isItemUpdate = action.type === "MY_ITEM_UPDATED" && action.payload.item.id === this.props.itemId; if (isItemUpdate) { this.setState({itemUpdated: action.payload.item}) } }) } componentWillUnmount() { this.unsubscribe(); } render() { // Initially use the data provided by the parent, but once it''s updated by some event, use the updated data const item = this.state.itemUpdated || this.props.item; return ( <div> {...} </div> ); } }

En este caso, redux-dispatch-subscribe puede no ser muy eficiente ya que aún crearía 100k suscripciones. Prefiere crear su propio middleware optimizado, similar a redux-dispatch-subscribe con una API como store.listenForItemChanges(itemId) , almacenando los oyentes de elementos como un mapa para la búsqueda rápida de los oyentes correctos para ejecutar ...

Solución de rendimiento 3: intentos de vectores

Un enfoque más eficaz consideraría usar una estructura de datos persistente como un vector trie :

Si representa su lista de 100k elementos como un trie, cada nodo intermedio tiene la posibilidad de cortocircuitar el renderizado antes, lo que permite evitar una gran cantidad de shouldComponentUpdate en niños.

Esta técnica se puede usar con ImmutableJS y puede encontrar algunos experimentos que hice con ImmutableJS: Reaccionar el rendimiento: renderizar una lista grande con PureRenderMixin Sin embargo, tiene inconvenientes ya que las bibliotecas como ImmutableJs aún no exponen API públicas / estables para hacer eso ( issue ), y mi solución contamina el DOM con algunos nodos <span> intermedios inútiles ( issue ).

Aquí hay un JsFiddle que demuestra cómo una lista ImmutableJS de 100k elementos se puede representar de manera eficiente. La representación inicial es bastante larga (¡pero supongo que no inicializa su aplicación con 100k elementos!), Pero después de que puede notar que cada actualización solo shouldComponentUpdate una pequeña cantidad de shouldComponentUpdate . En mi ejemplo, solo actualizo el primer elemento cada segundo, y usted nota que incluso si la lista tiene 100k elementos, solo requiere algo así como 110 llamadas a shouldComponentUpdate cual es mucho más aceptable! :)

Editar : parece que ImmutableJS no es tan bueno para preservar su estructura inmutable en algunas operaciones, como insertar / eliminar elementos en un índice aleatorio. Aquí hay un JsFiddle que demuestra el rendimiento que puede esperar de acuerdo con la operación en la lista. Sorprendentemente, si desea agregar muchos elementos al final de una lista grande, llamar a list.push(value) muchas veces parece preservar mucho más la estructura de árbol que llamar a list.concat(values) .

Por cierto, está documentado que la Lista es eficiente al modificar los bordes. No creo que estos malos resultados al agregar / eliminar en un índice dado estén relacionados con mi técnica, sino más bien con la implementación subyacente de la Lista ImmutableJs.

Las listas implementan Deque, con adiciones y eliminaciones eficientes tanto desde el final (push, pop) como desde el comienzo (unshift, shift).