redusers.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. // delay
  2. {
  3. const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
  4. }
  5. // createstore для тестов
  6. {
  7. function createStore(reducer) {
  8. let state = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
  9. let cbs = [] //массив подписчиков
  10. const getState = () => state //функция, возвращающая переменную из замыкания
  11. const subscribe = cb => (cbs.push(cb), //запоминаем подписчиков в массиве
  12. () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
  13. const dispatch = action => {
  14. if (typeof action === 'function') { //если action - не объект, а функция
  15. return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
  16. }
  17. const newState = reducer(state, action) //пробуем запустить редьюсер
  18. if (newState !== state) { //проверяем, смог ли редьюсер обработать action
  19. state = newState //если смог, то обновляем state
  20. for (let cb of cbs) {
  21. cb()
  22. } //и запускаем подписчиков
  23. }
  24. }
  25. return {
  26. getState, //добавление функции getState в результирующий объект
  27. dispatch,
  28. subscribe //добавление subscribe в объект
  29. }
  30. }
  31. }
  32. // promiseReducer
  33. // Улучшите promiseReducer из материала занятия, добавив возможность работать с несколькими промисами.Для этого состояние из формата { status, payload, error } должно превратиться в состояние вида:
  34. {
  35. function promiseReducer(state = {}, { type, status, payload, error, nameOfPromise }) {
  36. if (type === 'PROMISE') {
  37. return {
  38. ...state,
  39. [nameOfPromise]: { status, payload, error }
  40. }
  41. }
  42. return state
  43. }
  44. // Акшоны:
  45. // Соответственно, имя промиса будет передаваться в редьюсер как ключ в объекте action, а так же:
  46. const actionPending = nameOfPromise => ({ nameOfPromise, type: 'PROMISE', status: 'PENDING' }) // в actionPending - единственный параметр
  47. const actionFulfilled = (nameOfPromise, payload) => ({ nameOfPromise, type: 'PROMISE', status: 'FULFILLED', payload }) // как первый параметр, payload станет вторым параметром
  48. const actionRejected = (nameOfPromise, error) => ({ nameOfPromise, type: 'PROMISE', status: 'REJECTED', error }) // по аналогии с actionFulfilled
  49. const actionPromise = (nameOfPromise, promise) => // первый параметр, второй параметр - сам промис, который мы будем обрабатывать;
  50. async dispatch => {
  51. dispatch(actionPending(nameOfPromise)) //сигнализируем redux, что промис начался
  52. try {
  53. const payload = await promise //ожидаем промиса
  54. dispatch(actionFulfilled(nameOfPromise, payload)) //сигнализируем redux, что промис успешно выполнен
  55. return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
  56. }
  57. catch (error) {
  58. dispatch(actionRejected(nameOfPromise, error)) //в случае ошибки - сигнализируем redux, что промис несложился
  59. }
  60. }
  61. // проверка работоспособности
  62. {
  63. // const store = createStore(promiseReducer)
  64. // store.subscribe(() => console.log(1, store.getState())) //должен запускаться 6 раз
  65. // store.dispatch(actionPromise('delay', delay(1000)))
  66. // store.dispatch(actionPromise('luke', fetch("https://swapi.dev/api/people/1").then(res => res.json())))
  67. // store.dispatch(actionPromise('tatooine', fetch("https://swapi.dev/api/planets/1").then(res => res.json())))
  68. }
  69. }
  70. // authReducer
  71. {
  72. const jwtDecode = function (token) {
  73. try {
  74. let parseData = token.split('.')[1]
  75. return JSON.parse(atob(parseData))
  76. }
  77. catch (e) {
  78. return undefined
  79. }
  80. }
  81. function authReducer(state = {}, { type, token }) {
  82. if (type === 'AUTH_LOGIN') {
  83. let payload = jwtDecode(token)
  84. return state = {
  85. token,
  86. payload
  87. }
  88. }
  89. if (type === 'AUTH_LOGOUT') {
  90. return {}
  91. }
  92. return state
  93. }
  94. // проверка (и экшенкриейторы бонусом):
  95. const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token })
  96. const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
  97. const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2Mzc3ZTEzM2I3NGUxZjVmMmVjMWMxMjUiLCJsb2dpbiI6InRlc3Q1IiwiYWNsIjpbIjYzNzdlMTMzYjc0ZTFmNWYyZWMxYzEyNSIsInVzZXIiXX0sImlhdCI6MTY2ODgxMjQ1OH0.t1eQlRwkcP7v9JxUPMo3dcGKprH-uy8ujukNI7xE3A0"
  98. // const store = createStore(authReducer)
  99. // store.subscribe(() => console.log(store.getState()))
  100. // store.dispatch(actionAuthLogin(token))
  101. /*{
  102. token: "eyJhbGc.....",
  103. payload: {
  104. "sub": {
  105. "id": "6377e133b74e1f5f2ec1c125",
  106. "login": "test5",
  107. "acl": [
  108. "6377e133b74e1f5f2ec1c125",
  109. "user"
  110. ]
  111. },
  112. "iat": 1668812458
  113. }
  114. }*/
  115. // store.dispatch(actionAuthLogout()) // {}
  116. }
  117. // cartReducer
  118. {
  119. function cartReducer(state = {}, { type, count, good }) {
  120. if (type === 'CART_ADD') {
  121. return {
  122. ...state,
  123. [good._id]: {
  124. good,
  125. count: (state[good._id] ? state[good._id].count + count : count)
  126. }
  127. }
  128. }
  129. if (type === 'CART_SUB') {
  130. if (state[good._id]) {
  131. let newCount = state[good._id].count - count
  132. if (newCount > 0) {
  133. return {
  134. ...state,
  135. [good._id]: {
  136. good,
  137. count: newCount
  138. }
  139. }
  140. } else {
  141. delete state[good._id]
  142. return { ...state }
  143. }
  144. } else {
  145. return undefined
  146. }
  147. }
  148. if (type === 'CART_DEL') {
  149. delete state[good._id]
  150. return { ...state }
  151. }
  152. if (type === 'CART_SET') {
  153. if (count > 0) {
  154. return {
  155. ...state,
  156. [good._id]: {
  157. good,
  158. count
  159. }
  160. }
  161. } else {
  162. delete state[good._id]
  163. return { ...state }
  164. }
  165. }
  166. if (type === 'CART_CLEAR') {
  167. return state = {}
  168. }
  169. return state
  170. }
  171. // экшоны:
  172. // Добавление товара.Должен добавлять новый ключ в state, или обновлять, если ключа в state ранее не было, увеличивая количество
  173. const actionCartAdd = (good, count = 1) => ({ type: 'CART_ADD', count, good })
  174. // Уменьшение количества товара.Должен уменьшать количество товара в state, или удалять его если количество будет 0 или отрицательным
  175. const actionCartSub = (good, count = 1) => ({ type: 'CART_SUB', count, good })
  176. // Удаление товара.Должен удалять ключ из state
  177. const actionCartDel = (good) => ({ type: 'CART_DEL', good })
  178. // Задание количества товара.В отличие от добавления и уменьшения, не учитывает того количества, которое уже было в корзине, а тупо назначает количество поверху(или создает новый ключ, если в корзине товара не было).Если count 0 или отрицательное число - удаляем ключ из корзины;
  179. const actionCartSet = (good, count = 1) => ({ type: 'CART_SET', count, good })
  180. // Очистка корзины.state должен стать пустым объектом { }
  181. const actionCartClear = () => ({ type: 'CART_CLEAR' })
  182. // Проверочный код
  183. const store = createStore(cartReducer)
  184. store.subscribe(() => console.log(store.getState())) //
  185. console.log(store.getState()) //{}
  186. store.dispatch(actionCartAdd({ _id: 'пиво', price: 50 }))
  187. // {пиво: {good: {_id: 'пиво', price: 50}, count: 1}}
  188. store.dispatch(actionCartAdd({ _id: 'чипсы', price: 75 }))
  189. // {
  190. // пиво: {good: {_id: 'пиво', price: 50}, count: 1},
  191. // чипсы: {good: {_id: 'чипсы', price: 75}, count: 1},
  192. //}
  193. store.dispatch(actionCartAdd({ _id: 'пиво', price: 50 }, 5))
  194. // {
  195. // пиво: {good: {_id: 'пиво', price: 50}, count: 6},
  196. // чипсы: {good: {_id: 'чипсы', price: 75}, count: 1},
  197. //}
  198. store.dispatch(actionCartSet({ _id: 'чипсы', price: 75 }, 2))
  199. // {
  200. // пиво: {good: {_id: 'пиво', price: 50}, count: 6},
  201. // чипсы: {good: {_id: 'чипсы', price: 75}, count: 2},
  202. //}
  203. store.dispatch(actionCartSub({ _id: 'пиво', price: 50 }, 4))
  204. // {
  205. // пиво: {good: {_id: 'пиво', price: 50}, count: 2},
  206. // чипсы: {good: {_id: 'чипсы', price: 75}, count: 2},
  207. //}
  208. store.dispatch(actionCartDel({ _id: 'чипсы', price: 75 }))
  209. // {
  210. // пиво: {good: {_id: 'пиво', price: 50}, count: 2},
  211. //}
  212. store.dispatch(actionCartClear()) // {}
  213. }