// delay { const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms)) } // createstore для тестов { 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, которая удаляет подписчика из списка const 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 в объект } } } // promiseReducer // Улучшите promiseReducer из материала занятия, добавив возможность работать с несколькими промисами.Для этого состояние из формата { status, payload, error } должно превратиться в состояние вида: { function promiseReducer(state = {}, { type, status, payload, error, nameOfPromise }) { if (type === 'PROMISE') { return { ...state, [nameOfPromise]: { status, payload, error } } } return state } // Акшоны: // Соответственно, имя промиса будет передаваться в редьюсер как ключ в объекте action, а так же: const actionPending = nameOfPromise => ({ nameOfPromise, type: 'PROMISE', status: 'PENDING' }) // в actionPending - единственный параметр const actionFulfilled = (nameOfPromise, payload) => ({ nameOfPromise, type: 'PROMISE', status: 'FULFILLED', payload }) // как первый параметр, payload станет вторым параметром const actionRejected = (nameOfPromise, error) => ({ nameOfPromise, type: 'PROMISE', status: 'REJECTED', error }) // по аналогии с actionFulfilled const actionPromise = (nameOfPromise, promise) => // первый параметр, второй параметр - сам промис, который мы будем обрабатывать; async dispatch => { dispatch(actionPending(nameOfPromise)) //сигнализируем redux, что промис начался try { const payload = await promise //ожидаем промиса dispatch(actionFulfilled(nameOfPromise, payload)) //сигнализируем redux, что промис успешно выполнен return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса } catch (error) { dispatch(actionRejected(nameOfPromise, error)) //в случае ошибки - сигнализируем redux, что промис несложился } } // проверка работоспособности { // const store = createStore(promiseReducer) // store.subscribe(() => console.log(1, store.getState())) //должен запускаться 6 раз // store.dispatch(actionPromise('delay', delay(1000))) // store.dispatch(actionPromise('luke', fetch("https://swapi.dev/api/people/1").then(res => res.json()))) // store.dispatch(actionPromise('tatooine', fetch("https://swapi.dev/api/planets/1").then(res => res.json()))) } } // authReducer { const jwtDecode = function (token) { try { let parseData = token.split('.')[1] return JSON.parse(atob(parseData)) } catch (e) { return undefined } } function authReducer(state = {}, { type, token }) { if (type === 'AUTH_LOGIN') { let payload = jwtDecode(token) return state = { token, payload } } if (type === 'AUTH_LOGOUT') { return {} } return state } // проверка (и экшенкриейторы бонусом): const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token }) const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' }) const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2Mzc3ZTEzM2I3NGUxZjVmMmVjMWMxMjUiLCJsb2dpbiI6InRlc3Q1IiwiYWNsIjpbIjYzNzdlMTMzYjc0ZTFmNWYyZWMxYzEyNSIsInVzZXIiXX0sImlhdCI6MTY2ODgxMjQ1OH0.t1eQlRwkcP7v9JxUPMo3dcGKprH-uy8ujukNI7xE3A0" // const store = createStore(authReducer) // store.subscribe(() => console.log(store.getState())) // store.dispatch(actionAuthLogin(token)) /*{ token: "eyJhbGc.....", payload: { "sub": { "id": "6377e133b74e1f5f2ec1c125", "login": "test5", "acl": [ "6377e133b74e1f5f2ec1c125", "user" ] }, "iat": 1668812458 } }*/ // store.dispatch(actionAuthLogout()) // {} } // cartReducer { function cartReducer(state = {}, { type, count, good }) { if (type === 'CART_ADD') { return { ...state, [good._id]: { good, count: (state[good._id] ? state[good._id].count + count : count) } } } if (type === 'CART_SUB') { if (state[good._id]) { let newCount = state[good._id].count - count if (newCount > 0) { return { ...state, [good._id]: { good, count: newCount } } } else { delete state[good._id] return { ...state } } } else { return undefined } } if (type === 'CART_DEL') { delete state[good._id] return { ...state } } if (type === 'CART_SET') { if (count > 0) { return { ...state, [good._id]: { good, count } } } else { delete state[good._id] return { ...state } } } if (type === 'CART_CLEAR') { return state = {} } return state } // экшоны: // Добавление товара.Должен добавлять новый ключ в state, или обновлять, если ключа в state ранее не было, увеличивая количество const actionCartAdd = (good, count = 1) => ({ type: 'CART_ADD', count, good }) // Уменьшение количества товара.Должен уменьшать количество товара в state, или удалять его если количество будет 0 или отрицательным const actionCartSub = (good, count = 1) => ({ type: 'CART_SUB', count, good }) // Удаление товара.Должен удалять ключ из state const actionCartDel = (good) => ({ type: 'CART_DEL', good }) // Задание количества товара.В отличие от добавления и уменьшения, не учитывает того количества, которое уже было в корзине, а тупо назначает количество поверху(или создает новый ключ, если в корзине товара не было).Если count 0 или отрицательное число - удаляем ключ из корзины; const actionCartSet = (good, count = 1) => ({ type: 'CART_SET', count, good }) // Очистка корзины.state должен стать пустым объектом { } const actionCartClear = () => ({ type: 'CART_CLEAR' }) // Проверочный код const store = createStore(cartReducer) store.subscribe(() => console.log(store.getState())) // console.log(store.getState()) //{} store.dispatch(actionCartAdd({ _id: 'пиво', price: 50 })) // {пиво: {good: {_id: 'пиво', price: 50}, count: 1}} store.dispatch(actionCartAdd({ _id: 'чипсы', price: 75 })) // { // пиво: {good: {_id: 'пиво', price: 50}, count: 1}, // чипсы: {good: {_id: 'чипсы', price: 75}, count: 1}, //} store.dispatch(actionCartAdd({ _id: 'пиво', price: 50 }, 5)) // { // пиво: {good: {_id: 'пиво', price: 50}, count: 6}, // чипсы: {good: {_id: 'чипсы', price: 75}, count: 1}, //} store.dispatch(actionCartSet({ _id: 'чипсы', price: 75 }, 2)) // { // пиво: {good: {_id: 'пиво', price: 50}, count: 6}, // чипсы: {good: {_id: 'чипсы', price: 75}, count: 2}, //} store.dispatch(actionCartSub({ _id: 'пиво', price: 50 }, 4)) // { // пиво: {good: {_id: 'пиво', price: 50}, count: 2}, // чипсы: {good: {_id: 'чипсы', price: 75}, count: 2}, //} store.dispatch(actionCartDel({ _id: 'чипсы', price: 75 })) // { // пиво: {good: {_id: 'пиво', price: 50}, count: 2}, //} store.dispatch(actionCartClear()) // {} }