使用 react-apollo 来熟练运用 GraphQL
首先
这篇文章是GraphQL Advent Calendar 2018的第20天。
我虽然搭建了一个使用react-apollo的前端环境,但是我遇到了一个问题,就是不知道如何使用react-apollo来编写这样的组件。下面是总结的我自己遇到的问题。
我想要创建一个页面,该页面可以显示调用多个API的结果。
使用 react-apollo 的 Query 组件可以实现这个功能。
import React, { Component } from 'react';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';
import { CircularProgress } from '@material-ui/core';
const GET_USER = gql`
query($id: Int) {
user(id: $id) {
name
}
}
`;
const GET_DELIVERY = gql`
query($userId: Int) {
delivery(userId: $userId) {
id
delivery_date
}
}
`;
class Hoge extends Component<Props> {
props: Props;
render() {
return (
<div>
<Query query={GET_USER} variables={{ id: 1 }}>
{({ data, loading }) => {
const { user } = data;
if (loading || !user) {
return <CircularProgress />; // データのfetch中はスピナーがくるくる回る
}
return <div>{user.name}</div>;
}}
</Query>
<Query query={GET_DELIVERY} variables={{ userId: 1 }}>
{({ data, loading }) => {
const { delivery } = data;
if (loading || !delivery) {
return <CircularProgress />; // データのfetch中はスピナーがくるくる回る
}
return <div>{delivery.delivery_date}</div>;
}}
</Query>
</div>
);
}
}
export default Hoge;
我想要创建一个无限滚动的列表。
通过将react-apollo的fetchMore和react-infinite-scroller结合起来,可以实现。
import React, { Component } from 'react';
import { Query } from 'react-apollo';
import InfiniteScroll from 'react-infinite-scroller';
import { CircularProgress } from '@material-ui/core';
const GET_ALL_COMPANIES = gql`
query($offset: Int, $limit: Int) {
allCompanies(offset: $offset, limit: $limit) {
data {
id
name
tel
representative_last_name
representative_first_name
post_code
prefecture
city
region
street
building
}
pageInfo {
startCursor
endCursor
hasNextPage
}
}
}
`;
const CompanyListWithGql = () => {
return (
<Query
query={GET_ALL_COMPANIES}
variables={{
offset: 0,
limit: 100
}}
>
{({ data, fetchMore, loading }) => {
const { allCompanies } = data;
if (loading || !allCompanies) {
return <CircularProgress />;
}
return (
<CompanyList allCompanies={allCompanies} fetchMore={fetchMore} />
);
}}
</Query>
);
};
class CompanyList extends Component<Props> {
props: Props;
onLoadMore = () => {
const {
allCompanies: { data: companies },
fetchMore
} = this.props;
fetchMore({
variables: {
offset: companies.length
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
const prevCompanies = prev.allCompanies.data;
const currentCompanies = fetchMoreResult.allCompanies.data;
return {
...prev,
allCompanies: {
...prev.allCompanies,
data: [...prevCompanies, ...currentCompanies],
pageInfo: fetchMoreResult.allCompanies.pageInfo
}
};
}
});
};
render() {
const {
allCompanies: { data: companies, pageInfo }
} = this.props;
return (
<InfiniteScroll
loadMore={this.onLoadMore}
hasMore={pageInfo.hasNextPage}
loader={<CircularProgress />}
>
<table className="table is-striped">
<thead>
<tr>
<th>ID</th>
<th>会社名</th>
<th>tel</th>
<th>郵便番号</th>
<th>都道府県</th>
<th>市区町村</th>
<th>地域名</th>
<th>番地</th>
<th>建物名</th>
</tr>
</thead>
<tbody>
{companies.map((company, i) => (
<tr key={company.id}>
<td>{company.id}</td>
<td>{company.name}</td>
<td>{company.tel}</td>
<td>{company.post_code}</td>
<td>{company.prefecture}</td>
<td>{company.city}</td>
<td>{company.region}</td>
<td>{company.street}</td>
<td>{company.building}</td>
</tr>
))}
</tbody>
</table>
</InfiniteScroll>
);
}
}
export default CompanyListWithGql;
在上面的例子中,初始显示使用偏移量(offset):0,限制数量(limit):100来获取数据,每显示100条数据,偏移量(offset)的值增加100。
在react-infinite-scroller中,可以通过指定加载器(loader)来指定在api获取中显示的组件,因此可以实现无限滚动,直到显示第100条:加载器旋转 => 后续100条数据被追加显示。
<InfiniteScroll
loadMore={this.onLoadMore}
hasMore={pageInfo.hasNextPage}
loader={<CircularProgress />}
>
另外,为了实现无限滚动,在Apollo Server端需要实现以下返回值的功能。
const GET_ALL_COMPANIES = gql`
query($offset: Int, $limit: Int) {
allCompanies(offset: $offset, limit: $limit) {
data { // dataには会社情報
id
name
tel
post_code
prefecture
city
region
street
building
}
pageInfo { // pageInfoには無限スクロールのための情報を含める
startCursor // offsetの値
endCursor // limitの値
hasNextPage // データがまだあるかどうかのboolean
}
}
}
`;
以下是返回上述响应的Apollo Server的实现示例。我们使用Sequelize作为ORM。
const companies = await db.companies.findAll({
attributes: [
'id',
'name',
'tel',
'post_code',
'prefecture',
'city',
'region',
'street',
'building',
],
offset,
limit,
});
const count = await db.companies.count();
return {
data: companies,
pageInfo: {
startCursor: offset,
endCursor: limit,
hasNextPage: offset !== count,
},
};
我想要制作一个表格。
(Wǒ .)
如果使用formik来创建验证表单,可以想象成这样。
import React, { Component } from 'react';
import { withFormik, Field, Form } from 'formik';
import * as yup from 'yup';
import { graphql, compose } from 'react-apollo';
import gql from 'graphql-tag';
export const UPDATE_USER = gql`
mutation updateUser($name: String, $age: Int) {
updateUser(name: $name, age: $age) {
id
name
age
}
}
`;
class Hoge extends Component<Props> {
props: Props;
render() {
const { isSubmitting } = this.props;
return (
<Form>
<div>
<div>
<label>
名前
<Field type="text" placeholder="名前" name="name" />
</label>
</div>
<div>
<label>
年齢
<Field type="number" placeholder="年齢" name="age" />
</label>
</div>
<div>
<button type="submit" disabled={isSubmitting}>
更新
</button>
</div>
</div>
</Form>
);
}
}
export default compose(
graphql(UPDATE_USER, { name: 'updateUser' }), // 1) nameで指定した値で
withFormik({
mapPropsToValues: ({ user }) => ({
name: user.name,
age: user.age
}),
handleSubmit: (values, { props }) => {
const { updateUser } = props; // 2) propsに関数として渡ってくる
updateUser({
variables: {
name: values.name,
age: values.age
}
});
},
validationSchema: yup.object().shape({
name: yup.string().required(),
age: yup.number().required()
})
})
)(Hoge);
使用 react-apollo 的 compose 方法,将 graphql 函数合成为组件的感觉。
export default compose(
graphql(UPDATE_USER, { name: 'updateUser' }),
通过这样做,可以将能够执行GraphQL的UPDATE_USER函数作为props.updateUser传递到React组件中。如果没有指定name,则会通过props.mutate函数传递,所以最好指定name。
此外,使用compose合成GraphQL函数的数量是如此,通过这样的写法可以将多个函数作为props传递。
参考链接:https://www.apollographql.com/docs/react/basics/setup
export default compose(
graphql(gql`mutation (...) { ... }`, { name: 'createTodo' }),
graphql(gql`mutation (...) { ... }`, { name: 'updateTodo' }),
graphql(gql`mutation (...) { ... }`, { name: 'deleteTodo' }),
)(MyComponent);
function MyComponent(props) {
// Instead of the default prop name, `mutate`,
// we have three different prop names.
console.log(props.createTodo);
console.log(props.updateTodo);
console.log(props.deleteTodo);
return null;
}
当在表单中更新值时,显示的值会被重置。
以下是我在遇到类似情况时采取的解决方法。
请查看这里的处理方法:
https://www.apollographql.com/docs/react/advanced/caching.html#automatic-updates
导致此问题的原因是缓存未能正确更新。
当事象发生时,用户更新的mutation只返回了id,其他值并未被获取。
export const UPDATE_USER = gql`
mutation updateUser($name: String, $age: Int) {
updateUser(name: $name, age: $age) {
id
}
}
`;
在添加name和age的获取功能后,缓存也会被更新,解决了表单显示倒退的问题。
export const UPDATE_USER = gql`
mutation updateUser($name: String, $age: Int) {
updateUser(name: $name, age: $age) {
id
name
age
}
}
`;
另外,我们可以通过指定fetchPolicy来选择是否使用缓存来执行react-apollo的查询。
虽然最开始没有使用缓存就能解决这个问题,但由于每次都需要进行fetch操作,导致显示变得较慢。
我想在React的生命周期或者onClick事件中使用GraphQL查询。
使用compose将withApollo与组件合成后,能够将graphql的client作为props传递给组件。
通过使用该client,可以发起graphql的查询和变更操作。
import React, { Component } from 'react';
import { compose, withApollo } from 'react-apollo';
import gql from 'graphql-tag';
const GET_USER = gql`
query($id: Int) {
user(id: $id) {
name
}
}
`;
class Hoge extends Component<Props> {
props: Props;
componentDidMount = async () => {
const { client } = this.props;
const { data } = await client.query({
query: GET_USER,
variables: { id: 1 }
});
// data.userを使ってなんか処理する
};
render() {
return <div>some code</div>;
}
}
export default compose(withApollo)(Hoge);
向props传递graphql函数的两种方法。
在组件的props中传递graphql函数有两种方法如下:
// graphqlを使う方法
export default compose(graphql(UPDATE_USER, { name: 'updateUser' }))(MyComponent),
// withApolloを使う方法
export default compose(withApollo)(MyComponent);
然而,当使用compose方法来组合graphql时,遇到了query的graphql函数出现了”is not a function”错误的bug?(而对于mutation则没有这个问题)
如果遇到这个错误,我认为可以使用”withApollo”来解决。
我想将传递给mutation的参数设为对象。
如果要更新的属性增加,可以通过不断增加参数来进行处理,但是这样的描述会变得冗长。
// これくらいならまだいいが、、
export const UPDATE_USER = gql`
mutation updateUser($name: String, $age: Int) {
updateUser(name: $name, age: $age) {
id
name
age
}
}
`;
// 引数が多くなってくるとコードが見にくい!
export const UPDATE_USER = gql`
mutation updateUser($name: String, $age: Int, $hoge: string, $huga: string, $bar: string) {
updateUser(name: $name, age: $age, hoge: $hoge, huga: $huga, bar: $bar) {
id
name
age
hoge
huga
bar
}
}
`;
在这种情况下,您可以通过在apollo-server的一侧定义schema来使描述清晰明了。在定义传递给mutation的参数的schema时,可以使用input types。
如果在输入时定义了类似以下的模式,那么…
input UpdateUserParams {
name: String!
age: Int!
hoge: String
huga: String
bar: String
}
// !をつけることでその属性は必須のパラメータにできます。
之前的updateUser可以这样写。
export const UPDATE_USER = gql`
mutation updateUser($input: UpdateUserParams!) {
updateUser(input: $input) {
id
name
age
hoge
huga
bar
}
}
`;
我想写一个不需要返回值的突变(mutation)。
当执行变异查询时,如果不需要特定的返回值,则可以按照以下方式编写。
export const UPDATE_USER = gql`
mutation updateUser($input: UpdateUserParams!) {
updateUser(input: $input)
}
`;