Saltar al contenido

¿Cómo usar correctamente una función de selector de curry con el gancho useSelector de react-redux?

Por fin después de tanto batallar pudimos dar con el resultado de este asunto que muchos usuarios de nuestro sitio presentan. Si deseas compartir algún dato puedes dejar tu comentario.

Solución:

Cuando el valor de retorno de un selector es una función nueva, el componente siempre se volverá a renderizar en cada cambio de tienda.

useSelector() usa estricto === verificaciones de igualdad de referencia por defecto, no igualdad superficial

Puedes verificar esto con un selector súper simple:

const curriedSelector = state => () => 0;

let renders = 0;
const Component = () => 
  // Returns a new function each time
  // triggers a new render each time
  const value = useSelector(curriedSelector)();
  return `Value $value (render: $++renders)`;

Incluso si el value es siempre 0, el componente se volverá a renderizar en cada acción de la tienda, ya que useSelector no sabe que estamos llamando a la función para obtener el valor real.

Pero si nos aseguramos de que useSelector recibe la final value en lugar de la función, entonces el componente solo se renderiza en real value cambio.

const curriedSelector = state => () => 0;

let renders = 0;
const Component = () => 
  // Returns a computed value
  // triggers a new render only if the value changed
  const value = useSelector(state => curriedSelector(state)());
  return `Value $value (render: $++renders)`;


La conclusión es que funciona, pero es super ineficiente para devolver una nueva función (o cualquier nueva no primitiva) de un selector usado con useSelector cada vez que se llama.

los accesorios se pueden usar a través del cierre (vea los ejemplos a continuación) o usando un selector de curry.

La documentación significaba:

  • cierre useSelector(state => state.todos[props.id])
  • curry useSelector(state => curriedSelector(state)(props.id))

connect siempre está disponible, y si cambia un poco su selector, podría funcionar con ambos.

export const getTodoById = (state,  id ) => /* */

const Component =  props => 
  const todo = useSelector(state => getTodoById(state, props));

// or
connect(getTodoById)(Component)

Tenga en cuenta que, dado que está devolviendo un objeto de su selector, es posible que desee cambiar la verificación de igualdad predeterminada de useSelector a una verificación de igualdad superficial.

import  shallowEqual  from 'react-redux'

export function useShallowEqualSelector(selector) 
  return useSelector(selector, shallowEqual)

o solo

const todo = useSelector(state => getTodoById(state, id), shallowEqual);

Si está realizando cálculos costosos en el selector o los datos están profundamente anidados y el rendimiento se convierte en un problema, eche un vistazo a la respuesta de Olivier, que utiliza la memorización.

Aquí hay una solución, usa memoïzation para no volver a renderizar el componente en cada cambio de tienda:

Primero creo una función para hacer selectores, porque el selector depende de la propiedad del componente id, entonces quiero tener un nuevo selector por instancias de componente.

El selector evitará que el componente se vuelva a renderizar cuando el todo o la id prop no hayan cambiado.

Por ultimo uso useMemo porque no quiero tener más de un selector por instancia de componente.

Puedes ver el último ejemplo de la documentación para tener más información.

// selectors
const makeGetTodoByIdSelector = () => createSelector(
   state => state.todo.byId,
   (_, id) => id,
   (todoById, id) => (
       ...todoById[id], 
       display: getFancyDisplayName(todoById[id])
   )
);

const getFancyDisplayName = t => `$t.id: $t.title`;

// example component
const TodoComponent = () => 
  // get id from react-router in URL
  const id = match.params.id && decodeURIComponent(match.params.id);

  const getTodoByIdSelector = useMemo(makeGetTodoByIdSelector, []);

  const todo = useSelector(state => getTodoByIdSelector(state, id));

  return todo.display;

Sí, así es como se hace, ejemplo simplificado:

// Curried functions
const getStateById = state => id => state.todo.byId[id];

const getIdByState = id => state => state.todo.byId[id];

const SOME_ID = 42;

const TodoComponent = () => 
  // id from API
  const id = SOME_ID;

  // Curried
  const todoCurried = useSelector(getStateById)(id);
  const todoCurried2 = useSelector(getIdByState(id));

  // Closure
  const todoClosure = useSelector(state => state.todo.byId[id]);

  // Curried + Closure
  const todoNormal = useSelector(state => getStateById(state)(id));

  return (
    <>
      todoCurried.display
      todoCurried2.display
      todoClosure.display
      todoNormal.display
    
  );
;

Ejemplo completo:

Editar estilo-antd-react-starter

Reseñas y valoraciones del artículo

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *