WEBAPP: P8PDataGrid - управление состоянием хука переписано на редьюсер для обеспечения атомарности обновления связанных состояний

This commit is contained in:
Mim 2026-04-08 10:23:35 +03:00
parent 3e6afa284d
commit 63751700b4
2 changed files with 178 additions and 85 deletions

View File

@ -7,9 +7,10 @@
//Подключение библиотек //Подключение библиотек
//--------------------- //---------------------
import { useState, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React import { useReducer, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React
import { BackEndCtx } from "../context/backend"; //Контекст взаимодействия с сервером import { BackEndCtx } from "../context/backend"; //Контекст взаимодействия с сервером
import { object2Base64XML } from "../core/utils"; //Вспомогательные функции import { object2Base64XML } from "../core/utils"; //Вспомогательные функции
import { DG_AT, INITIAL_STATE, dataGridReducer } from "./p8p_data_grid_reducer"; //Редьюсер состояния
//--------- //---------
//Константы //Константы
@ -38,38 +39,39 @@ const useP8PDataGrid = ({
initFilters = [], initFilters = [],
initOrders = [], initOrders = [],
storedArgs = {}, storedArgs = {},
resetPageNumberOnStoredArgsChange = true,
executeStoredArgs = {}, executeStoredArgs = {},
resetPageNumberOnExecuteStoredArgsChange = true,
allowDataLoad = () => true allowDataLoad = () => true
}) => { }) => {
//Собственное состояние - таблица данных //Подключим редьюсер состояния
const [dataGrid, setDataGrid] = useState({ const [state, dispatch] = useReducer(dataGridReducer, INITIAL_STATE({ initFilters, initOrders }));
columnsDef: [],
groups: [],
rows: [],
filters: Array.isArray(initFilters) ? [...initFilters] : [],
orders: Array.isArray(initOrders) ? [...initOrders] : [],
pageNumber: 1,
pagesAlign: null,
pagesPosition: null,
pagesCount: 0,
fixedColumns: 0,
fixedHeader: false,
morePages: true
});
//Собственное состояние - признак загрузки данных //Установка даных таблицы
const [isDataLoaded, setIsDataLoaded] = useState(false); const setDataGrid = (dataGridData, pageSize, isError) => dispatch({ type: DG_AT.SET_DATA_GRID, payload: { dataGridData, pageSize, isError } });
//Собственное состояние - флаг загрузки //Установка фильтра таблицы
const [isLoading, setLoading] = useState(false); const setDataGridFilter = filters => dispatch({ type: DG_AT.SET_DATA_GRID_FILTER, payload: filters });
//Собственное состояние - необходимость обновления данных //Установка сортировок таблицы
const [reload, setReload] = useState(true); const setDataGridOrder = orders => dispatch({ type: DG_AT.SET_DATA_GRID_ORDER, payload: orders });
//Собственное состояние - дополнительные параметры процедуры //Установка страницы таблицы
const setDataGridPageNumber = pageNumber => dispatch({ type: DG_AT.SET_DATA_GRID_PAGE_NUMBER, payload: pageNumber });
//Установка флага загруженности данных
const setIsDataLoaded = isDataLoaded => dispatch({ type: DG_AT.SET_IS_DATA_LOADED, payload: isDataLoaded });
//Установка флага активности процесса загрузки данных
const setIsLoading = isLoading => dispatch({ type: DG_AT.SET_IS_LOADING, payload: isLoading });
//Установка флага необходимости обновления данных
const setReload = (reload, resetPageNumber = false) => dispatch({ type: DG_AT.SET_RELOAD, payload: { reload, resetPageNumber } });
//Ссылка на актуальные параметры хранимой процедуры
const refStoredArgs = useRef(storedArgs); const refStoredArgs = useRef(storedArgs);
//Собственное состояние - дополнительные параметры вызова процедуры //Ссылка на актуальные параметры исполнения хранимой процедуры
const refExecuteStoredArgs = useRef(executeStoredArgs); const refExecuteStoredArgs = useRef(executeStoredArgs);
//Признак допустимости обновления данных //Признак допустимости обновления данных
@ -83,52 +85,40 @@ const useP8PDataGrid = ({
//Загрузка данных таблицы с сервера //Загрузка данных таблицы с сервера
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
try { try {
setLoading(true); //Начинаем загрузку
setIsLoading(true);
const data = await executeStored({ const data = await executeStored({
stored, stored,
args: { args: {
CFILTERS: { VALUE: object2Base64XML(dataGrid.filters, { arrayNodeName: filtersNodeName }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, CFILTERS: {
CORDERS: { VALUE: object2Base64XML(dataGrid.orders, { arrayNodeName: ordersNodeName }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, VALUE: object2Base64XML(state.dataGrid.filters, { arrayNodeName: filtersNodeName }),
NPAGE_NUMBER: dataGrid.pageNumber, SDATA_TYPE: SERV_DATA_TYPE_CLOB
},
CORDERS: { VALUE: object2Base64XML(state.dataGrid.orders, { arrayNodeName: ordersNodeName }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
NPAGE_NUMBER: state.dataGrid.pageNumber,
NPAGE_SIZE: pageSize, NPAGE_SIZE: pageSize,
NINCLUDE_DEF: reloadDef ? 1 : dataGrid.dataLoaded ? 0 : 1, NINCLUDE_DEF: reloadDef ? 1 : state.isDataLoaded ? 0 : 1,
...refStoredArgs.current ...refStoredArgs.current
}, },
respArg, respArg,
...refExecuteStoredArgs.current ...refExecuteStoredArgs.current
}); });
setDataGrid(pv => ({ //Устанавливаем полученные данные и признак загрузки данных с учетом возможных ошибок
...pv, setDataGrid(data[contentNodeName], pageSize, isRespErr(data));
...data[contentNodeName],
columnsDef: data[contentNodeName].columnsDef ? [...data[contentNodeName].columnsDef] : pv.columnsDef || [],
rows:
data[contentNodeName].pagesCount > 0 || pv.pageNumber == 1
? [...(data[contentNodeName].rows || [])]
: [...(pv.rows || []), ...(data[contentNodeName].rows || [])],
groups: data[contentNodeName].groups
? data[contentNodeName].pagesCount > 0 || pv.pageNumber == 1
? [...(data[contentNodeName].groups || [])]
: [...(pv.groups || []), ...data[contentNodeName].groups.filter(g => !pv.groups.find(pg => pg.name == g.name))]
: [...(pv.groups || [])],
morePages: data[contentNodeName].morePages && (data[contentNodeName].rows || []).length >= pageSize
}));
//Устанавливаем признак загрузки данных с учетом возможных ошибок
setIsDataLoaded(!isRespErr(data));
} catch (e) { } catch (e) {
//Если произошла ошибка - данные не загружены //Если произошла ошибка - данные не загружены
setIsDataLoaded(false); setIsDataLoaded(false);
} finally { } finally {
//Сбрасываем признаки загрузки и перезагрузки данных //Сбрасываем признаки загрузки и перезагрузки данных
setLoading(false); setIsLoading(false);
setReload(false);
} }
}, [ }, [
SERV_DATA_TYPE_CLOB, SERV_DATA_TYPE_CLOB,
contentNodeName, contentNodeName,
dataGrid.dataLoaded, state.isDataLoaded,
dataGrid.filters, state.dataGrid.filters,
dataGrid.orders, state.dataGrid.orders,
dataGrid.pageNumber, state.dataGrid.pageNumber,
executeStored, executeStored,
filtersNodeName, filtersNodeName,
isRespErr, isRespErr,
@ -140,48 +130,30 @@ const useP8PDataGrid = ({
]); ]);
//При изменении состояния фильтра //При изменении состояния фильтра
const handleFilterChanged = useCallback(({ filters }) => { const handleFilterChanged = useCallback(({ filters }) => setDataGridFilter(filters), []);
setDataGrid(pv => ({ ...pv, filters: [...filters], pageNumber: 1 }));
setReload(true);
}, []);
//При изменении состояния сортировки //При изменении состояния сортировки
const handleOrderChanged = useCallback(({ orders }) => { const handleOrderChanged = useCallback(({ orders }) => setDataGridOrder(orders), []);
setDataGrid(pv => ({ ...pv, orders: [...orders], pageNumber: 1 }));
setReload(true);
}, []);
//При изменении количества отображаемых страниц //При изменении количества отображаемых страниц
const handlePagesCountChanged = useCallback(() => { const handlePagesCountChanged = useCallback(() => setDataGridPageNumber(state.dataGrid.pageNumber + 1), [state.dataGrid.pageNumber]);
setDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1 }));
setReload(true);
}, []);
//При изменении страницы отображения //При изменении страницы отображения
const handlePageChange = useCallback(({ page }) => { const handlePageChange = useCallback(({ page }) => setDataGridPageNumber(page), []);
setDataGrid(pv => ({ ...pv, pageNumber: page }));
setReload(true);
}, []);
//При необходимости обновления таблицы //При необходимости обновления таблицы
const doReload = useCallback( const doReload = useCallback(
({ returnOnFirstPage = false }) => { ({ returnOnFirstPage = false }) => setReload(true, state.dataGrid.pagesCount <= 0 || returnOnFirstPage),
//Если это не страничный вывод или установлен признак возврата на первую страницу [state.dataGrid.pagesCount]
if (dataGrid.pagesCount <= 0 || returnOnFirstPage) {
setDataGrid(pv => ({ ...pv, pageNumber: 1 }));
}
setReload(true);
},
[dataGrid.pagesCount]
); );
//Проверка изменений параметров //Проверка изменений параметров
const isArgsChanged = useCallback( const isArgsChanged = useCallback(
(currentArgs, args) => { (currentArgs, args) => {
//Если дополнительные параметры изменились (и сейчас не происходит загрузка данных с сервера) //Если дополнительные параметры изменились (и сейчас не происходит загрузка данных с сервера)
return !isLoading && JSON.stringify(currentArgs) != JSON.stringify(args); return !state.isLoading && JSON.stringify(currentArgs) != JSON.stringify(args);
}, },
[isLoading] [state.isLoading]
); );
//При изменение дополнительных параметров процедуры //При изменение дополнительных параметров процедуры
@ -191,9 +163,9 @@ const useP8PDataGrid = ({
//Устанавливаем новые дополнительные параметры //Устанавливаем новые дополнительные параметры
refStoredArgs.current = storedArgs; refStoredArgs.current = storedArgs;
//При изменении дополнительных параметров необходимо перезагрузить данные //При изменении дополнительных параметров необходимо перезагрузить данные
setReload(true); setReload(true, resetPageNumberOnStoredArgsChange);
} }
}, [storedArgs, isArgsChanged]); }, [storedArgs, resetPageNumberOnStoredArgsChange, isArgsChanged]);
//При изменение дополнительных параметров вызова процедуры //При изменение дополнительных параметров вызова процедуры
useEffect(() => { useEffect(() => {
@ -202,19 +174,28 @@ const useP8PDataGrid = ({
//Устанавливаем новые дополнительные параметры //Устанавливаем новые дополнительные параметры
refExecuteStoredArgs.current = executeStoredArgs; refExecuteStoredArgs.current = executeStoredArgs;
//При изменении дополнительных параметров необходимо перезагрузить данные //При изменении дополнительных параметров необходимо перезагрузить данные
setReload(true); setReload(true, resetPageNumberOnExecuteStoredArgsChange);
} }
}, [executeStoredArgs, isArgsChanged]); }, [executeStoredArgs, resetPageNumberOnExecuteStoredArgsChange, isArgsChanged]);
//При необходимости обновить данные таблицы //При необходимости обновить данные таблицы
useEffect(() => { useEffect(() => {
if (isAllowDataLoad && reload) { if (isAllowDataLoad && state.reload) {
loadData(); loadData();
} }
}, [isAllowDataLoad, reload, loadData]); }, [isAllowDataLoad, state.reload, loadData]);
//Возвращаем данные таблицы //Возвращаем данные таблицы
return { dataGrid, isDataLoaded, isLoading, handleFilterChanged, handleOrderChanged, handlePagesCountChanged, handlePageChange, doReload }; return {
dataGrid: state.dataGrid,
isDataLoaded: state.isDataLoaded,
isLoading: state.isLoading,
handleFilterChanged,
handleOrderChanged,
handlePagesCountChanged,
handlePageChange,
doReload
};
}; };
//---------------- //----------------

View File

@ -0,0 +1,112 @@
/*
Парус 8 - Панели мониторинга
Таблица данных - редьюсер состояния
*/
//---------
//Константы
//---------
//Типы действий
const DG_AT = {
SET_DATA_GRID: "SET_DATA_GRID", //Установка даных таблицы
SET_DATA_GRID_FILTER: "SET_DATA_GRID_FILTER", //Установка фильтра таблицы
SET_DATA_GRID_ORDER: "SET_DATA_GRID_ORDER", //Установка сортировок таблицы
SET_DATA_GRID_PAGE_NUMBER: "SET_DATA_GRID_PAGE_NUMBER", //Установка страницы таблицы
SET_IS_DATA_LOADED: "SET_IS_DATA_LOADED", //Установка флага загруженности данных
SET_IS_LOADING: "SET_IS_LOADING", //Установка флага активности процесса загрузки данных
SET_RELOAD: "SET_RELOAD" //Установка флага необходимости обновления данных
};
//Состояние приложения по умолчанию
const INITIAL_STATE = ({ initFilters, initOrders }) => ({
dataGrid: {
columnsDef: [],
groups: [],
rows: [],
filters: Array.isArray(initFilters) ? [...initFilters] : [],
orders: Array.isArray(initOrders) ? [...initOrders] : [],
pageNumber: 1,
pagesAlign: null,
pagesPosition: null,
pagesCount: 0,
fixedColumns: 0,
fixedHeader: false,
morePages: true
},
isDataLoaded: false,
isLoading: false,
reload: true
});
//-----------
//Тело модуля
//-----------
//Обработчики действий
const handlers = {
//Установка даных таблицы
[DG_AT.SET_DATA_GRID]: (state, { payload }) => {
const { dataGridData, pageSize, isError } = payload;
return {
...state,
dataGrid: {
...state.dataGrid,
...dataGridData,
columnsDef: dataGridData.columnsDef ? [...dataGridData.columnsDef] : state.dataGrid.columnsDef || [],
rows:
dataGridData.pagesCount > 0 || state.dataGrid.pageNumber == 1
? [...(dataGridData.rows || [])]
: [...(state.dataGrid.rows || []), ...(dataGridData.rows || [])],
groups: dataGridData.groups
? dataGridData.pagesCount > 0 || state.dataGrid.pageNumber == 1
? [...(dataGridData.groups || [])]
: [...(state.dataGrid.groups || []), ...dataGridData.groups.filter(g => !state.dataGrid.groups.find(pg => pg.name == g.name))]
: [...(state.dataGrid.groups || [])],
morePages: dataGridData.morePages && (dataGridData.rows || []).length >= pageSize
},
isDataLoaded: isError === true ? false : true
};
},
//Установка фильтра таблицы
[DG_AT.SET_DATA_GRID_FILTER]: (state, { payload }) => ({
...state,
dataGrid: { ...state.dataGrid, filters: [...payload], pageNumber: 1 },
reload: true
}),
//Установка сортировок таблицы
[DG_AT.SET_DATA_GRID_ORDER]: (state, { payload }) => ({
...state,
dataGrid: { ...state.dataGrid, orders: [...payload], pageNumber: 1 },
reload: true
}),
//Установка страницы таблицы
[DG_AT.SET_DATA_GRID_PAGE_NUMBER]: (state, { payload }) => ({ ...state, dataGrid: { ...state.dataGrid, pageNumber: payload }, reload: true }),
//Установка флага загруженности данных
[DG_AT.SET_IS_DATA_LOADED]: (state, { payload }) => ({ ...state, isDataLoaded: payload }),
//Установка флага активности процесса загрузки данных
[DG_AT.SET_IS_LOADING]: (state, { payload }) => ({ ...state, isLoading: payload, reload: payload === false ? false : state.reload }),
//Установка флага необходимости обновления данных
[DG_AT.SET_RELOAD]: (state, { payload }) => ({
...state,
reload: payload.reload,
...(payload.resetPageNumber ? { dataGrid: { ...state.dataGrid, pageNumber: 1 } } : {})
}),
//Обработчик по умолчанию
DEFAULT: state => state
};
//----------------
//Интерфейс модуля
//----------------
//Константы
export { DG_AT, INITIAL_STATE };
//Редьюсер состояния
export const dataGridReducer = (state, action) => {
//Подберём обработчик
const handle = handlers[action.type] || handlers.DEFAULT;
//Исполним его
return handle(state, action);
};