Slide 1

Slide 1 text

Graphql with Reactjs

Slide 2

Slide 2 text

名前: @brn (青野健利) 職業: フロントエンドエンジニア・ネイティブエンジニア 会社: Cyberagent アドテクスタジオ RightSegment・AI Messenger ブログ: http://abcdef.gets.b6n.ch/

Slide 3

Slide 3 text

Why graphql? フロントエンド主導でAPIの設計ができる。 より複雑化していくSPAの設計に向いている。

Slide 4

Slide 4 text

Why React? Viewを宣言的に書ける。 状態をViewから切り離すことができる。 これらをそこそこの速度で実現している。

Slide 5

Slide 5 text

React+Graphql Reactでgraphqlを使うなら、できるだけ親和性が高いライブラリを選びたい。

Slide 6

Slide 6 text

Relay Facebook製のライブラリで、Reactを組み合わせる前提で作られた。 Facebookでは実際に使用されている。

Slide 7

Slide 7 text

Relay インストール npm install react-relay --save

Slide 8

Slide 8 text

Relay import React from 'react'; import Relay from 'react-relay'; class App extends React.Component { render() { return (
    {this.props.todos.todos.edges.map(edge =>
  • {edge.node.name} (ID: {edge.node.id})
  • )}
); } } Component

Slide 9

Slide 9 text

Relay export default Relay.createContainer(App, { fragments: { todos: () => Relay.QL` fragment on Todos { todos(first: 1) { edges { node { id value }, }, }, } `, }, }); Container

Slide 10

Slide 10 text

Relay Relay Container Component React Component Server Parent Component Relay Store Props Read Query Response Response

Slide 11

Slide 11 text

Relay import Relay from 'react-relay'; export default class extends Relay.Route { static queries = { viewer: () => Relay.QL` query { todos } `, }; static routeName = 'AppHomeRoute'; } Route

Slide 12

Slide 12 text

Relay import 'babel-polyfill'; import App from './components/App'; import AppHomeRoute from './routes/AppHomeRoute'; import React from 'react'; import ReactDOM from 'react-dom'; import Relay from 'react-relay'; ReactDOM.render( , document.getElementById('root') ); App

Slide 13

Slide 13 text

Relay これで完成!と言いたいところですが… RelayはBabelを前提に作られており、 Relay.QL`` の部分はランタイムでは実行できません。 Babelによってコンパイルされることで関数になり、実行できるようになります。 なので、早速 npm install babel-relay-plugin でインストールします。

Slide 14

Slide 14 text

Relay 今度こそ! Error : Invalid introspection data supplied to `getBabelRelayPlugin()`. The resulting schema is not an object with a `__schema` property or a schema IDL language. while parsing file…

Slide 15

Slide 15 text

Relay どうやらSchema.jsonと言うものが必要なようなのですが、 作り方も何なのかもドキュメントがなく、ただ、 relay-starter-kitというgitリポジトリにあるとだけ書いてあります…

Slide 16

Slide 16 text

Relay

Slide 17

Slide 17 text

Relay schema.jsonの正体はGraphQLの型定義のメタデータで、 introspectionQueryという機能を利用し自動生成する。 relay-starter-kitのscriptsディレクトリに詳細があるので、そこをみるとよろし。 私はもうRelayはやめます!

Slide 18

Slide 18 text

Relay ここが駄目だよRelayちゃん とにかくAPIが複雑。すぐ使えるようなものではない上に Documentがexampleベースなのだが、そのexampleも複雑なので、 読む気がしない。 Babel前提なのはいいが、schema.json作れとか面倒すぎる。 Relay.Routeとか意味がわかりづらすぎ。 Reduxとも組み合わせづらい relay-modernである程度はよくなりそうだが…

Slide 19

Slide 19 text

React-apollo そこで、react-apolloです

Slide 20

Slide 20 text

React-apollo React-apolloとは React環境(React-native等)ならどこでも動くGraphQLクライアント ビルド設定等不要で、jsランタイムさえあれば動きます。 基本的にかなりシンプルで使いやすいです。

Slide 21

Slide 21 text

React-apollo インストール npm install react-apollo --save

Slide 22

Slide 22 text

React-apollo import { ApolloClient, createNetworkInterface } from 'react- apollo'; export const client = new ApolloClient({ networkInterface: createNetworkInterface({ uri: 'http://localhost:3000/graphql' }) }); Client

Slide 23

Slide 23 text

React-apollo import { gql, graphql } from 'react-apollo'; import React from 'react'; const QUERY = gql` query TodoAppQuery($id: ID) { todos(id: $id) { id text } }`; @graphql(QUERY, { options(props) { return {id: props.id} } }) export default class TodoApp extends React.Component { render() { const {fetchMore, refetch, todos = []} = this.props.data; return (
refetch()}>Refresh
    {todos.map(todo =>
  • {todo.text}
  • )}
); } } Component

Slide 24

Slide 24 text

React-apollo import { ApolloProvider } from 'react-apollo'; import ReactDOM from 'react-dom'; import React from 'react'; import {client} from './client'; import TodoApp from './todo'; ReactDOM.render(( ), document.querySelector('#app')); App

Slide 25

Slide 25 text

React-apollo 後はBabel等でjsxをコンパイルすれば動きます。 特に面倒な設定は無いはず!

Slide 26

Slide 26 text

React-apollo Client Apolloがサーバにアクセスする際のエンドポイントを指定します。 ここにはMiddlewareも指定できて、リクエスト・レスポンスの編集も可能です。

Slide 27

Slide 27 text

React-apollo import ApolloClient, { createNetworkInterface } from 'apollo-client’; const networkInterface = createNetworkInterface({ uri: '/graphql' }); networkInterface.use([{ applyMiddleware(req, next) { if (!req.options.headers) { req.options.headers = {}; // Create the header object if needed. } req.options.headers['authorization'] = localStorage.getItem('token') ? localStorage.getItem('token') : null; next(); } }]); const client = new ApolloClient({ networkInterface, }); App

Slide 28

Slide 28 text

React-apollo Component apolloでReactComponentをラップすることで、 GraphQLのリクエストを行える様にする。 ここはRelayと同じだが、Relayと違ってES Decoratorを利用する事ができるのと、 コンポーネント側に便利なPropsが提供されている。

Slide 29

Slide 29 text

React-apollo data: { loading: false, error: null, variables: { id: 'asdf' }, refetch() { ... }, fetchMore() { ... }, startPolling() { ... }, stopPolling() { ... }, // ... more methods } this.props.data配下に便利なメソッド、プロパティが定義される

Slide 30

Slide 30 text

React-apollo ApolloProvider 最後にApolloProviderでReactComponentTreeにGraphQLリクエスト機能を渡す。 ここで、ReduxのStoreと連携することも可能

Slide 31

Slide 31 text

React-apollo+Redux import { gql, graphql } from 'react-apollo'; import {connect} from 'react-redux'; import React from 'react'; import {addTodo, getTodos} from './action'; @connect((state) => state, (dispatch) => ({ addTodo(todo) {return dispatch(addTodo(todo))}, getTodos(todo) {return dispatch(getTodos(todo))} })) @graphql(ADD_QUERY, { props({ownProps, mutate}) { return { addTodo(text) { mutate({variables: {text}}).then(t => ownProps.addTodo(t.data.addTodo)); } } } }) @graphql(QUERY, { options(props) { return {id: props.id}; }, skip: props => typeof window === 'object' }) export default class TodoApp extends React.Component { constructor(p, c) { super(p, c); } render() { const {todosRoot: {todos = []}, addTodo} = this.props; return (
this._handleInput(e)}/> addTodo(this._value)}>add
this.props.apollo.refetch()}>Refresh
    {todos.map(todo =>
  • {todo.text}
  • ))}
); } } Component

Slide 32

Slide 32 text

React-apollo+Redux import { createStore, combineReducers, applyMiddleware, compose } from 'redux’; import {ApolloClient} from 'react-apollo'; import todoReducer from './reducer'; export default (client) => { return createStore( combineReducers({ todosRoot: todoReducer, apollo: client.reducer() }), typeof window === 'object'? window.__APOLLO_STATE__: {}, // initial state compose( applyMiddleware(client.middleware()), (typeof window === 'object' && typeof indow.__REDUX_DEVTOOLS_EXTENSION__ ! == 'undefined') ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f ) ); }; Store

Slide 33

Slide 33 text

React-apollo SSR React-apolloはServerSideRenderingにも対応していて、 簡単な設定でSSRできます!

Slide 34

Slide 34 text

React-apollo SSR import 'isomorphic-fetch'; import fs from 'fs'; import express from 'express'; import ejs from 'ejs'; import React from 'react'; import TodoApp from '../client/src/todo.jsx'; import {client} from '../client/src/client'; import {renderToString} from 'react-dom/server'; import {ApolloProvider, getDataFromTree} from 'react-apollo'; const app = express(); const template = fs.readFileSync('./index.html', 'utf8'); const tree = ApolloProvider>; app.get('/', (req, res) => { getDataFromTree(tree).then(v => { res.end(ejs.render(template, {dom: renderToString(tree)})); }); }); app.listen(3000);

Slide 35

Slide 35 text

React-apollo SSR SSRで重要な点 •  isomorphic-fetchのインストール •  getDataFromTreeで実際にリクエストを行う •  初期状態をhtml上にjsonで渡す。

Slide 36

Slide 36 text

React-apollo SSR まとめ react-apolloとnodejsでisomorphicなGraphQL生活を!