GraphQL 在小程序中的调用示例

什么是GraphQL

struts2其实就是为我们封装了servlet,简化了jsp跳转的复杂操作,并且提供了易于编写的标签,可以快速开发view层的代码。


我们以小程序框架 Taro 为前提,在项目中使用 GraphQL
查询数据,具体步骤如下:

一、Hibernate简介

在很多场景下,我们不需要使用JdbcTemplate直接操作SQL语句,这时候可以用ORM工具来节省数大量的的代码和开发时间。ORM工具能够把注意力从容易出错的SQL代码转向如何实现应用程序的真正需求。

Spring对ORM框架的支持提供了与这些框架的集成点以及一些附加的服务:

  • 支持集成Spring声明式事务;
  • 透明的异常处理;
  • 线程安全的、轻量级的模板类;
  • DAO支持类;
  • 资源管理。

Hibernate是在开发者社区很流行的开源ORM框架。

官方文档定义:一种用于API的查询语言, Graph + Query,有以下特点

  过去,我们用jsp和servlet搭配,实现展现时,大体的过程是:

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

yarn add graphql apollo-boost,安装好后,package.json
中添加如下两个依赖:

二、Spring+Hibernate实例

请求你所要的数据不多不少获取多个资源只用一个请求描述所有可能的类型系统

  1 jsp触发action


 "dependencies": { ... "apollo-boost": "^0.3.1", "graphql": "^14.2.1" ... },

1.创建数据库表

mysql新建数据库store,然后执行如下sql:

奥门威尼斯网址 1奥门威尼斯网址 2

1 create table Category (
2 Id int not null,
3 Name varchar(80) null,
4 constraint pk_category primary key (Id)
5 );
6 
7 INSERT INTO category(id,Name) VALUES (1,'女装');
8 INSERT INTO category(id,Name) VALUES (2,'美妆');
9 INSERT INTO category(id,Name) VALUES (3,'书籍');

奥门威尼斯网址,db_store.sql

解决了什么问题1. 来说一个实际的场景:

  2 servlet接受action,交给后台class处理

当GraphQL发布以来,非常清楚的显示出,他将会成为非常好的技术.社区都在耐心的等待技术评价.
但是你可能和我一样,发现文档比我们期待的更难理解.可能的原因是由于GraphQL和Relay的联合使用.

apollo-boost 是什么

Apollo Boost 是一个零配置的 Apollo Client,包含很多省心的默认配置,比如
InMemoryCacheHttpLink,我们都是用的合理默认配置。当然也包含
graphql-tag,用 apollo 全家桶,真香!

graphql-client.js

import Taro from '@tarojs/taro'import ApolloClient from 'apollo-boost';const client = new ApolloClient({ uri: 'https://openapi.baichanghui.com/graphql', fetch: (url, options) => Taro.request({ url, method: options.method, data: options.body, header: options.headers, }).then(({data, statusCode}) => { return { ok: () => { return statusCode >= 200 && statusCode < 300; }, text: () => { return Promise.resolve(JSON.stringify; } } })});export default client

注:这里的 openapi.baichanghui.com 是一个可供临时测试用的接口

import { gql } from 'graphql-boost';import graphqlClient from "../../utils/graphql-client";

 getData = () => { const query = gql`{ hello { message info version } product(id: "R4MGX4") { title city routeDays totalSellNum minHeadCount id } products { items { id } } }`; graphqlClient.query({query, variables: {}}).then(result => { console.log('result===', result.data); }); };

友情提示:字段间可以用空格,逗号或者换行

<Button onClick={this.getData}>获取数据</Button>

虽然我们在写 query 语句
{ title city routeDays totalSellNum minHeadCount id }
时字段之间是空格分隔的,但被 gql 转换成
\n title\n city\n routeDays\n totalSellNum\n minHeadCount\n id\n

如图所示:

奥门威尼斯网址 3

在微信开发工具的 network 里就能看到返回数据,实现 1 次请求返回传统要请求
3 次接口的数据量。

如图所示:

奥门威尼斯网址 4

今天,你 GraphQL 了吗?

2.代码结构

我用的IDE是IdeaIU,通过maven构建项目,通过xml配置spring。完成后的代码结构为:

奥门威尼斯网址 5

前后端联调接口一直以来都是特别费劲的一个环节,使用REST接口,接口返回的数据格式,数据类型都是后端自己预先定义好的,如果返回的数据格式并不是调用者所期望的,作为前端的我们可以通过以下两种方式去解决

  3 后台class跳转到其他的jsp,实现数据展现

我也感觉到了你的痛苦.我的大脑都要融化掉了,我告诉我自己我将会尝试在其他的框架里是用它.我做到了!这一次我仅仅把关注点放在GraphQL自身,其他的地方保持尽可能的简单.

3.创建实体类Category

class Category{
    private int cateId;

    private String cateName;

    //次数省略get,set方法

    @Override
    public String toString() {
        return "id="+cateId+" name="+cateName;
    }
}

  

和后端沟通,该接口自己做一些适配工作

  现在有了struts2,实现过程变为

Sharing is Caring

这个教程的配置部分尽可能的简单,结合GraphQL和Redux.减少复杂的部分,所有的内容你可以直接从这里看到(指代码部分).

我们将使用Redux来代替Relay,在服务器上使用es5而不是es6/babel-node.所有的GraphQL的东西都保持尽可能的简单.

下面配置一下项目

4.修改pom.xml,引入相关依赖。

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>4.3.5.Final</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.30</version>
        </dependency>
    </dependencies>

  

有这种经历的人都知道,让后端改接口这是一个很不现实方案,尤其是对于三端公用同一套后端接口的情况下,
让后端改接口的结构基本不可能,所以一般都是前端自己做一些接口数据的适配工作

  1 jsp出发action

项目文件配置

创建新文件件(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
 }
}

5.配置applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">


    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/store"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <bean id="sessionFactory"
          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:hibernate/hibernate.cfg.xml"/>
    </bean>

    <tx:annotation-driven/>
    <bean id="transactionManager"
          class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <bean id="categoryDao" class="CategoryDao">
        <constructor-arg ref="sessionFactory"></constructor-arg>
    </bean>
</beans>

  

dataSource没什么特别的,就不在解释了。看下其他几点:

①hibernate sessionFactory:

使用Hibernate所需的主要接口是org.hibernate.Session,Session接口提供了CRUD等最基本的数据访问功能。通过Hibernate的Session接口,应用程序的Repository能够满足所有的持久化需求。而获取Hibernate
Session对象的标准方式是借助于Hibernate SessionFactory接口的实现类。

在sessionFactory配置主要设置了两个属性:dataSource设置了数据连接,configLocation设置了hibernate配置文件的路径。

②事务

要是数据库操作支持事务,需要配置<tx:annotation-driven/>和transactionManager。

其实我们真的很希望,
我们需要什么数据,需要什么格式,后端就按照什么格式给我们返回什么样的数据结构,我们要哪些字段,后端就只给我们返回我们需要的这些字段,
其他的都不返回,这样,前端就和后端解耦了,我们不用再每天和后端因为接口问题去撕逼,GraphQL就是一个这样的思路来帮助我们解决这个前后端联调接口的问题,
在前端直接写查询, 后端只管给前端返回前端查询的这些数据;

  2 struts2拦截请求,调用后台action

GraophQL

GraphQL从简化的角度考虑,有一个类型系统构成-这是我们用来理解他的心理模型-我们将看到这里有三种”类型”.

  1. 模型的类型
  2. 查询的类型
  3. schema的类型

在实际的编码中,类型可能比这个简单,这里只是为了到入门的目的,所以比较简单

6.hibernate配置

①hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
        <!DOCTYPE hibernate-configuration PUBLIC
                "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
    <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
    <property name="show_sql">true</property>
    <mapping resource="hibernate/Category.hbm.xml"/>
</session-factory>
</hibernate-configuration>

  

②Category.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="Category" table="Category">
        <id name="cateId" column="id">
            <generator class="native"/>
        </id>
        <property name="cateName" column="name"/>
    </class>
</hibernate-mapping>

  

  1. 还有一种场景:

  3 action返回结果,由不同的jsp展现数据

模型的类型

我们将创建一个”模型类型”,实际相当于实际的数据的镜像.

 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中刚看到这个描述内容.

7.数据访问实现类CategoryDao

如果方法要支持事务,需要加注解@Transactional。

public class CategoryDao {
    private SessionFactory sessionFactory;

    public CategoryDao(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    private Session currentSession() {
        return sessionFactory.getCurrentSession();
    }

    @Transactional
    public void save(Category category) {
        currentSession().save(category);
    }

    @Transactional
    public void update(Category category){
        currentSession().update(category);
    }

    @Transactional
    public void delete(int id) {
        Query query = currentSession().createSQLQuery("DELETE FROM category WHERE Id=::ID");
        query.setInteger("::ID", id);
        query.executeUpdate();
    }

    @Transactional
    public int count() {
        return getAll().size();
    }

    @Transactional
    public Category getById(int id) {
        Criteria criteria=currentSession().createCriteria(Category.class);
        criteria.add(Restrictions.eq("id",id));
        return (Category) criteria.uniqueResult();
    }

    @Transactional
    public List<Category> getAll() {
        return currentSession().createCriteria(Category.class).list();
    }
}

  

一个页面里展示的信息, info1, info2,
info3,前端需要请求多个接口,info1对应的接口A中的a字段,info2对应的接口B中的b字段,info3对应的接口C中的c字段

  

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.

8.测试

@ContextConfiguration(locations = "classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class testCategoryDao {
    @Autowired
    private CategoryDao categoryDao;

    @Test
    public void testAdd() {
        Category category = new Category();
        category.setCateId(4);
        category.setCateName("母婴");

        categoryDao.save(category);
    }

    @Test
    public void testUpdate() {
        Category category = new Category();
        category.setCateId(4);
        category.setCateName("男装");

        categoryDao.update(category);
    }


    @Test
    public void testGetById() {
        int id = 4;
        Category category = categoryDao.getById(id);

        if(category==null){
            System.out.println("not exist");
        }else {
            System.out.println(category.toString());
        }
    }

    @Test
    public void testGetAll() {
        List<Category> categories = categoryDao.getAll();
        for (Category item : categories) {
            System.out.println(item);
        }
    }

    @Test
    public void testCount() {
        int count = categoryDao.count();
        System.out.println(count);
    }

    @Test
    public void testDelete() {
        int id = 4;
        categoryDao.delete(id);
    }
}

  

源码地址:

 

// /api/user/A{ id: 1111, name: '张三', a: '当前页面要展示的info1', b: 'b' // 其他字段}// /api/order/B{ id: 2222, name: 'hahah', a: 'a' b: '当前页面要展示的info2', // 其他字段}// /api/system/C{ id: 3333, name: 'hehe', a: 'a' c: '当前页面要展示的info3', // 其他字段}

  下面我们看下,需要的jar包

奥门威尼斯网址 6

  前面两个是apache commons的jar包,暂且忽略

  freemarker提供了另一种展现方式

  ognl提供了OGNL表达式

  struts2-core提供struts2核心包

  xwork-core由于struts2很多事基于webwork的,因此也需要这个的核心包

 

  我们提供了三个jsp

Schema type

最终”schema type”把类型放到一起.

这个时候,稍微有点脾气的前端,都会去找后端撕逼,

登陆界面login.jsp

奥门威尼斯网址 7

1 <%@ page language="java" contentType="text/html; charset=GBK"
 2     pageEncoding="GBK"%>
 3 <%@taglib prefix="s" uri="/struts-tags"%>
 4 <html>
 5 <head>
 6 <meta http-equiv="Content-Type" content="text/html; charset=GBK">
 7 <title><s:text name="loginPage"/></title>
 8 </head>
 9 <body>
10 <s:form action="login">
11     <s:textfield name="username" key="user"/>
12     <s:textfield name="password" key="pass"/>
13     <s:submit key="login"/>
14 </s:form>
15 </body>
16 </html>

奥门威尼斯网址 8

为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”中添加的字段内容.可以探索一下文档.

前端A:
“就这三个字段,你还让我请求三个接口,你不能一个接口都返回给我吗”,后端B:“哎,
我也想啊,但是xxxxx, 所以我这边不好改,”,…最后那就这样吧。

登陆成功界面welcome.jsp

奥门威尼斯网址 9

<%@ page language="java" contentType="text/html; charset=GBK"
    pageEncoding="GBK"%>
<%@taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
    <title><s:text name="succPage"/></title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
    <s:text name="succTip">
        <s:param>${sessionScope.user}</s:param>
    </s:text><br/>
</body>
</html>

奥门威尼斯网址 10

为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.

当然,我举得这个例子是一个很简单的场景,实际开发过程中要比这个还要复杂;

登陆失败界面error.jsp

奥门威尼斯网址 11

<%@ page language="java" contentType="text/html; charset=GBK"
    pageEncoding="GBK"%>
<%@taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
    <title><s:text name="errorPage"/></title>
    <meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
    <s:text name="failTip"/>
</body>
</html>

奥门威尼斯网址 12

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,可以看到信息.

如果使用GraphQL的话,前端自己写查询,这个页面需要哪些需哪数据,后端就返回给哪些数据,这是考虑到后端所有的接口都在同一个域下面,但是一般比较复杂的系统,后端都会分为不同的域,
用户域,商品域,基础模块域,交易域等等,这时即使用了GraphQL也可能

  当login.jsp触发action时,就会向后抬发送login.action的请求,这个请求被后台拦截,交给struts.xml中配置的action处理

奥门威尼斯网址 13

1 <?xml version="1.0" encoding="GBK"?>
 2 <!DOCTYPE struts PUBLIC
 3     "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
 4     "http://struts.apache.org/dtds/struts-2.1.7.dtd">
 5 <struts>
 6     <!-- 指定全局国际化资源文件 -->
 7     <constant name="struts.custom.i18n.resources" value="mess"/>
 8     <!-- 指定国际化编码所使用的字符集 -->    
 9     <constant name="struts.i18n.encoding" value="GBK"/>
10     <!-- 所有的Action定义都应该放在package下 -->
11     <package name="test" extends="struts-default">
12         <action name="login" class="com.test.action.LoginAction">
13             <result name="error">/error.jsp</result>
14             <result name="success">/welcome.jsp</result>
15         </action>
16     </package>
17 </struts>

奥门威尼斯网址 14

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设定为我们的响应数据.

后端C:“你看其他都不是我负责的域,我要是自己给你封装一个,我自己底层需要经过xxxxx等复杂的步骤去获取其他域的,这个很复杂,
你还是直接去他哪个域去查询吧”,

下面是LoginAction的代码,可以看到成功登陆后,程序把username写入session中。以便于我们在welcome.jsp中利用${sessionScope.user}取得名字

奥门威尼斯网址 15

1 package com.test.action;
 2 
 3 import com.opensymphony.xwork2.ActionContext;
 4 import com.opensymphony.xwork2.ActionSupport;
 5 
 6 public class LoginAction extends ActionSupport {
 7     private String username;
 8     private String password;
 9 
10     public String getUsername() {
11         return username;
12     }
13 
14     public void setUsername(String username) {
15         this.username = username;
16     }
17 
18     public String getPassword() {
19         return password;
20     }
21 
22     public void setPassword(String password) {
23         this.password = password;
24     }
25 
26     public String execute() throws Exception {
27         if (getUsername().equals("xingoo") && getPassword().equals("123")) {
28             ActionContext.getContext().getSession().put("user", getUsername());
29             return SUCCESS;
30         }else{
31             return ERROR;
32         }
33     }
34 }

奥门威尼斯网址 16

  这里还要两个国际化文件,

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.

有两种方法,

  mess.properties 

奥门威尼斯网址 17

loginPage=loginPage
errorPage=errorPage
succPage=succPage
failTip=sorry,login failed
succTip=welcome{0},login success
user=username
pass=password
login=login

奥门威尼斯网址 18

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,提供我们查询的结果.

你就再多写一个GraphQL自己写一个node中间层,中间层来处理这些接口数据的聚合,换句话说,中间层来聚合成一个GraphQL查询来返回给前端,
中间层分别取调用服务端的三个接口,然后把三个接口返回的数据聚合成前端所需要的GraphQL一个简单的入门示例

  mess_zh_CN.properties

奥门威尼斯网址 19

loginPage=登陆界面
errorPage=失败界面
succPage=成功界面
failTip=对不起,您不能登录!
succTip=欢迎,{0},您已经登录!
user=用户名
pass=密 码
login=登陆

奥门威尼斯网址 20

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},看看可以获得什么结果.

准备

  登陆界面

 

奥门威尼斯网址 21

 

 

感谢

感谢阅读,我希望这篇文章能对你有帮助.你可以在这里查看源代码.现在我们使用Redux和GraphQL构建了非常好的app.

另外感谢Dan Abramov指出教程中的一个错误.

npm i --save express express-graphql graphql cors

  登陆成功

奥门威尼斯网址 22

 

Resources

服务端代码

  登陆失败

奥门威尼斯网址 23

var express = require('express');var graphqlHTTP = require('express-graphql');const { buildSchema } = require('graphql');const cors = require('cors'); // 用来解决跨域问题// 创建 schema,需要注意到:// 1. 感叹号 ! 代表 not-null// 2. rollDice 接受参数const schema = buildSchema(` type Query { username: String age: Int! }`)const root = { username: () = { return '李华' }, age: () = { return Math.ceil(Math.random() * 100) },}const app = express();app.use(cors());app.use('/graphql', graphqlHTTP({ schema: schema, rootValue: root, graphiql: true}))app.listen(3300);console.log('Running a GraphQL API server at ')

客户端代码

graphql demo

获取当前用户数据

” title=”” data-original-title=”复制”>

!DOCTYPE htmlhtml lang="en"head meta charset="UTF-8" meta name="viewport" content="width=device-width, initial-scale=1.0" meta "X-UA-Compatible" content="ie=edge" titlegraphql demo/title/headbody button 获取当前用户数据/button p /p p /p/bodyscript var test = document.querySelector('.test'); test.onclick = function () { var username = document.querySelector('.username'); var age = document.querySelector('.age'); fetch('', { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, method: 'POST', body: JSON.stringify({ query: `{ username, age, }` }), mode: 'cors' // no-cors, cors, *same-origin }) .then(function (response) { return response.json(); }) .then(function (res) { console.log('返回结果', res); username.innerHTML = `姓名:${res.data.username}`; age.innerHTML = `年龄:${res.data.age}` }) .catch(err = { console.error('错误', err); }); }/script/html

发表评论

电子邮件地址不会被公开。 必填项已用*标注