Browse Source

HW<19>begining

Gennadysht 2 years ago
parent
commit
53abe88595
2 changed files with 238 additions and 0 deletions
  1. 21 0
      js/19_Redux_pro/example.html
  2. 217 0
      js/19_Redux_pro/hw_19_01.html

+ 21 - 0
js/19_Redux_pro/example.html

@@ -0,0 +1,21 @@
+<script>
+
+    async function Exec(dispatch) {
+        dispatch(actionPending()) //сигнализируем redux, что промис начался
+        try {
+            const payload = await promise //ожидаем промиса
+            dispatch(actionFulfilled(payload)) //сигнализируем redux, что промис успешно выполнен
+            return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
+        }
+        catch (error) {
+            dispatch(actionRejected(error)) //в случае ошибки - сигнализируем redux, что промис несложился
+        }
+    }
+
+    function actionPromise(promise) {
+        return Exec;
+    }
+    let myPromise = doSomethingSlow(1000);
+    let execFunc = actionPromise(myPromise);
+    store.dispatch(execFunc); 
+</script>

+ 217 - 0
js/19_Redux_pro/hw_19_01.html

@@ -0,0 +1,217 @@
+<header>reducers</header>
+
+<body>
+    <div id="testDiv">
+        Test
+    </div>
+
+    <script>
+        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) {
+            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) {
+            const loginQuery =
+                `query login($login:String, $password:String){
+                login(login:$login, password:$password)
+            }`;
+            return gql(
+                url,
+                loginQuery,
+                { login: login, password: password });
+        }
+
+
+
+        function TreatAuth({ payload }, state) {
+            let newState = { ...state };
+            newState.auth = { ...state.auth };
+            newState.auth.state = { token: payload.data.login, payload: jwtDecode(payload.data.login) };
+            return newState;
+        }
+
+        function TreatBuy({ status, payload }) {
+
+        }
+
+        const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
+        function promiseReducer(state = {}, action) {
+            if (action) {
+                if (action.type === 'PROMISE') {
+                    let newState = TreatPayload(action, "buy", state, TreatBuy);
+                    newState = TreatPayload(action, "auth", newState, TreatAuth);
+                    return newState;
+                }
+            }
+            return state;
+        }
+
+        function TreatPayload(action, key, state, onPayloadFunc) {
+            let result = state;
+            let keyAction = action[key];
+            if (keyAction) {
+                result = changeStatus(key, state, action);
+                if (state !== result && keyAction.status == "FULLFILLED")
+                    onPayloadFunc(keyAction, result)
+            }
+            return result;
+        }
+        function changeStatus(key, state, action) {
+            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 == "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) {
+                let action = { type: "PROMISE" };
+                action[name] = { status: "PENDING" };
+                dispatch(action) //сигнализируем redux, что промис начался
+
+                try {
+                    const payload = await promise //ожидаем промиса;
+                    action = { type: "PROMISE" };
+                    action[name] = { status: "FULLFILLED", payload: payload };
+                    dispatch(action) //сигнализируем redux, что промис успешно выполнен
+                    return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
+                }
+                catch (error) {
+                    action = { type: "PROMISE" };
+                    action[name] = { status: "REJECTED", error: error };
+                    dispatch(action) //в случае ошибки - сигнализируем redux, что промис несложился
+                }
+            };
+        }
+
+        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
+}
+
+//////
+
+
+/////