react+graphql起手和特性介绍(三)

紧接上篇react+graphql起手和特性介绍(二),介绍完graphql与koa的服务搭建和graphql的一些常用特性,接下来我们介绍下在react中如何使用graphql
我们使用create-react-app创建react应用:

npm i -g create-react-app
mkdir react-graphql-app
create-react-app react-graphql-app

安装以下前端依赖

npm install react-apollo graphql-tag graphql apollo-client apollo-cache-inmemory apollo-link-http

各个依赖包的作用:

  • apollo-link-http 请求配置和网络请求能力
  • apollo-cache-inmemory 数据缓存
  • apollo-client 请求流程控制,生成请求数据,错误控制,响应数据解析
  • graphql-tag 查询类的schema数据解析,包含对应的 webpack-loader
  • react-apollo 连接graphql与react
  • graphql 提供graphql的核心执行能力

然后我们进行react和graphql的整合

// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';

import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloProvider } from 'react-apollo';

import App from './App';

// 我们可以自定义fetch,对请求进行统一处理
const customFetch = (uri, options) => {
    return fetch(uri, options);
};


const client = new ApolloClient({
    // 连接到graphql服务器
    link: createHttpLink({ uri: 'http://localhost:9191/graphql', fetch: customFetch }),
    // 设置缓存
    cache: new InMemoryCache(),
});

// ApolloProvider 为react提供graphql能力
const WrappedApp = (
    <ApolloProvider client={client}>
        <App />
    </ApolloProvider>
);

ReactDOM.render(WrappedApp, document.getElementById('root'));

为了能解析.graphql文件,需要修改webpack配置,添加graphql-loader

// config/webpack.config.dev.js && config/webpack.config.prod.js

...

module.exports = {
    ...
    
    module: {
        strictExportPresence: true,
        rules: [
            ...
            
            {
                // 解析 .graphql/.gql 后缀的loader
                test: /\.(graphql|gql)$/,
                exclude: /node_modules/,
                loader: 'graphql-tag/loader',
            },
            
            ...
        ]
    }
    ...
}

我们以post为示例,讲一下在组件中要做什么操作
创建定义查询的schema文件

# src/post.graphql

# 导入 user.graphql
#import "./user.graphql"

# fragment 定义片段,可以用于多个地方
fragment PostInfo on Post {
    id
    title
    content
    userId
    user {
        ...UserInfo
    }
}

query getPost($id: ID!) {
    post(id: $id) {
        id
        title
        content
        userId
        user {
            id
            name
            age
            available
            birthday
            money
            gender
        }
    }
}

query getPosts {
    posts {
        ...PostInfo
    }
}


mutation createPost($data: PostInput!) {
    createPost(data: $data) {
        ...PostInfo
    }
}
# src/user.graphql

fragment UserInfo on User {
    id
    name
    age
    available
    birthday
    tags
    gender
    role
}
// src/App.js

import React, { Component } from 'react';
import Post from './Post';

class App extends Component {
    render() {
        return (
            <div>
                <Post/>
            </div>
        );
    }
}

export default App;
// src/Post.js

import React, { Component } from 'react';
import { Query, Mutation, ApolloConsumer } from "react-apollo";
// 导入查询定义,定义的查询名次对应导入对象的方法名称,如 getPost => postQuery.getPost
import postQuery from './post.graphql';

class Post extends Component {
    state = {
        post: {},
        postId: '',
        newPost: {
            title: '',
            content: '',
        }
    }

    render() {
        // 由ApolloConsumer传入我们需要的数据,包含:
        // data 正常返回时的数据
        // loading 正在请求时loading会为true
        // error 发生错误时error将会有错误数据
        // 这里进行了重命名,将创建post,获取posts列表进行了命名区分
        const { 
            client, 
            getPostsData, getPostsDataLoading,
            createPost, createPostData, createPostLoading, 
            getPostsDataError
        } = this.props;

        const { postId, post, newPost } = this.state;
        return(
            <div>
                <div>
                    {
                        // loading状态时将显示...
                        getPostsDataLoading ? 
                            <div>...</div> 
                            : 
                            (
                                getPostsDataError ? 
                                    // 有错误数据,将会显示错误
                                    <div>{JSON.stringify(getPostsDataError)}</div> 
                                    : 
                                    // 正常则显示请求到的数据
                                    <div>{JSON.stringify(getPostsData.posts)}</div>
                            ) 
                    }
                </div >

                <hr />

                <div>
                    <input 
                        type="text" 
                        name="postId" 
                        value={postId} 
                        onChange={(e) => { 
                            this.setState({ postId: e.target.value }) 
                        }} 
                    /> 

                    <button onClick={() => {
                        // client 也是props传过来的对象,可以让我们主动发起请求
                        client.query({
                            // 对应定义的 getPost 查询
                            query: postQuery.getPost,
                            // 设置请求参数
                            variables: {
                                id: postId
                            }
                        }).then(({ data: { post } }) => {
                            this.setState({
                                post
                            })
                        })

                    }}>
                        getPost
                    </button>

                    <div>{JSON.stringify(post)}</div>
                </div>

                <hr/>

                <div>
                    <input 
                        type="text" 
                        value={newPost.title}
                        onChange={(e) => this.setState({ 
                            newPost: { 
                                ...newPost, 
                                title: e.target.value,
                            } 
                        })}
                    />

                    <input 
                        type="text" 
                        value={newPost.content}
                        onChange={(e) => this.setState({ 
                            newPost: { 
                                ...newPost, 
                                content: e.target.value,
                            } 
                        })}
                    />
                    
                    <button onClick={() => createPost({
                        // createPost是ApolloConsumer传过来的包装好的请求方法,
                        // 这里只用设置请求参数,loading,data,error 将会通过props
                        // 传递进来
                        variables: {
                            data: newPost
                        }
                    })}>
                        createPost
                    </button>

                    {
                        createPostLoading ?
                            <div>...</div> 
                            : 
                            <div>
                            {JSON.stringify(createPostData && createPostData.createPost)}
                            </div>
                    }
                </div>
            </div >
            
        )
    }
}

class PostWrap extends Component {
    render() {
        return (
            <ApolloConsumer>
            {(client) => (
                // 传入要使用的motation查询
                <Mutation mutation={postQuery.createPost}>
                    {(
                        // 方法重命名
                        createPost, 
                        { 
                            // 状态数据重命名
                            data: createPostData, 
                            loading: createPostLoading 
                        }
                    ) => (
                       // 当同时多个查询时,使用这种嵌套模式
                        <Query
                            query={postQuery.getPosts}
                            >
                                {({ 
                                    // 状态数据重命名
                                    data: getPostsData, 
                                    loading: getPostsLoading, 
                                    error: getPostsDataError 
                                }) => 
                                    // 将重命名的状态数据和查询方法传递到组件中
                                    // Query指定的查询在组件加载后就会自动发起请求
                                    <Post {...{
                                            client, 
                                            getPostsData, 
                                            getPostsLoading, 
                                            getPostsDataError, 
                                            createPost, 
                                            createPostData, 
                                            createPostLoading
                                        }} 
                                    />}
                        </Query>
                    )}
                </Mutation>
            )}
            </ApolloConsumer>
        )
    }
}

export default PostWrap;

通过这种方式我们可以在react中使用graphql了,这种方式极大方便了我们对请求数据api的管理,而且可以通过整合查询,减少页面的请求次数。
如果你对这系列文章有疑问或发现有错误的地方,欢迎在下方留言讨论。

相关推荐