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從簡化的角度考慮,有一個類型系統構成-這是我們用來理解他的心理模型-我們將看到這里有三種”類型”.
- 模型的類型
- 查詢的類型
- 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指出教程中的一個錯誤.