Взаимодействуем с VK
ОБНОВЛЕНИЕ 2018: Вышло второе издание (современный код и версии пакетов, данное издание УСТАРЕЛО)
На канале так же проводятся бесплатные вебинары, публикуются переводы и авторские материалы, присоединяйтесь!
Взаимодействуем с VK
Чтобы работать с VK API вам необходимо будет создать приложение на сайте vk.com, и указать в настройках URL сервера, с которого вы будете выполнять запросы.
Localhost не поддерживается.
Интеграция VK API
Необходимо добавить скрипт openapi перед нашей сборкой - bundle.js, а так же вызвать VK.init
<!DOCTYPE html>
<html>
<head>
<title>Redux [RU]Tutorial</title>
</head>
<body>
<div id="root" class="container-fluid">
</div>
<script src="//vk.com/js/api/openapi.js"></script>
<script src="/static/bundle.js"></script>
<script language="javascript">
VK.init({
apiId: 5087365
});
</script>
</body>
</html>
Авторизация
Создадим действия для User.
src/actions/UserActions.js
import {
LOGIN_REQUEST,
LOGIN_SUCCES,
LOGIN_FAIL
} from '../constants/User'
export function handleLogin() {
return function(dispatch) {
dispatch({
type: LOGIN_REQUEST
})
VK.Auth.login((r) => { // eslint-disable-line no-undef
if (r.session) {
let username = r.session.user.first_name;
dispatch({
type: LOGIN_SUCCES,
payload: username
})
} else {
dispatch({
type: LOGIN_FAIL,
error: true,
payload: new Error('Ошибка авторизации')
})
}
},4); // запрос прав на доступ к photo
}
}
Проверьте список констант:
export const LOGIN_REQUEST = 'LOGIN_REQUEST'
export const LOGIN_SUCCES = 'LOGIN_SUCCES'
export const LOGIN_FAIL = 'LOGIN_FAIL'
"Приконнектим" в <App />
UserActions, и добавим новые свойства в компонент <User />
src/containers/App.js
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import User from '../components/User'
import Page from '../components/Page'
import * as pageActions from '../actions/PageActions'
import * as userActions from '../actions/UserActions'
class App extends Component {
render() {
const { user, page } = this.props
const { getPhotos } = this.props.pageActions
const { handleLogin } = this.props.userActions
return <div className='row'>
<Page photos={page.photos} year={page.year} getPhotos={getPhotos} fetching={page.fetching} />
<User name={user.name} handleLogin={handleLogin} error={user.error} />
</div>
}
}
function mapStateToProps(state) {
return {
user: state.user,
page: state.page
}
}
function mapDispatchToProps(dispatch) {
return {
pageActions: bindActionCreators(pageActions, dispatch),
userActions: bindActionCreators(userActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
Обновим reducer user:
src/reducers/user.js
import {
LOGIN_SUCCES,
LOGIN_FAIL
} from '../constants/User'
const initialState = {
name: '',
error: ''
}
export default function user(state = initialState, action) {
switch(action.type) {
case LOGIN_SUCCES:
return { ...state, name: action.payload, error: '' }
case LOGIN_FAIL:
return { ...state, error: action.payload.message }
default:
return state
}
}
И покажем все это в компоненте <User />
src/components/User.js
import React, { PropTypes, Component } from 'react'
export default class User extends Component {
render() {
const { name, error } = this.props
let template
if (name) {
template = <p>Привет, {name}!</p>
} else {
template = <button className='btn' onClick={this.props.handleLogin}>Войти</button>
}
return <div className='ib user'>
{template}
{error ? <p className='error'> {error}. <br /> Попробуйте еще раз.</p> : ''}
</div>
}
}
User.propTypes = {
name: PropTypes.string.isRequired,
handleLogin: PropTypes.func.isRequired,
error: PropTypes.string.isRequired
}
Сейчас если кликнуть на "войти" - всплывет VK окно с подтверждением прав доступа (первый раз). После подтверждения прав, вместо кнопки войти появляется надпись "Привет, ХХХ". При перезагрузке сайта и повторных нажатиях на "войти" - VK окно мгновенно закрывается, а кнопка вновь изменяется на "Привет, XXX". Неплохо бы было проверять "статус", например в componentWillMount, но оставлю это на "домашку".
Как всегда, доблестный логгер пишет в консоли - что происходит.
Загрузка фото
Нам нужно практически повторить, все что написано выше, только для блока Page.
Можете попробовать сами, используя метод photos.getAll
из VK API.
Для начала, проверим список констант:
src/constants/Page.js
export const GET_PHOTOS_REQUEST = 'GET_PHOTOS_REQUEST'
export const GET_PHOTOS_SUCCESS = 'GET_PHOTOS_SUCCESS'
export const GET_PHOTOS_FAIL = 'GET_PHOTOS_FAIL'
Напишем немало кода, для загрузки фото:
src/actions/PageActions.js
import {
GET_PHOTOS_REQUEST,
GET_PHOTOS_FAIL,
GET_PHOTOS_SUCCESS
} from '../constants/Page'
let photosArr = []
let cached = false
function makeYearPhotos(photos, selectedYear) {
let createdYear, yearPhotos = []
photos.forEach((item) => {
createdYear = new Date(item.created*1000).getFullYear()
if (createdYear === selectedYear ) {
yearPhotos.push(item)
}
})
yearPhotos.sort((a,b) => b.likes.count-a.likes.count);
return yearPhotos
}
function getMorePhotos(offset, count, year, dispatch) {
VK.Api.call('photos.getAll', {extended:1, count: count, offset: offset},(r) => { // eslint-disable-line no-undef
try {
if (offset <= r.response[0] - count) {
offset+=200;
photosArr = photosArr.concat(r.response)
getMorePhotos(offset,count,year,dispatch)
} else {
let photos = makeYearPhotos(photosArr, year)
cached = true
dispatch({
type: GET_PHOTOS_SUCCESS,
payload: photos
})
}
}
catch(e) {
dispatch({
type: GET_PHOTOS_FAIL,
error: true,
payload: new Error(e)
})
}
})
}
export function getPhotos(year) {
return (dispatch) => {
dispatch({
type: GET_PHOTOS_REQUEST,
payload: year
})
if (cached) {
let photos = makeYearPhotos(photosArr, year)
dispatch({
type: GET_PHOTOS_SUCCESS,
payload: photos
})
} else {
getMorePhotos(0,200,year,dispatch)
}
}
}
makeYearPhotos
и getMorePhotos
можно вынести в папку utils, как вспомогательные функции.
Главное здесь, что мы по прежнему вызываем действия (dispatch actions). Все так, как было в самом начале, просто добавилось немного больше логики для получения фото. Алгоритм получения всех фото (да и необходимость получения всех) - оставляю без комментариев. Мне кажется, это приемлемый способ.
Чтобы потестировать показ ошибок, достаточно просто исправить цифру 200 на 2 или 20. VK с любовью вам ответит, что вы мягко-говоря, очень настойчиво обращаетись к API ;)
Исправив редьюсер и отрисовку в компоненте, мы закончим начатое.
src/reducers/page.js
import {
GET_PHOTOS_REQUEST,
GET_PHOTOS_SUCCESS,
GET_PHOTOS_FAIL
} from '../constants/Page'
const initialState = {
year: 2016,
photos: [],
fetching: false,
error: ''
}
export default function page(state = initialState, action) {
switch (action.type) {
case GET_PHOTOS_REQUEST:
return { ...state, year: action.payload, fetching: true, error: '' }
case GET_PHOTOS_SUCCESS:
return { ...state, photos: action.payload, fetching: false, error: '' }
case GET_PHOTOS_FAIL:
return { ...state, error: action.payload.message, fetching: false }
default:
return state;
}
}
src/components/Page.js
import React, { PropTypes, Component } from 'react'
export default class Page extends Component {
onYearBtnClick(e) {
this.props.getPhotos(+e.target.innerText)
}
render() {
const { year, photos, fetching, error } = this.props
const years = [2016,2015,2014,2013,2012,2011,2010]
return <div className='ib page'>
<p>
{ years.map((item,index) => <button className='btn' key={index} onClick={::this.onYearBtnClick}>{item}</button> )}
</p>
<h3>{year} год [{photos.length}]</h3>
{ error ? <p className='error'> Во время загрузки фото произошла ошибка</p> : '' }
{
fetching ?
<p>Загрузка...</p>
:
photos.map((entry, index) =>
<div key={index} className='photo'>
<p><img src={entry.src} /></p>
<p>{entry.likes.count} ❤</p>
</div>
)
}
</div>
}
}
Page.propTypes = {
year: PropTypes.number.isRequired,
photos: PropTypes.array.isRequired,
getPhotos: PropTypes.func.isRequired,
error: PropTypes.string.isRequired
}
Итого: Вы научились выполнять асинхронные запросы и корректно показывать прелоадер, ошибки или успешный результат.
Исходный код на текущий момент.
P.S. css тоже был слегка подправлен.
Last updated
Was this helpful?