Чтобы работать с VK API вам необходимо будет создать приложение на сайте vk.com, и указать в настройках URL сервера, с которого вы будете выполнять запросы.
По адресу https://vk.com/apps?act=manage создайте новое приложение (веб-сайт) и заполните поля как на скриншоте, если используете локалхост и порт 3000.
<!DOCTYPE html><htmllang="en"><head><metacharset="utf-8"/><metaname="viewport"content="width=device-width, initial-scale=1, shrink-to-fit=no"/><metaname="theme-color"content="#000000"/><linkrel="manifest"href="%PUBLIC_URL%/manifest.json"/><linkrel="shortcut icon"href="%PUBLIC_URL%/favicon.ico"/><title>Redux [RU] Tutorial v2</title></head><body><noscript>
You need to enable JavaScript to run this app.
</noscript><divid="root"></div><scriptsrc="https://vk.com/js/api/openapi.js?158"></script><scriptlanguage="javascript">VK.init({apiId:XXXXXX,<!--вашномер-->});</script></body></html>
exportconstLOGIN_REQUEST='LOGIN_REQUEST';exportconstLOGIN_SUCCESS='LOGIN_SUCCESS';exportconstLOGIN_FAIL='LOGIN_FAIL';exportfunctionhandleLogin(){returnfunction(dispatch){dispatch({type:LOGIN_REQUEST,});//eslint-disable-next-line no-undefVK.Auth.login((r)=>{if(r.session){letusername=r.session.user.first_name;dispatch({type:LOGIN_SUCCESS,payload:username,});}else{dispatch({type:LOGIN_FAIL,error:true,payload:newError('Ошибка авторизации'),});}},4);// запрос прав на доступ к photo};}
Так как загрузка информации из профиля - действие асинхронное, мы использовали проверенную схему из трех действий:
XXX_REQUEST - диспатчим непосредственно перед стартом реального запроса (для юзера это выглядит, как будто во время запроса)
XXX_SUCCESS + данные - если все прошло успешно добавляем данные
ХХХ_FAIL + ошибка - если что-то пошло не так
Чтобы достать имя пользователя, мы вытащили его из response(r).session. Данные нам предоставил VK, так как мы подтвердили "разрешаю доступ" во всплывающем окне.
"Приконнектим" в <App />UserActions, и добавим новые свойства в компонент <User />
importReact,{Component}from'react';import{connect}from'react-redux';import{User}from'../components/User';import{Page}from'../components/Page';import{getPhotos}from'../actions/PageActions';import{handleLogin}from'../actions/UserActions';classAppextendsComponent{render(){// вытащили handleLoginAction из this.propsconst{user,page,getPhotosAction,handleLoginAction,}=this.props;return(<divclassName="app"><Pagephotos={page.photos}year={page.year}isFetching={page.isFetching}getPhotos={getPhotosAction}/>{/* добавили новые props для User */}<Username={user.name}isFetching={user.isFetching}error={user.error}handleLogin={handleLoginAction}/></div>);}}constmapStateToProps=(store)=>{return{user:store.user,// вытащили из стора (из редьюсера user все в переменную thid.props.user)page:store.page,};};constmapDispatchToProps=(dispatch)=>{return{getPhotosAction:(year)=>dispatch(getPhotos(year)),// "приклеили" в this.props.handleLoginAction функцию, которая умеет диспатчить handleLoginhandleLoginAction:()=>dispatch(handleLogin()),};};exportdefaultconnect(mapStateToProps,mapDispatchToProps)(App);
Здесь мы поступили так же, как когда-то для page:
подписались на кусочек стора (user)
добавили экшен и передали его в dispatch в функции handleLoginAction
кусочек стора (user) и handleLoginAction - стали доступны нам в this.props
import{LOGIN_REQUEST,LOGIN_SUCCESS,LOGIN_FAIL,}from'../actions/UserActions';constinitialState={name:'',error:'',// добавили для сохранения текста ошибкиisFetching:false,// добавили для реакции на статус "загружаю" или нет};exportfunctionuserReducer(state=initialState,action){switch(action.type){caseLOGIN_REQUEST:return{...state,isFetching:true,error:''};caseLOGIN_SUCCESS:return{...state,isFetching:false,name:action.payload,};caseLOGIN_FAIL:return{...state,isFetching:false,error:action.payload.message,};default:returnstate;}}
В редьюсере есть интересные моменты:
когда мы начали делать запрос (LOGIN_REQUEST) мы очищаем error. Например, была ошибка, мы стали делать новый запрос - ошибка очистилась;
если случился LOGIN_SUCCESS - мы в name записываем action.payload (а как вы помните, там мы передаем в строке имя пользователя) и ставим статус загрузки - false (то есть, не загружается, ибо загрузилось);
если случился LOGIN_FAIL - опять же, загружаю? Нет, значит isFetching - false. Ошибка? Да - запиши в поле error.
В коде компонента <User /> ничего необычного нет. Рендерим шаблончик (в зависимости от props).
Сейчас если кликнуть на "войти" - всплывет VK окно с подтверждением прав доступа (первый раз). После подтверждения прав, вместо кнопки войти появляется надпись "Привет, ХХХ". При перезагрузке сайта и повторных нажатиях на "войти" - VK окно мгновенно закрывается, а кнопка вновь изменяется на "Привет, XXX". Как всегда, доблестный логгер пишет в консоли - что происходит.
Нам нужно практически повторить, все что написано выше, только для блока Page.
Поэтому, наконец-то появилась самостоятельная задача. Я крайне рекомендую с ней посидеть, так как это практически конец основного материала. Если у вас что-то не получится - вы поймете что нужно закрепить, что перечитать. Не торопитесь смотреть ответ, попробуйте сделать это сами, таким образом вы получите от этого учебника гораздо больше.
Задача: используя метод photos.getAll вытащите свои фотографии из VK за год, выбранный кнопкой. Отсортируйте их в обратном порядке по лайкам, чтобы самая популярная фото оказалась первой.
После скриншотов есть подсказка: функция, которая делает запрос за фото.
letphotosArr=[];letcached=false;functionmakeYearPhotos(photos,selectedYear){letcreatedYear,yearPhotos=[];photos.forEach((item)=>{createdYear=newDate(item.date*1000).getFullYear();if(createdYear===selectedYear){yearPhotos.push(item);}});yearPhotos.sort((a,b)=>b.likes.count-a.likes.count);returnyearPhotos;}functiongetMorePhotos(offset,count,year,dispatch){//eslint-disable-next-line no-undefVK.Api.call('photos.getAll',{extended:1,count:count,offset:offset,v:'5.80',},(r)=>{try{photosArr=photosArr.concat(r.response.items);if(offset<=r.response.count){offset+=200;// максимальное количество фото которое можно получить за 1 запросgetMorePhotos(offset,count,year,dispatch);}else{letphotos=makeYearPhotos(photosArr,year);cached=true;dispatch({type:GET_PHOTOS_SUCCESS,payload:photos,});}}catch(e){dispatch({type:GET_PHOTOS_FAIL,error:true,payload:newError(e),});}});}
Так как я не нашел опцию передачи года, то просто выгрузил все фото, по 200 штук за один запрос. Это несколько избыточно, как и тот факт, что мы вызываем функцию makeYearPhotos, вместо того чтобы один раз загрузить все фото и "разместить" их по годам. Я оставил код из первого издания учебника, чтобы не усложнять пример.
exportconstGET_PHOTOS_REQUEST='GET_PHOTOS_REQUEST';exportconstGET_PHOTOS_SUCCESS='GET_PHOTOS_SUCCESS';exportconstGET_PHOTOS_FAIL='GET_PHOTOS_FAIL';letphotosArr=[];letcached=false;functionmakeYearPhotos(photos,selectedYear){letcreatedYear,yearPhotos=[];photos.forEach((item)=>{createdYear=newDate(item.date*1000).getFullYear();if(createdYear===selectedYear){yearPhotos.push(item);}});yearPhotos.sort((a,b)=>b.likes.count-a.likes.count);returnyearPhotos;}functiongetMorePhotos(offset,count,year,dispatch){//eslint-disable-next-line no-undefVK.Api.call('photos.getAll',{extended:1,count:count,offset:offset,v:'5.80',},(r)=>{try{photosArr=photosArr.concat(r.response.items);if(offset<=r.response.count){offset+=200;// максимальное количество фото которое можно получить за 1 запросgetMorePhotos(offset,count,year,dispatch);}else{letphotos=makeYearPhotos(photosArr,year);cached=true;dispatch({type:GET_PHOTOS_SUCCESS,payload:photos,});}}catch(e){dispatch({type:GET_PHOTOS_FAIL,error:true,payload:newError(e),});}});}exportfunctiongetPhotos(year){return(dispatch)=>{dispatch({type:GET_PHOTOS_REQUEST,payload:year,});if(cached){letphotos=makeYearPhotos(photosArr,year);dispatch({type:GET_PHOTOS_SUCCESS,payload:photos,});}else{getMorePhotos(0,200,year,dispatch);}};}
makeYearPhotos и getMorePhotos можно вынести в папку utils, как вспомогательные функции.
Главное здесь, что мы по прежнему вызываем действия (dispatch actions). Все так, как было в самом начале, просто добавилось немного больше логики для получения фото. Алгоритм получения всех фото (да и необходимость получения всех) - оставляю без комментариев. Мне кажется, это приемлемый способ.
Исправив редьюсер и отрисовку в компоненте, мы закончим начатое.
Как вы заметили, мы использовали index в качестве ключа для наших div'ов. Запустите пример, попробуйте поменять года. Возможно, вы словите баг, когда у элементов с одинаковым индексом изображение меняется с задержкой. Проблема в том, что мы использовали индекс для элементов, которые изменяются (а индекс-то остается прежним! Ключ в итоге не изменяется, итого реакт "путается").
Чтобы этого избежать, сделайте ключ уникальным (например, для этого у нас есть id в ответе от VK API):