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指出教程中的一个错误.
网友评论