Selektory
Selektory to funkcje, które otrzymują cały obiekt stanu (lub obiekt stanu konkretnego reducera) i zwracają sekcję, która interesuje dany komponent. Mechanizm ten pozwala na umieszczenie logiki w jednym miejscu i łatwiejszy refactoring, zmniejsza poziom skomplikowania mapStateToProps
oraz pozwala na wprowadzenie dodatkowej optymalizacji renderowania komponentów.
Selektory nie są mechanizmem oferowanym przez Redux czy react-redux - są to zwykłe funkcje.
Używanie selektorów w celu uproszczenia kodu
Zakładając następujący stan Reduxa:
{
todos: [
{
id: 1,
name: "Zapoznać się z kursem React",
status: "done"
},
{
id: 2,
name: "Zapoznać się z kursem Redux",
status: "in-progress"
},
{
id: 3,
name: "Zapoznać się ze szczegółami na temat selektorów",
status: "in-progress"
}
]
}
za każdym razem, kiedy chcemy wybrać tylko TODO w statusie in-progress
musielibyśmy pisać:
const mapStateToProps = (state) => {
return {
todo: state.filter(item => item.status === "in-progress")
}
}
Jeżeli w strukturze Reduxa albo poszczególnego reducera zaszła by jakakolwiek zmiana, konieczne było by poprawienie wszystkich miejsc, w których użyliśmy powyższego kodu. Dodatkowo, kod ten znajdował by się w pliku komponentu, co prowadzi do przenikania się odpowiedzialności. Zamiast tego możemy utworzyć selektor i zapisać go w pliku z danymi Reduxa:
export const getInProgress = (todos) => {
return todos.filter(item => item.status === "in-progress");
};
export const getDone = (todos) => {
return todos.filter(item => item.status === "done");
};
a w samym komponencie:
import { getInProgress } from './redux/todo';
const mapStateToProps = (state) => {
return {
todo: getInProgress(state.todos)
}
}
Używanie selektorów z parametryzacją
Selektory, podobnie jak kreatory akcji, mogą być parametryzowane w celu rozszerzenia ich możliwości i zmniejszenia ilości powtarzającego się kodu:
export const getInStatus = (todos, status) => {
return todos.filter(item => item.status === status);
};
const mapStateToProps = (state) => {
return {
todo: getInStatus(state.todos, "in-progess"),
done: getInStatus(state.todos, "done")
};
};
Parametryzację można także przeprowadzić za pomocą propsów komponentu:
const mapStateToProps = (state, ownProps) => {
return {
todo: getInStatus(state.todos, ownProps.status)
};
};
ReactDOM.render(<TodoList status="in-progress" />, document.getElementById('root'));
reselect
- polepszanie wydajności
Używanie selektorów do wyboru podzbioru obiektów może prowadzić do zbędnego re-renderowania się komponentu. connect
sprawdza, czy props przekazane do komponentu są równe propsom przekazanym w poprzednim renderowaniu i jeżeli tak, nie re-renderuje komponentu. Niestety, selektory zwracające nową tablicę (wynik działania .filter
) powodują, że nawet jeżeli rzeczywiste elementy nie ulegną zmianie, nowa tablica nie będzie równa starej.
Aby zapobiec takiej sytuacji możemy użyć memoizacji danych, np. za pomocą popularnej biblioteki reselect. Funkcja createSelector
z tej biblioteki musi zostać wywołana z minimum 2 funkcjami:
- dowolna ilość funkcji wybierających dane z Reduxa,
- jedna (ostatnia) funkcja przekształcająca dane na oczekiwane przez komponent
Wyniki funkcji zwracających dane są zapamiętywane i jeżeli nie uległy zmianie, funkcja przekształcająca nie jest wywoływana, a zamiast niej zwracana jest przypisana do zapamiętanych danych wejściowych wartość:
import { createSelector } from 'reselect';
const state = {
todos: [
{
id: 1,
name: "Zapoznać się z kursem React",
status: "done"
},
{
id: 2,
name: "Zapoznać się z kursem Redux",
status: "in-progress"
},
{
id: 3,
name: "Zapoznać się ze szczegółami na temat selektorów",
status: "in-progress"
}
]
};
const getInStatus = (state, status) => {
return state.filter(item => item.status === status);
};
const memoizedInProgress = createSelector(
// wybierz dane z Reduxa, pełna tabela
(state) => state.todos,
// wybierz z danych zwróconych przez poprzednią funkcję interesujące nas dane
(todos) => {
console.log("Brak danych w pamięci - przelicz");
return getInStatus(todos, "in-progress")
}
);
console.log(memoizedInProgress(state)); // "Brak danych w pamięci - przelicz", "[Object, Object]"
console.log(memoizedInProgress(state)); // "[Object, Object]"