翻譯|Redux和GraphQL入門


title: 翻譯|Redux和GraphQL入門
date: 2017-04-11 23:15:32
categories: 翻譯
tags: Redux


當GraphQL發布以來,非常清楚的顯示出,他將會成為非常好的技術.社區都在耐心的等待技術評價.
但是你可能和我一樣,發現文檔比我們期待的更難理解.可能的原因是由于GraphQL和Relay的聯合使用.

我也感覺到了你的痛苦.我的大腦都要融化掉了,我告訴我自己我將會嘗試在其他的框架里是用它.我做到了!這一次我僅僅把關注點放在GraphQL自身,其他的地方保持盡可能的簡單.

Sharing is Caring

這個教程的配置部分盡可能的簡單,結合GraphQL和Redux.減少復雜的部分,所有的內容你可以直接從這里看到(指代碼部分).

我們將使用Redux來代替Relay,在服務器上使用es5而不是es6/babel-node.所有的GraphQL的東西都保持盡可能的簡單.

下面配置一下項目

項目文件配置

創建新文件件(graphql-app).
需要一個package.json.

 npm init

需要在服務器上安裝一下模塊:graphql-js,express-graphql,express,webpack和webpack-dev-server.

編寫服務器的編碼使用es5,避免編譯過程.

創建sevsr.js文件,導入我們安裝的模塊
server.js

 var webpack = require(‘webpack’);
var WebpackDevServer = require(‘webpack-dev-server’);
var express = require(‘express’);
var graphqlHTTP = require(‘express-graphql’);
var graphql = require(‘graphql’);
//下面是有關graphql使用的配置,有對象和類型
var GraphQLSchema = graphql.GraphQLSchema;
var GraphQLObjectType = graphql.GraphQLObjectType;
var GraphQLString = graphql.GraphQLString;
var GraphQLInt = graphql.GraphQLInt;

你可以看到我們給graphQL的類型定義了變量,后面我們要使用這些變量.

接著我們為GraphQL創建可以獲取的數據.這里使用Goldbergs的數據作為來源.

我們的數據

 var goldbergs = {
 1: {
   character: "Beverly Goldberg",
   actor: "Wendi McLendon-Covey",
   role: "matriarch",
   traits: "embarrassing, overprotective",
   id: 1
 },
 2: {
   character: "Murray Goldberg",
   actor: "Jeff Garlin",
   role: "patriarch",
   traits: "gruff, lazy",
   id: 2
 },
 3: {
   character: "Erica Goldberg",
   actor: "Hayley Orrantia",
   role: "oldest child",
   traits: "rebellious, nonchalant",
   id: 3
 },
 4: {
   character: "Barry Goldberg",
   actor: "Troy Gentile",
   role: "middle child",
   traits: "dim-witted, untalented",
   id: 4
 },
 5: {
   character: "Adam Goldberg",
   actor: "Sean Giambrone",
   role: "youngest child",
   traits: "geeky, pop-culture obsessed",
   id: 5
 },
 6: {
   character: "Albert 'Pops' Solomon",
   actor: "George Segal",
   role: "grandfather",
   traits: "goofy, laid back",
   id: 6
 }
}

GraophQL

GraphQL從簡化的角度考慮,有一個類型系統構成-這是我們用來理解他的心理模型-我們將看到這里有三種”類型”.

  1. 模型的類型
  2. 查詢的類型
  3. schema的類型

在實際的編碼中,類型可能比這個簡單,這里只是為了到入門的目的,所以比較簡單

模型的類型

我們將創建一個”模型類型”,實際相當于實際的數據的鏡像.

 var goldbergType = new GraphQLObjectType({
  name: "Goldberg",
  description: "Member of The Goldbergs",
  fields: {
   character: {
     type: GraphQLString,
     description: "Name of the character",
   },
   actor: {
     type: GraphQLString,
     description: "Actor playing the character",
   },
   role: {
     type: GraphQLString,
     description: "Family role"
   },
   traits: {
     type: GraphQLString,
     description: "Traits this Goldberg is known for"
   },
   id: {
     type: GraphQLInt,
     description: "ID of this Goldberg"
   }
 }
});

我們創建了一個GraphQLObjectType的對象實例,取名為”Goldberg”.
在“fields”下,每一個“type”表明一個期待的類型.例如 string(GraphQLString)最為演員角色的類型,int(GraphQLInt)作為Id的類型約束.

你可能也注意到了”description”字段,GraphQL自帶說明文檔.當我們結合express-graphql使用GraphiQL的時候可以在action中剛看到這個描述內容.

Query Type

“Query type”定義了我們怎么查詢我們的數據

var queryType = new GraphQLObjectType({
  name: "query",
  description: "Goldberg query",
  fields: {
    goldberg: {
      type: goldbergType,
      args: {
        id: {
          type: GraphQLInt
        }
      },
      resolve: function(_, args){
        return getGoldberg(args.id)
      }
    }
  }
});

“query type”也是GraphQLObjectType的實例.只是用于不同的目的.
我們創建goldberg這個查詢字段,設定的類型是goldbergType.在args(參數)下我們可以看到新的goldberg字段,它將接受id作為參數.

但我們解析查詢的時候,我們返回gegGoldberg()函數的調用返回值

 function getGoldberg(id) {
 return goldbergs[id]
}

從查詢中的id從data中返回其中一個Goldberg.

Schema type

最終”schema type”把類型放到一起.

為schema提供服務

我們可以使用express和graphqlHTTP 中間件來提供schma服務.

 var graphQLServer = express();
graphQLServer.use('/', graphqlHTTP({ schema: schema, graphiql: true }));
graphQLServer.listen(8080);
console.log("The GraphQL Server is running.")
node server

瀏覽器打開http://localhost:8080/.可以看到GraphiQL IDE工作了.
如果我們執行了查詢

 { 
 goldberg(id: 2) { 
   id,
   character
 }
}

返回的結果是

 {
 "data": {
   "goldberg": {
     "id": 2,
     "character": "Murray Goldberg"
   }
  }
}

再做一些其他查詢也非常的有意思.

提示:在屏幕的頂部右邊,有一個按鈕,標簽為”Docs”,如果我們點擊按鈕,可以看到之前在”description”中添加的字段內容.可以探索一下文檔.

為app提供服務

為了在我們app的前端使用GraphQL,需要安裝babel,babel-loader以及一組babel-presets的約定.

 npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-stage-0 babel-preset-react

創建文件.babelrc,這個文件告訴babel,我們的預先設定.

 {
 "presets": ["es2015", "stage-0", "react"]
}

創建一個新的index.js文件.目前還沒有內容.

創建新的文件夾static,在文件夾中添加index.html文件.

 <div id="example"></div>
<script src="/static/bundle.js"></script>
<h3>hello world</h3>

現在我們的項目結構看起來像這樣

graphql-app
| -- index.js
| -- server.js
| -- package.json
| -- .babelrc
| -- static
   | -- index.hml

在server.js文件中,我們需要配置webpack,借助babel打包項目的js文件.

在graphQLServer.listen(8080)下

 var compiler = webpack({
  entry: "./index.js",
  output: {
    path: __dirname,
    filename: "bundle.js",
    publicPath: "/static/"
  },
  module: {
    loaders: [
      { test: /\.js$/, 
        exclude: /node_modules/, 
        loader: "babel-loader"
      }
    ]
  }
});

Webpack 將會接受index.js文件,編譯一個est的版本到/static/bundle.js文件.

接下來我們創建一個新的WebpackDevServer 來提供bundled的項目.

 var app = new WebpackDevServer(compiler, {
 contentBase: "/public/",
 proxy: {"/graphql": `http://localhost:${8080}`},
 publicPath: "/static/",
 stats: {colors: true}
});
app.use("/", express.static("static"));
app.listen(3000);
console.log("The App Server is running.")

proxy字段添加了我們已經創建的GraphQL服務到我們的app server,這可以使我們直接在app內部進行查詢,不會有跨域問題.

啟動一下

noder server

瀏覽器打開http://localhost:3000,我們會看到”hello world”的消息.
再到http://localhost:3000/graphql.

React和Redux

為了添加react和react-redux,app需要額外的組件:React,Redux,React-Redux,Redux-thunk和Immutable.

npm install --save react react-dom redux react-redux redux-thunk immutable

因為我們使用babel配置了webpack,我們可以在前端使用es6

從static/index.html文件中刪除掉”hello world”,使用React添加新的信息.

 import React from "react";
import ReactDOM from "react-dom";
const Main = React.createClass({
  render: () => {
    return (
      <div>
        <p>hello react!</p>
      </div>
    )
  }
});
ReactDOM.render(
 <Main />,
 document.getElementById("example")
);

重新啟動localhost:300,可以看到信息.

Reducer

添加新的文件夾,取名”app”最為子文件夾

 | -- app
   | -- actions
   | -- components
   | -- reducers

在reducerS 文件夾中創建reducer.js的文件,里面將執行我們的reducer函數.

我們會使用利用Immuatable模塊為state服務,以便我們形成好的習慣.

 import Immutable from "immutable";
const immutableState = Immutable.Map({
  fetching: false,
  data: Immutable.Map({})
})

我們的state有兩個字段-一個讓我們知道是否在查詢/等待響應的中間階段,另一個包含著返回的響應數據.

下一步我么把ImmutableState添加到reducer 函數中

 export const queryReducer = (state = immutableState, action) => {
  switch (action.type) {
    case "STARTING_REQUEST":
      return state.set("fetching", true);
    case "FINISHED_REQUEST":
      return state.set("fetching", false)
             .set("data", Immutable.Map(action.response.data.goldberg));
    default:
      return state
  }
}

當我們在執行“STARING_REQUEST” action的時候,分發的動作改變”fecthing”的state 為true,表示在獲取數據中.

當執行“FINISHED_REQUEST” action的時候,分發的工作改變 “feching”的state為false,data的state設定為我們的響應數據.

Store

返回到index.js文件,我們想在reducer之外創建store,store接入到我們的主組件.我們需要借助redux和react-redux的助手函數來把剛剛創建的reducer導入store.

還需要使用redux-thunk 中間件來協助后面的數據請求動過.

import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import { queryReducer } from "./app/reducers/reducers.js";
import thunkMiddleware from "redux-thunk";

首先我們應用redux-thunk中間件

 const createStoreWithMiddleware = applyMiddleware(
  thunkMiddleware
)(createStore)

然后在Redux Provider中包裝我們的主組件,傳遞queryReducer到createStoreWithMiddleware.

 ReactDOM.render(
  <Provider store={createStoreWithMiddleware(queryReducer)}>
    <Main />
  </Provider>,
  document.getElementById("example")
);

完成了!創建了store.

Actions

在actions文件夾中創建新文件actions.js

我們需要創建兩個action來分發動作到我們的reducer,其中之一為“STARTING_REQUEST”,另一個為”FINISHED_REQUES”

const startingRequest = () => {
  return {
    type: "STARTING_REQUEST"
  }
}
const finishedRequest = (response) => {
  return {
    type: "FINISHED_REQUEST",
    response: response
  }
}

在store中之前應用的中間件redux-thunk是一件非常偉大的事情,當一個action返回一個函數,這個函數可以使用dispatch來注入到reducer.(譯注:對于一部操作,返回響應值以后,可以在發起一個dispatch來通知reducer對state做出改變).

在一個新的getGraph action中,使用了兩次dispatch()

export const getGraph = (payload) => {
  return dispatch => {
    dispatch(startingRequest());
    return new Promise(function(resolve, reject) {
      let request=new XMLHttpRequest();
      request.open("POST", "/graphql", true);
      request.setRequestHeader("Content-Type",
                               "application/graphql");
      request.send(payload);
      request.onreadystatechange = () => {
        if (request.readyState === 4) {
          resolve(request.responseText)
        }
      }
    }).then(response =>
            dispatch(finishedRequest(JSON.parse(response))))
  }
}

當getGraph()函數調用的時候,我們dispatch startingRequest(),表示開始一個新的查詢.然后開始一個異步的請求(提示:”header”中有application/graphql的類型).當我們的查詢完成的時候,我們dispatch finishedRequest() action,提供我們查詢的結果.

Component

在”component”文件夾中,我們創建一個新的文件, Query.js文件

我們需要導入react,幾個助手函數,還有剛剛創建的getGraph函數.

import React from ‘react’;
import { connect } from ‘react-redux’;
import { getGraph } from ‘../actions/actions.js’;

目前我們創建了空的出查詢組件

let Query = React.createClass({
  render() {
    return (
      <div>
      </div>
    )
  }
});

我們要在組件中掛載我們的store和dispatch方法,方式是通過創建container組件和react-redux connect()函數

const mapStateToProps = (state) => {
  return {
    store: state
  }
};
export const QueryContainer = connect(
 mapStateToProps
)(Query);

在我們的Query組件中,我們需要接入componentDidMount 生命周期函數,從而可以在組件掛載的時候獲取數據.

let Query = React.createClass({
  componentDidMount() {
    this.props.dispatch(
      getGraph("{goldberg(id: 2) {id, character, actor}}")
    );
  }
})

然后我們要添加組件來用于填充獲取的響應的數據,一個提交額外查詢的按鈕.我們想知道在數據查詢過程中的狀態,并且顯示在頁面中.

let Query = React.createClass({
  componentDidMount() {
    this.props.dispatch(
      getGraph("{goldberg(id: 2) {id, character, actor}}")
    );
  },
  render() {
    let dispatch = this.props.dispatch;
    let fetchInProgress = String(this.props.store.get('fetching'));
    let queryText;
    let goldberg = this.props.store.get('data').toObject();
    return (
      <div>
        <p>Fetch in progress: {fetchInProgress}</p>
        <h3>{ goldberg.character }</h3>
        <p>{ goldberg.actor }</p>
        <p>{ goldberg.role }</p>
        <p>{ goldberg.traits }</p>
        <input ref={node => {queryText = node}}></input>
        <button onClick={() => {
          dispatch(getGraph(queryText.value))}
        }>
          query
        </button>
      </div>
    )
  }
});

上面這一步做完以后,最后一件事情就是把QueryContainer組件添加到我們的主組件.

index.js

 import { QueryContainer } from “./app/components/Query.js”;

使用QueryConatiner組件替代”hello react”組件

 const Main = () => {
  return (
    <div>
      <QueryContainer />
    </div>
  )
};

完成!現在運行編制好的GraphQL查詢就可以獲得核心內容.試著查詢:{gold-berg(id:4)}{id,charactar,actor,traits},看看可以獲得什么結果.

感謝

感謝閱讀,我希望這篇文章能對你有幫助.你可以在這里查看源代碼.現在我們使用Redux和GraphQL構建了非常好的app.

另外感謝Dan Abramov指出教程中的一個錯誤.

Resources

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容