翻译|Redux和GraphQL入门

作者: smartphp | 来源:发表于2017-04-12 00:22 被阅读371次

    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

    相关文章

      网友评论

        本文标题:翻译|Redux和GraphQL入门

        本文链接:https://www.haomeiwen.com/subject/vfepattx.html