技術探し

JavaScriptを中心に記事を書いていきます :;(∩´﹏`∩);:

react-apollo@3.0.0へ移行する

現在のステータスはbetaですので、急ぐ必要はないです。

github.com

$ npm i react-apollo@beta

大きな注目点としては、hooksの追加と@apollo/react-testingへの分離でしょうか。

この記事は、自分がぶつかった部分のまとめなので抜けはあります、注意してください。

Entry Point

内部的な話ですが、すべて名前空間にパッケージが移され、react-apollo自体は後方互換のためのプロキシとなります。

react-apolloのentryは以下のようになります。

// index.js

export { getApolloContext, resetApolloContext, ApolloProvider, ApolloConsumer } from '@apollo/react-common';
export { Query, Mutation, Subscription } from '@apollo/react-components';
export { graphql, withQuery, withMutation, withSubscription, withApollo } from '@apollo/react-hoc';
export { useQuery, useMutation, useSubscription, useApolloClient, getMarkupFromTree, getDataFromTree, renderToStringWithData } from '@apollo/react-hooks';
//# sourceMappingURL=index.js.map

Hooks

react-hooksの登場に伴って、apolloも今後はhooksが主流になっていきます。

QueryとMutationは以下のような書き方に変更できます。

// スキーマ
const GET_ORGS = gql`
  query {
    organizations {
      name
      uid
    }
  }
`;

const ADD_ORG = gql`
  mutation addOrganization($name: String!) {
    addOrganization(name: $name) {
      name
      uri
      uid
    }
  }
`;

以前のクラス

// ここでdataの型を今までは入れないと推論ができなかった
class GetOrgsQuery extends Query<{ organizations: Organizations }> {}

export class OrganizationsBox extends React.PureComponent<unknown, State> {
  state = { currentValue: '' };

  onChange = (e: React.FormEvent<HTMLInputElement>) => {
    this.setState({ currentValue: e.currentTarget.value });
  };

  onSubmit = (addOrganization: MutationFunc) => {
    addOrganization({ variables: { name: this.state.currentValue } });
    this.setState({ currentValue: '' });
  };

  render() {
    return (
      <GetOrgsQuery query={GET_ORGS}>
        {({ loading, error, data }) => (
          <Ul>
            {error || loading ? <p>{error ? `Error! ${error.message}` : 'loading...'}</p> : null}
            {data &&
              data.organizations &&
              data.organizations.map(({ name, uid }) => (
                <Li key={uid}>
                  <A to={`/orgs/${name}`}>{name}</A>
                </Li>
              ))}
            <Mutation mutation={ADD_ORG} refetchQueries={[{ query: GET_ORGS }]}>
              {(addOrganization, { error }) => (
                <React.Fragment>
                  {error && <p>{`Error! ${error.message}`}</p>}
                  <InputBox>
                    <input onChange={this.onChange} value={this.state.currentValue} />
                    <button onClick={() => this.onSubmit(addOrganization)}>Add</button>
                  </InputBox>
                </React.Fragment>
              )}
            </Mutation>
          </Ul>
        )}
      </GetOrgsQuery>
    );
  }
}

Hooks

import { useQuery, useMutation } from 'react-apollo';

export const OrganizationsBox: React.FC = () => {
  const [currentValue, updateCurrentValue] = useState('');
  const { loading, error, data } = useQuery<{ organizations: Organizations }>(GET_ORGS);
  const [addOrganization, { error, data }]  = useMutation(ADD_ORG, {
    refetchQueries: [{ query: GET_ORGS }]
  });

  const onChange = (e: React.FormEvent<HTMLInputElement>) => {
    updateCurrentValue(e.currentTarget.value);
  };

  const onSubmit = (addOrganization: any) => {
    addOrganization({ variables: { name: currentValue } });
    updateCurrentValue('');
  };

  if (error || loading) {
    return <p>{error ? `Error! ${error.message}` : 'loading...'}</p>;
  }

  return (
    <Ul>
      {data &&
        data.organizations.map(({ name, uid }) => (
          <Li key={uid}>
            <A to={`/orgs/${name}`}>{name}</A>
          </Li>
        ))}
      <InputBox>
        <input onChange={onChange} value={currentValue} />
        <button onClick={() => onSubmit(addOrganization)}>Add</button>
      </InputBox>
    </Ul>
  );
};

上記の通り、jsx内で関数を書く必要がなくなり、可読性が増しました。

const { loading, error, data } = useQuery(GET_ORGS);
const [addOrganization, { error, data }]  = useMutation(ADD_ORG);
const { loading, data } = useSubscription(LATEST_ORGS);

と定義し、コールすれば終わりです。
注意点として、Mutationだけユーザー定義が入るため配列です。

react-testing

よくテストやstorybookで使われる MockedProviderreact-apolloからのインポートではなく、@apollo-react-testingというライブラリからのインポートに変更されます。

- import { MockedProvider } from 'react-apollo/test-utils';
+ import { MockedProvider } from '@apollo/react-testing';

MockLink, MockSubscriptionLink も同様に移動しました。

型定義

リネーム

MutationFnMutationFunction に中身は同様でリネームされました。

削除

自分が把握している限りで以下の型がなくなりました。

GraphqlQueryControlsMutationFuncChildDataProps

https://github.com/apollographql/react-apollo/pull/2892/commits/ade881f07b1175d28b0aae79915bfc5ed8dd9e5a

独自で定義する場合は以下のようになります。

import { OperationVariables, QueryControls, MutationFunction, DataProps } from 'react-apollo';

export interface GraphqlQueryControls<TGraphQLVariables = OperationVariables>
  extends QueryControls<any, TGraphQLVariables> {}

export type MutationFunc<TData = any, TVariables = OperationVariables> = MutationFunction<
  TData,
  TVariables
>;

export type ChildDataProps<
  TProps = {},
  TData = {},
  TGraphQLVariables = OperationVariables
> = TProps & DataProps<TData, TGraphQLVariables>;

その他

  • walkTreeのサポートが無くなる
  • preactのサポートがなくなる
  • composeもなくなる
    • 中身がlodashのflowRightと同じなので各自で定義してねとのこと

昔から思っていた不満がどんどんなくなってきていて、最高になってきているので今後も楽しみ。