With TypeScript + GraphQL develop a data query system SpaceX rocket launch

Translation: Crazy house technology

blog.logrocket.com/build-a-gra…

Disclaimer: without permission is forbidden

Nearly two years GraphQL and TypeScript use will drive explosive growth , both when used in conjunction with React, they can provide an ideal development experience for developers.

GraphQL change the way we think about the API, and an intuitive key / value pairs match, the client can request the exact data needed to display on a web page or mobile application screen. TypeScript by adding a static variable of type to extend JavaScript, thereby reducing errors and improving the readability of the code.

This article will guide you to use React and Apollo to build the client application, and call SpaceX public GraphQL API, to display information about the launch. We will automatically generate TypeScript type of query and execute these queries using React Hooks.

This article assumes that you have some understanding of React, GraphQL and TypeScript, and by studying how to integrate them to build a normal operation of the program. If you need to add some basic knowledge, you can focus on the public number "front-end Pioneer."

If you encounter difficulties in the learning process, you can refer to the source code or view the Live App .

Why GraphQL + TypeScript?

GraphQL API require strong typing, data supplied from a single endpoint. GET request by calling on the endpoint, client may receive the rear end of the data is completely self-describing, and including all available data of the corresponding type.

By GraphQL code generator , we can scan the Web query file in the application directory, and match them with information GraphQL API provided, so you can create TypeScript since all the requested data type. By using GraphQL, we can automatically and freely enter our property React assembly. This can reduce errors and improve product iteration speed.

getting Started

We will use the create-react-app with TypeScript configured to create the program. First, execute the following command:

npx create-react-app graphql-typescript-react --typescript
// NOTE - you will need Node v8.10.0+ and NPM v5.2+
复制代码

By using --typescriptsigns, CRA and project files will be generated for you .tsand .tsxit will create a tsconfig.jsonfile.

Switch to the app directory:

cd graphql-typescript-react
复制代码

Now install additional dependencies. We use the Apollo program to perform GraphQL API request. Apollo library is required apollo-boost, react-apollo, react-apollo-hooks, graphql-tagand graphql.

apollo-boostQuery API and contains the tools needed to cache data in memory, react-apolloproviding React bindings, react-apollo-hookspacked in Apollo query React Hook, graphql-tagused to build our query document, graphqla peer dependency, it provides a realization GraphQL details.

yarn add apollo-boost react-apollo react-apollo-hooks graphql-tag graphql
复制代码

graphql-code-generatorFor workflow automation TypeScript of. Next, install codegen CLI to generate configuration and plug-ins we need.

yarn add -D @graphql-codegen/cli
复制代码

Configuration settings codegen execute the following command:

$(npm bin)/graphql-codegen init
复制代码

This will start the CLI wizard and perform the following steps:

  1. React to use the program build.
  2. schema is located https://spacexdata.herokuapp.com/graphql.
  3. The position of your operation and code set ./src/components/**/*.{ts,tsx}, so that it can search all files for the TypeScript query statement.
  4. Use the default plug-in "TypeScript", "TypeScript Operations", "TypeScript React Apollo".
  5. The resulting target folder is updated to src/generated/graphql.tsx(react-apollo plugin requires .tsx).
  6. Do not generate introspection file.
  7. Use the default codegen.ymlfile.
  8. Make you run the script codegen.

Run the CLI yarncommand to install the CLI tool plug-ins and add to package.json.

We will also codegen.ymlfile an update, by added withHooks:trueto generate the type of configuration options React Hook query. Your profile should look like this:

overwrite: true
schema: 'https://spacexdata.herokuapp.com/graphql'
documents: './src/components/**/*.ts'
generates:
  src/generated/graphql.tsx:
    plugins:
      - 'typescript'
      - 'typescript-operations'
      - 'typescript-react-apollo'
    config:
      withHooks: true
复制代码

Query and generate the type of writing GraphQL

GraphQL main benefit is its use of declarative data extraction. We can write and use their components coexist query, and the UI can accurately its request content to be rendered.

When using the REST API, we can find the documents may not be current. REST If any problems arise, we need to cooperate with console.log to debug data.

GraphQL URL allows you to view the model is completely defined by access and execute its request for, so as to solve this problem in the UI. Now access spacexdata.herokuapp.com/graphql to see the exact data you will use.

While SpaceX we can get a lot of data, but we will only display information about launching tasks. We have two main components:

  1. The user can view more information about them by clicking on the "launch" task list.
  2. Details of the task of a single transmission.

For the first component, we will inquire launchsand request flight_number, mission_nameand launch_year. We will show the data in the list when the user clicks on an item, query launchto get more data the rocket. Let's test the first query in the GraphQL playground.

To write our query, you first need to create a src/componentsfolder, then create a src/components/LaunchListfolder. In this folder, create index.tsx, LaunchList.tsx, query.tsand styles.cssfiles. In the query.tsfile, you can send a query from the playground and place it gqlin the string.

import gql from 'graphql-tag';

export const QUERY_LAUNCH_LIST = gql`
  query LaunchList {
    launches {
      flight_number
      mission_name
      launch_year
    }
  }
`;
复制代码

Other queries will be based on flight_numbermore detailed data of a single shot. Since this will be dynamically generated by user interaction, so we need to use GraphQL variables . We can also test the query with variables on the playground.

Behind the query name, you can use the prefix $and to specify the type of a variable, then the query form, you can use the variable. For our queries, by passing $idto set variable boot id, type of the variable is String!.

We pass idas a variable, which corresponds to LaunchListthe query flight_number. LaunchProfileQuery objects also contain nested type or may be acquired by the corresponding value specified in the key parentheses.

E.g., launchcontained within a rocketdefinition (type LaunchRocket), which comprises internal rocket_nameand rocket_type. For a better understanding can be used in LaunchRocketthe field, you can see the available data through the side of the navigator mode.

Inquire now transfer this to our program. Create a src/components/LaunchProfilefolder and index.tsx, LaunchProfile.tsx, query.tsand styles.cssfiles. In the query.tsdocument, we pasted the previous query from the playground.

import gql from 'graphql-tag';

export const QUERY_LAUNCH_PROFILE = gql`
  query LaunchProfile($id: String!) {
    launch(id: $id) {
      flight_number
      mission_name
      launch_year
      launch_success
      details
      launch_site {
        site_name
      }
      rocket {
        rocket_name
        rocket_type
      }
      links {
        flickr_images
      }
    }
  }
`;
复制代码

Now that we have defined the query, you can finally generate TypeScript interfaces and types of Hook. Performed in the terminal:

yarn codegen
复制代码

In the src/generated/graphql.tsmiddle, you will find all types needed to define the program, and get GraphQL endpoint to retrieve the corresponding query the data.

This file is often very large, but very valuable inside information. I recommend take the time to research it, and understand all types of our codegen based GraphQL architecture created.

For example, a check type Launchwhich we interact with GraphQL on the playground Launchrepresent TypeScript object. You can also scroll to the bottom of the file to view the generated code specifically for the inquiry we will be executed - it creates components, HOC, type of props or queries, as well as the type of hook.

Apollo client initialization

In the src/index.tsxmiddle, we need to initialize the client and with Apollo ApolloProvidercomponent will clientbe added to the context in React. Also needed ApolloProviderHookscomponents to enable the hook in the context.

We initialize a new ApolloClient and give it GraphQL URI API, and then <App/>the component package provider in context. Your index file should look like this:

import React from 'react';
import ReactDOM from 'react-dom';
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from 'react-apollo';
import { ApolloProvider as ApolloHooksProvider } from 'react-apollo-hooks';
import './index.css';
import App from './App';

const client = new ApolloClient({
  uri: 'https://spacexdata.herokuapp.com/graphql',
});

ReactDOM.render(
  <ApolloProvider client={client}>
    <ApolloHooksProvider client={client}>
      <App />
    </ApolloHooksProvider>
  </ApolloProvider>,
  document.getElementById('root'),
);
复制代码

Construction of assembly

Now we already have all the conditions to perform GraphQL by Apollo queries required.

In src/components/LaunchList/index.tsx, create generated using a useLaunchListQueryhook function components. Queries hook returns data, loadingand errorvalue. We will check the container assembly loadingand error, and datatransmitted to the presentation component.

We will use this component as an intelligent component to maintain the separation of concerns, and the data to show only to indicate the content of a given component. We also show basic load and error status while waiting for data.

Your container assembly should be as follows:

import * as React from 'react';
import { useLaunchListQuery } from '../../generated/graphql';
import LaunchList from './LaunchList';

const LaunchListContainer = () => {
  const { data, error, loading } = useLaunchListQuery();

  if (loading) {
    return <div>Loading...</div>;
  }

  if (error || !data) {
    return <div>ERROR</div>;
  }

  return <LaunchList data={data} />;
};

export default LaunchListContainer;
复制代码

Component represented by dataobjects constructed UI. We <ol>create an ordered list, and then displayed by mapping mission_nameand launch_year.

src/components/LaunchList/LaunchList.tsxIs as follows:

import * as React from 'react';
import { LaunchListQuery } from '../../generated/graphql';
import './styles.css';

interface Props {
  data: LaunchListQuery;
}

const className = 'LaunchList';

const LaunchList: React.FC<Props> = ({ data }) => (
  <div className={className}>
    <h3>Launches</h3>
    <ol className={`${className}__list`}>
      {!!data.launches &&
        data.launches.map(
          (launch, i) =>
            !!launch && (
              <li key={i} className={`${className}__item`}>
                {launch.mission_name} ({launch.launch_year})
              </li>
            ),
        )}
    </ol>
  </div>
);

export default LaunchList;
复制代码

If you are using VS Code, IntelliSense will show you the available values, and provides auto-complete list, because we are using the TypeScript. If we use the data nullor undefined, it will warn us.

It's great! Editing will help us to be encoded. Also, if you need a defined type or function, you can Cmd + tshortcut keys, or with the mouse hovers over it, it will give all the details.

You also need to add some CSS styles, it will display our projects and allows them to scroll through the list is high enough. In the src/components/LaunchList/styles.cssinside, add the following code:

.LaunchList {
  height: 100vh;
  overflow: hidden auto;
  background-color: #ececec;
  width: 300px;
  padding-left: 20px;
  padding-right: 20px;
}

.LaunchList__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.LaunchList__item {
  padding-top: 20px;
  padding-bottom: 20px;
  border-top: 1px solid #919191;
  cursor: pointer;
}
复制代码

In order to display more detailed information about launching tasks, but also build our profile components. In addition Profileto querying and assembly, the assembly code index.tsxfile is substantially the same. We will also pass a variable to React hook for startup id. It is now hard-coded first 42, and then add dynamic layout function after completion of the program.

In src/components/LaunchProfile/index.tsxadd the following code:

import * as React from 'react';
import { useLaunchProfileQuery } from '../../generated/graphql';
import LaunchProfile from './LaunchProfile';

const LaunchProfileContainer = () => {
  const { data, error, loading } = useLaunchProfileQuery({ variables: { id: '42' } });

  if (loading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>ERROR</div>;
  }

  if (!data) {
    return <div>Select a flight from the panel</div>;
  }

  return <LaunchProfile data={data} />;
};

export default LaunchProfileContainer;
复制代码

Now we need to create the presentation component. Launch the task name and details of it will be displayed at the top of the UI, and then in the description below the picture displayed at launch.

src/components/LaunchProfile/LaunchProfile.tsx Assembly is as follows:

import * as React from 'react';
import { LaunchProfileQuery } from '../../generated/graphql';
import './styles.css';

interface Props {
  data: LaunchProfileQuery;
}

const className = 'LaunchProfile';

const LaunchProfile: React.FC<Props> = ({ data }) => {
  if (!data.launch) {
    return <div>No launch available</div>;
  }

  return (
    <div className={className}>
      <div className={`${className}__status`}>
        <span>Flight {data.launch.flight_number}: </span>
        {data.launch.launch_success ? (
          <span className={`${className}__success`}>Success</span>
        ) : (
          <span className={`${className}__failed`}>Failed</span>
        )}
      </div>
      <h1 className={`${className}__title`}>
        {data.launch.mission_name}
        {data.launch.rocket &&
          ` (${data.launch.rocket.rocket_name} | ${data.launch.rocket.rocket_type})`}
      </h1>
      <p className={`${className}__description`}>{data.launch.details}</p>
      {!!data.launch.links && !!data.launch.links.flickr_images && (
        <div className={`${className}__image-list`}>
          {data.launch.links.flickr_images.map(image =>
            image ? <img src={image} className={`${className}__image`} key={image} /> : null,
          )}
        </div>
      )}
    </div>
  );
};

export default LaunchProfile;
复制代码

The final step is to set the style of this component with CSS. Add the following to the src/components/LaunchProfile/styles.cssfile:

.LaunchProfile {
  height: 100vh;
  max-height: 100%;
  width: calc(100vw - 300px);
  overflow: hidden auto;
  padding-left: 20px;
  padding-right: 20px;
}

.LaunchProfile__status {
  margin-top: 40px;
}

.LaunchProfile__title {
  margin-top: 0;
  margin-bottom: 4px;
}

.LaunchProfile__success {
  color: #2cb84b;
}

.LaunchProfile__failed {
  color: #ff695e;
}

.LaunchProfile__image-list {
  display: grid;
  grid-gap: 20px;
  grid-template-columns: repeat(2, 1fr);
  margin-top: 40px;
  padding-bottom: 100px;
}

.LaunchProfile__image {
  width: 100%;
}
复制代码

Now completed the static version of the component, you can view them in the UI. We will src/App.tsxinclude these components file and <App />convert function components. With the function components to make it more simple, and allows us to use when adding a hook-click functionality.

import React from 'react';
import LaunchList from './components/LaunchList';
import LaunchProfile from './components/LaunchProfile';

import './App.css';

const App = () => {
  return (
    <div className="App">
      <LaunchList />
      <LaunchProfile />
    </div>
  );
};

export default App;
复制代码

In order to get the style we want, will be src/App.cssreplaced by the following:

.App {
  display: flex;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}
复制代码

In the terminal performs yarn start, then open in your browser http://localhost:3000, you should see their most basic version of the program!

Adding user interaction

Now we need to add when get full-featured transmit data when the user clicks on the panel project. We will Appcreate a hook component to track the frequency of ID and pass it to the LaunchProfileassembly to retrieve the transmitted data.

In the src/App.tsxmiddle, we will add useStateto maintain and update the status of ID. When a user makes a selection from the list, we will also use the name handleIdChangeof useCallbacka click handler to update the ID. We need to idpass to LaunchProfile, then handleIdChangepassed to <LaunchList />.

The updated <App/>components should be as follows:

const App = () => {
  const [id, setId] = React.useState(42);
  const handleIdChange = React.useCallback(newId => {
    setId(newId);
  }, []);

  return (
    <div className="App">
      <LaunchList handleIdChange={handleIdChange} />
      <LaunchProfile id={id} />
    </div>
  );
};
复制代码

In the LaunchList.tsxassembly, we need to handleIdChangecreate a type and add it to deconstruct props in. Then, <li>shift the project onClickto execute the callback function.

export interface OwnProps {
  handleIdChange: (newId: number) => void;
}

interface Props extends OwnProps {
  data: LaunchListQuery;
}

// ...
const LaunchList: React.FC<Props> = ({ data, handleIdChange }) => (
  
// ...
<li
  key={i}
  className={`${className}__item`}
  onClick={() => handleIdChange(launch.flight_number!)}
>
复制代码

In LaunchList/index.tsxthere, you must import OwnPropsdeclaration is transmitted to the input of the container assembly props, and then propagated to the props <LaunchList data={data} {...props} />.

The final step is the idchanged refetchdata. In the LaunchList/index.tsxdocument, we will use useEffectto manage React lifecycle and idtrigger the extraction time change. The following extract is the only change you need to do to achieve:

interface OwnProps {
  id: number;
}

const LaunchProfileContainer = ({ id }: OwnProps) => {
  const { data, error, loading, refetch } = useLaunchProfileQuery({
    variables: { id: String(id) },
  });
  React.useEffect(() => {
    refetch();
  }, [id]);
复制代码

Since we have already expressed separately from the data, there is no need for <LaunchProfile />components of any updates, just update index.tsxthe file can be, so at the selected flight_numbertime for a complete change to re-transmit data.

Well, if you follow the above steps, you should now have a fully functional GraphQL procedure. If you do not know of any place can be the source code to find a viable solution.

to sum up

We can see that once configured the program, the development speed is very fast. We can easily build data-driven UI. GraphQL component allows us to define the data required, and may be used for seamlessly assembly props. Generated TypeScript the definition of our code with high stability.

If you want in-depth understanding of the project, the next step would be to use other fields to add a page in the API and more data association. To launch the task list on the page, you will get a list of current length and offsetvariable passed to the LaunchListquery.

I encourage you to explore more deeply and write their own inquiries in order to consolidate these concepts.

TypeScript combat training camp Recommended

The front end of three days of pure combat training camp with you using React hooks + KOA2 + TypeScript + Webpack, build a project from the front 0-1.

Limited Time Offer, only 10 yuan, scan code to receive coupons, immediately apply >>

Reproduced in: https: //juejin.im/post/5cfe29eae51d45105e021298

Guess you like

Origin blog.csdn.net/weixin_34283445/article/details/91479660
Recommended