Йо-йо! Всё моё время сейчас уходит на java script и react. Я также работаю в компании, которая продаёт автомобили онлайн. Недавно мы выкатили в «prod» новый дизайн списка сравнения. Прошлый генерировался с помощью php, а далее редактировался с помощью jQuery. Новый же написан на реакте. Плюс добавилась сортировка автомобилей.
Само собой я использую хранилище для данных, в том числе и списка автомобилей. Проблема в том, что если бы я сортировал и фильтровал данные в store, то либо постепенно при фильтрации данные из него удалялись, либо пришлось иметь два экземпляра списка ( полный и фильтрованный/сортированный ).
Сейчас я хочу показать как изящно избавился от этой проблемы, а ещё написал красивый метод для фильтрации и сортировки массивов.
import React, { Component } from 'react'; import TableColumns from './TableColumns' import {connect} from "react-redux"; import _CTFI from '../../store/utils'; class TableColumnsContainer extends Component{ render(){ const { tabSign, // какой таб? это параметр для фильтрации. В моём случае это все авто/новые/подержанные (all/new/used) list, // Список sortParam // Параметр для сортировки } = this.props; const filtredList = /* Нужно получить фильтрованный и сортированный список */ return( <TableColumns isMobile={isMobile} toggleOpenSection={toggleOpenSection} sections={sections} list={filtredList} tabSign={tabSign} removeColumn={removeColumn} /> ) } } const mapStateToProps = (state) => ({ tabSign: state.tabSign, list: state.list, sections: state.creatableRows, isMobile: state.isMobile, sortParam: state.sortParam }); export default connect(mapStateToProps, null)(TableColumnsContainer);
Ещё раз… У нас есть родительский компонент (hight order component), список (array, содержит объекты), таб, по которому фильтруем(все/новые/c пробегом), параметр, по которому фильтруем. Ну и собственно дочерний компонент, в котором это все рисуется.
Я уже говорил, что не буду изменять store. Список я буду изменять в нашем родительском компоненте. Как именно:
Функция, которая вернёт мне экземпляр объекта:
export default function _CTFI(array) { if(Array.isArray(array)){ return new _CTF(array); } else { console.error('variable is not array') // Тут можете поменять как вам удобно } }
Бам! Так просто.
Создадим новый объект:
const _CTF = function (vars) { if(this instanceof _CTF){ this.data = JSON.parse(JSON.stringify(vars)); // Обязательно JJSON.parse(JSON.stringify(vars)) или другой способ deepСlone объектов, например _.cloneDeep return this; } else { return new _CTF(vars); } }
Фильтрация по табам:
_CTF.prototype.filterByState = function (state) { switch(state){ case 'new': this.data = this.data.filter((Car)=> ( Car.CarState === 'Новый' )); break; case 'used': this.data = this.data.filter((Car)=> ( Car.CarState === 'С пробегом' )); break; default: break; } return this; }
Здесь я использую стандартные функции JS. В метод будет сообщаться состояние табов (all/new/used) и в зависимости от этого, будет происходить фильтрация через метод filter.
Сортировка по параметрам:
_CTF.prototype.orderByParam = function (param) { if(param === null){ return this; } else { switch(param){ case 'CarPrice': this.data = this.data.sort(this._sortByPrice); return this; case 'IsMarkedDown': this.data = this.data.sort(this._sortByMarkedDown); return this; case 'CarRun': this.data = this.data.sort(this._sortByCarRun); return this; default: console.warn(`Нет правила для сортировки по параметру ${param}`); return this; } } }
Плюс несколько функций сортировки:
_CTF.prototype._sortByPrice = function (a, b) { const pA = parseInt(a.CarPrice); const pB = parseInt(b.CarPrice); if(pA < pB){ return -1; } else { return 1; } } _CTF.prototype._sortByMarkedDown = function (a, b) { const pA = (a.IsMarkedDown === 1); const pB = (b.IsMarkedDown === 1); if(pA !== pB){ if(pA && !pB){ return -1 } else { return 1 } } else { return 0; } } _CTF.prototype._sortByCarRun = function (a, b) { const pA = parseInt(a.CarRun); const pB = parseInt(b.CarRun); if(pA < pB){ return -1; } else { return 1; } }
Так как мы ранее сохраняли массив в нашем объекте в свойстве data, то у нас нет проблем.
_CTF.prototype.get = function () { return this.data; }
Мы должны получить наш новый список:
const { tabSign, // какой таб? это параметр для фильтрации. В моём случае это все авто/новые/подержанные (all/new/used) list, // Список sortParam // Параметр для сортировки } = this.props; const filtredList = _CTFI(list).filterByState(tabSign).orderByParam(sortParam).get();
Получился очень изящно и читабельно.
А почему нет?! Только нам нужно будет хранить экземпляр нашего списка без изменений. Мы можем использовать наши методы и для в action’на. Это будет удобно если мы используем наш список в нескольких местах. Например:
export const filterAndSort = (string) => ( (dispatch, getState) => { const list = getState().list; const tabSign = getState().tabSign; const sortParam = getState().sortParam; const filtredList = _CTFI(list).filterByState(tabSign).orderByParam(sortParam).get(); dispatch({ type: SET_FILTRED_LIST, payload: filtredList }); } )
Но в таком случае, если у нас происходит изменение списка, нужно изменить и фильтрованный список:
export const changeListByParam = (param) => ( (dispatch, getState) => { const list = getState().list; const tabSign = getState().tabSign; const sortParam = getState().sortParam; const newList = fnForChange(list, param); const filtredList = _CTFI(newList).filterByState(tabSign).orderByParam(sortParam).get(); dispatch({ type: SET_LIST, payload: newList }); dispatch({ type: SET_FILTRED_LIST, payload: filtredList }); } )
Мы же можем так делать огромные цепочки фильтрации:
_CTF.prototype.filterByParams= function (param, fnForFilter) { if(param === null){ return this; } else { switch(param){ case 'CarPrice': this.data = fnForFilter(this.data, param); // Различные функции для фильтрации return this; case 'IsMarkedDown': this.data = fnForFilter(this.data, param); return this; case 'CarRun': this.data = fnForFilter(this.data, param); return this; default: return this; } } }
Тогда мы сможем вызывать его так:
const filtredList = _CTFI(list) .filterByParams('CarPrice', (data, param)=>{ return data.filter((obj)=>{ obj[param] > 100000 }) }) .filterByParams('IsMarkedDown', (data, param)=>{ return data.filter((obj)=>{ obj[param] === 1 }) }) /* И так далее */ .get(); // В конце get
// Обновление, новая версия класса export default function _CTFI(array) { if(Array.isArray(array)){ return new _CTF(array); } else { console.error('variable is not array') // Тут можете поменять как вам удобно } } export class _CTF { constructor(vars){ if(this instanceof _CTF){ this.data = [...vars] return this } else { return new _CTF(vars); } } filterByState = (state) => { switch(state){ case 'new': this.data = this.data.filter((Car)=> ( Car.CarState === 'Новый' )); break; case 'used': this.data = this.data.filter((Car)=> ( Car.CarState === 'С пробегом' )); break; default: break; } return this; } orderByParam = (param) => { if(param === null){ return this; } else { switch(param){ case 'CarPrice': this.data = this.data.sort(this._sortByPrice); return this; case 'IsMarkedDown': this.data = this.data.sort(this._sortByMarkedDown); return this; case 'CarRun': this.data = this.data.sort(this._sortByCarRun); return this; default: console.warn(`Нет правила для сортировки по параметру ${param}`); return this; } } } _sortByPrice = (a, b) => { const pA = parseInt(a.CarPrice); const pB = parseInt(b.CarPrice); if(pA < pB){ return -1; } else { return 1; } } _sortByMarkedDown = (a, b) => { const pA = (a.IsMarkedDown === 1); const pB = (b.IsMarkedDown === 1); if(pA !== pB){ if(pA && !pB){ return -1 } else { return 1 } } else { return 0; } } _sortByCarRun = (a, b) => { const pA = parseInt(a.CarRun); const pB = parseInt(b.CarRun); if(pA < pB){ return -1; } else { return 1; } } get = () => { return this.data; } }