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 --typescript
signs, CRA and project files will be generated for you .ts
and .tsx
it will create a tsconfig.json
file.
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-tag
and graphql
.
apollo-boost
Query API and contains the tools needed to cache data in memory, react-apollo
providing React bindings, react-apollo-hooks
packed in Apollo query React Hook, graphql-tag
used to build our query document, graphql
a peer dependency, it provides a realization GraphQL details.
yarn add apollo-boost react-apollo react-apollo-hooks graphql-tag graphql
复制代码
graphql-code-generator
For 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:
- React to use the program build.
- schema is located
https://spacexdata.herokuapp.com/graphql
. - The position of your operation and code set
./src/components/**/*.{ts,tsx}
, so that it can search all files for the TypeScript query statement. - Use the default plug-in "TypeScript", "TypeScript Operations", "TypeScript React Apollo".
- The resulting target folder is updated to
src/generated/graphql.tsx
(react-apollo plugin requires .tsx). - Do not generate introspection file.
- Use the default
codegen.yml
file. - Make you run the script
codegen
.
Run the CLI yarn
command to install the CLI tool plug-ins and add to package.json
.
We will also codegen.yml
file an update, by added withHooks:true
to 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:
- The user can view more information about them by clicking on the "launch" task list.
- Details of the task of a single transmission.
For the first component, we will inquire launchs
and request flight_number
, mission_name
and launch_year
. We will show the data in the list when the user clicks on an item, query launch
to 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/components
folder, then create a src/components/LaunchList
folder. In this folder, create index.tsx
, LaunchList.tsx
, query.ts
and styles.css
files. In the query.ts
file, you can send a query from the playground and place it gql
in 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_number
more 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 $id
to set variable boot id
, type of the variable is String!
.
We pass id
as a variable, which corresponds to LaunchList
the query flight_number
. LaunchProfile
Query objects also contain nested type or may be acquired by the corresponding value specified in the key parentheses.
E.g., launch
contained within a rocket
definition (type LaunchRocket
), which comprises internal rocket_name
and rocket_type
. For a better understanding can be used in LaunchRocket
the 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/LaunchProfile
folder and index.tsx
, LaunchProfile.tsx
, query.ts
and styles.css
files. In the query.ts
document, 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.ts
middle, 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 Launch
which we interact with GraphQL on the playground Launch
represent 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.tsx
middle, we need to initialize the client and with Apollo ApolloProvider
component will client
be added to the context in React. Also needed ApolloProviderHooks
components 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 useLaunchListQuery
hook function components. Queries hook returns data
, loading
and error
value. We will check the container assembly loading
and error
, and data
transmitted 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 data
objects constructed UI. We <ol>
create an ordered list, and then displayed by mapping mission_name
and launch_year
.
src/components/LaunchList/LaunchList.tsx
Is 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 null
or 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 + t
shortcut 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.css
inside, 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 Profile
to querying and assembly, the assembly code index.tsx
file 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.tsx
add 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.css
file:
.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.tsx
include 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.css
replaced 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 App
create a hook component to track the frequency of ID and pass it to the LaunchProfile
assembly to retrieve the transmitted data.
In the src/App.tsx
middle, we will add useState
to maintain and update the status of ID. When a user makes a selection from the list, we will also use the name handleIdChange
of useCallback
a click handler to update the ID. We need to id
pass to LaunchProfile
, then handleIdChange
passed 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.tsx
assembly, we need to handleIdChange
create a type and add it to deconstruct props in. Then, <li>
shift the project onClick
to 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.tsx
there, you must import OwnProps
declaration 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 id
changed refetch
data. In the LaunchList/index.tsx
document, we will use useEffect
to manage React lifecycle and id
trigger 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.tsx
the file can be, so at the selected flight_number
time 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 offset
variable passed to the LaunchList
query.
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