|
@@ -1,235 +0,0 @@
|
|
|
-<header>reducers</header>
|
|
|
-
|
|
|
-<body>
|
|
|
- <div id="testDiv">
|
|
|
- Test
|
|
|
- </div>
|
|
|
-
|
|
|
- <script>
|
|
|
- const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms)) // для наглядности отладки
|
|
|
- function jwtDecode(token) { // расщифровки токена авторизации
|
|
|
- if (!token || typeof token != "string")
|
|
|
- return undefined;
|
|
|
- let tokenArr = token.split(".");
|
|
|
- if (tokenArr.length != 3)
|
|
|
- return undefined;
|
|
|
- try {
|
|
|
- let tokenJsonStr = atob(tokenArr[1]);
|
|
|
- let tokenJson = JSON.parse(tokenJsonStr);
|
|
|
- return tokenJson;
|
|
|
- }
|
|
|
- catch {
|
|
|
- return undefined;
|
|
|
- }
|
|
|
- }
|
|
|
- function gql(url, query, vars) { // формирование запроса GQl
|
|
|
- let fetchSettings =
|
|
|
- {
|
|
|
- method: "POST",
|
|
|
- headers:
|
|
|
- {
|
|
|
- "Content-Type": "application/json",
|
|
|
- "Accept": "application/json"
|
|
|
- },
|
|
|
- body: JSON.stringify(
|
|
|
- {
|
|
|
- query: query,
|
|
|
- variables: vars
|
|
|
-
|
|
|
- })
|
|
|
- };
|
|
|
- return fetch(url, fetchSettings).then(res => res.json());
|
|
|
- }
|
|
|
- function signIn(login, password, url) { // авторизация через GQl
|
|
|
- const loginQuery =
|
|
|
- `query login($login:String, $password:String){
|
|
|
- login(login:$login, password:$password)
|
|
|
- }`;
|
|
|
- return gql(
|
|
|
- url,
|
|
|
- loginQuery,
|
|
|
- { login: login, password: password });
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- function treatAuth(action, state) { // обработка успешной авторизации и смена состояния
|
|
|
- let payload = action.payload;
|
|
|
- return setAuthState({ token: payload.data.login, payload: jwtDecode(payload.data.login) })
|
|
|
- }
|
|
|
- function setAuthState(authState, state) { //смена состояния авторизации
|
|
|
- let newState = { ...state };
|
|
|
- newState.auth = { ...state.auth };
|
|
|
- newState.auth.state = authState;
|
|
|
- return newState;
|
|
|
- }
|
|
|
-
|
|
|
- function treatBuy(action, state) { // обработка покупки
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- function promiseReducer(state = {}, action) { // диспетчер обработки
|
|
|
- if (action) {
|
|
|
- if (action.type === 'PROMISE') {
|
|
|
- let newState = treatPayload(action, "buy", state, treatBuy);
|
|
|
- newState = treatPayload(action, "auth", newState, treatAuth, () => setAuthState(undefined, state));
|
|
|
- return newState;
|
|
|
- }
|
|
|
- }
|
|
|
- return state;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function treatPayload(action, key, state, onPayloadFunc, resetStateFunc) { //вызов функций обработки состояния по статусу с сбросом текущего состояния и обработки payload
|
|
|
- let resultState = state;
|
|
|
- let keyAction = action[key];
|
|
|
- if (keyAction) {
|
|
|
- resultState = changeStatus(key, state, action, resetStateFunc);
|
|
|
- if (state !== result && keyAction.status == "FULLFILLED")
|
|
|
- onPayloadFunc(keyAction, resultState)
|
|
|
- }
|
|
|
- return resultState;
|
|
|
- }
|
|
|
- function changeStatus(key, state, action, resetStateFunc) { //обработка состояния по статусу
|
|
|
- let result = state;
|
|
|
- let actionData = action[key];
|
|
|
- if (actionData) {
|
|
|
- let stateData = state[key];
|
|
|
- let status = actionData.status;
|
|
|
- if (!stateData || stateData.status !== status) {
|
|
|
- let newStateForKey = { ...stateData, ...{ status: status } };
|
|
|
- result = { ...state };
|
|
|
- result[key] = newStateForKey;
|
|
|
- if (status == "PENDING") {
|
|
|
- if (resetStateFunc)
|
|
|
- resetStateFunc();
|
|
|
- newStateForKey.error = undefined;
|
|
|
- }
|
|
|
- if (status == "REJECTED")
|
|
|
- newStateForKey.error = actionData.error;
|
|
|
- }
|
|
|
- }
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
- function createStore(reducer) {
|
|
|
- let state = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
|
|
|
- let cbs = [] //массив подписчиков
|
|
|
-
|
|
|
- const getState = () => state //функция, возвращающая переменную из замыкания
|
|
|
- const subscribe = cb => (cbs.push(cb), //запоминаем подписчиков в массиве
|
|
|
- () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
|
|
|
-
|
|
|
- function dispatch(action) {
|
|
|
- if (typeof action === 'function') { //если action - не объект, а функция
|
|
|
- return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
|
|
|
- }
|
|
|
- const newState = reducer(state, action) //пробуем запустить редьюсер
|
|
|
- if (newState !== state) { //проверяем, смог ли редьюсер обработать action
|
|
|
- state = newState //если смог, то обновляем state
|
|
|
- for (let cb of cbs) cb() //и запускаем подписчиков
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return {
|
|
|
- getState, //добавление функции getState в результирующий объект
|
|
|
- dispatch,
|
|
|
- subscribe //добавление subscribe в объект
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- function actionPromise({ name, promise }) {
|
|
|
- return async function Exec(dispatch) {
|
|
|
- dispatch(actionPending(name)) //сигнализируем redux, что промис начался
|
|
|
- try {
|
|
|
- const payload = await promise //ожидаем промиса;
|
|
|
- dispatch(actionFulfilled(name, payload)); //сигнализируем redux, что промис успешно выполнен
|
|
|
- return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
|
|
|
- }
|
|
|
- catch (error) {
|
|
|
- dispatch(actionRejected(name, error)) //в случае ошибки - сигнализируем redux, что промис несложился
|
|
|
- }
|
|
|
- };
|
|
|
- }
|
|
|
- const actionPending = (name, error) => {
|
|
|
- let result = { type: 'PROMISE' };
|
|
|
- result[name] = { status: 'REJECTED' };
|
|
|
- return result;
|
|
|
- }
|
|
|
- const actionFulfilled = (name, payload) => {
|
|
|
- let result = { type: 'PROMISE' };
|
|
|
- result[name] = { status: 'FULFILLED', payload };
|
|
|
- return result;
|
|
|
- }
|
|
|
- const actionRejected = (name, error) => {
|
|
|
- let result = { type: 'PROMISE' };
|
|
|
- result[name] = { status: 'REJECTED', error };
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
- const store = createStore(promiseReducer)
|
|
|
-
|
|
|
- store.subscribe(() => {
|
|
|
- let state = store.getState();
|
|
|
- if (state.buy) {
|
|
|
- if (state.buy.status == "PENDING")
|
|
|
- testDiv.style.color = "orange";
|
|
|
- else if (state.buy.status == "FULLFILLED")
|
|
|
- testDiv.style.color = "green";
|
|
|
- else if (state.buy.status == "REJECTED") {
|
|
|
- testDiv.style.color = "red";
|
|
|
- testDiv.innerText = state.buy.errorMessage;
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- let execFunc = actionPromise({ name: "auth", promise: signIn("test457", "123123", "http://shop-roles.node.ed.asmer.org.ua/graphql") });
|
|
|
- store.dispatch(execFunc);
|
|
|
-
|
|
|
- /*
|
|
|
- const actionPending = () => ({ type: 'PROMISE', status: 'PENDING' })
|
|
|
- const actionFulfilled = payload => ({ type: 'PROMISE', status: 'FULFILLED', payload })
|
|
|
- const actionRejected = error => ({ type: 'PROMISE', status: 'REJECTED', error })
|
|
|
-
|
|
|
-
|
|
|
- store.subscribe(() => console.log(store.getState()))
|
|
|
-
|
|
|
- store.dispatch({ type: 'COUNTER_INC' })
|
|
|
- store.dispatch({ type: 'BOOLEAN_SET' })
|
|
|
- store.dispatch({ type: 'COUNTER_INC' })
|
|
|
- store.dispatch({ type: 'BOOLEAN_TOGGLE' })
|
|
|
- store.dispatch({ type: 'COUNTER_DEC' })
|
|
|
- store.dispatch({ type: 'ДИЧЬ' }) //не вызывает подписчика
|
|
|
- */
|
|
|
-
|
|
|
- </script>
|
|
|
-</body>
|
|
|
-
|
|
|
-function combineReducers(reducers) {
|
|
|
-function totalReducer(totalState = {}, action) {
|
|
|
-const newTotalState = {} //объект, который будет хранить только новые состояния дочерних редьюсеров
|
|
|
-
|
|
|
-//цикл + квадратные скобочки позволяют написать код, который будет работать с любыми количеством дочерных редьюсеров
|
|
|
-for (const [reducerName, childReducer] of Object.entries(reducers)) {
|
|
|
-const newState = childReducer(totalState[reducerName], action) //запуск дочернего редьюсера
|
|
|
-if (newState !== totalState[reducerName]) { //если он отреагировал на action
|
|
|
-newTotalState[reducerName] = newState //добавляем его в newTotalState
|
|
|
-}
|
|
|
-}
|
|
|
-
|
|
|
-//Универсальная проверка на то, что хотя бы один дочерний редьюсер создал новый стейт:
|
|
|
-if (Object.values(newTotalState).length) {
|
|
|
-return { ...totalState, ...newTotalState } //создаем новый общий стейт, накладывая новый стейты дочерних редьюсеров на
|
|
|
-старые
|
|
|
-}
|
|
|
-
|
|
|
-return totalState //если экшен не был понят ни одним из дочерних редьюсеров, возвращаем общий стейт как был.
|
|
|
-}
|
|
|
-
|
|
|
-return totalReducer
|
|
|
-}
|
|
|
-
|
|
|
-//////
|
|
|
-
|
|
|
-
|
|
|
-/////
|