Как бы вы загрузили файлы в приложение React-Relay?

Загрузка файла кажется мутацией. Он часто сопровождается другими данными. Но это большой двоичный объект, поэтому я не уверен, как GraphQL может с ним справиться. Как бы вы интегрировали загрузку файлов в приложение, созданное с помощью Relay?


person Nick Retallack    schedule 10.10.2015    source источник


Ответы (5)


Сначала вам нужно написать обновление Relay в вашем интерфейсном компоненте. Как это:

onDrop: function(files) {
  files.forEach((file)=> {
    Relay.Store.commitUpdate(
      new AddImageMutation({
        file,
        images: this.props.User,
      }),
      {onSuccess, onFailure}
    );
  });
},

А затем выполните мутацию во внешнем интерфейсе:

class AddImageMutation extends Relay.Mutation {
   static fragments = {
     images: () => Relay.QL`
       fragment on User {
         id,
       }`,
     };

   getMutation() {
     return Relay.QL`mutation{ introduceImage }`;
   }

   getFiles() {
     return {
       file: this.props.file,
     };
   }

   getVariables() {
     return {
       imageName: this.props.file.name,
     };
   }

   getFatQuery() {
     return Relay.QL`
       fragment on IntroduceImagePayload {
         User {
           images(first: 30) {
             edges {
               node {
                 id,
               }
             }
           }
         },
         newImageEdge,
       }
     `;
   }

   getConfigs() {
     return [{
       type: 'RANGE_ADD',
       parentName: 'User',
       parentID: this.props.images.id,
       connectionName: 'images',
       edgeName: 'newImageEdge',
       rangeBehaviors: {
         '': 'prepend',
       },
     }];
   }
 }

И наконец, реализуйте обработчик на сервере/схеме.

const imageMutation = Relay.mutationWithClientMutationId({
  name: 'IntroduceImage',
  inputFields: {
    imageName: {
      type: new GraphQL.GraphQLNonNull(GraphQL.GraphQLString),
    },
  },
  outputFields: {
    newImageEdge: {
      type: ImageEdge,
      resolve: (payload, args, options) => {
        const file = options.rootValue.request.file;
        //write the image to you disk
        return uploadFile(file.buffer, filePath, filename)
        .then(() => {
          /* Find the offset for new edge*/
          return Promise.all(
            [(new myImages()).getAll(),
              (new myImages()).getById(payload.insertId)])
          .spread((allImages, newImage) => {
            const newImageStr = JSON.stringify(newImage);
            /* If edge is in list return index */
            const offset = allImages.reduce((pre, ele, idx) => {
              if (JSON.stringify(ele) === newImageStr) {
                return idx;
              }
              return pre;
            }, -1);

            return {
              cursor: offset !== -1 ? Relay.offsetToCursor(offset) : null,
              node: newImage,
            };
          });
        });
      },
    },
    User: {
      type: UserType,
      resolve: () => (new myImages()).getAll(),
    },
  },
  mutateAndGetPayload: (input) => {
    //break the names to array.
    let imageName = input.imageName.substring(0, input.imageName.lastIndexOf('.'));
    const mimeType = input.imageName.substring(input.imageName.lastIndexOf('.'));
    //wirte the image to database
    return (new myImages())
    .add(imageName)
    .then(id => {
    //prepare to wirte disk
      return {
        insertId: id,
        imgNmae: imageName,
      };
    });
  },
});

Весь приведенный выше код вы можете найти в моем репозитории https://github.com/bfwg/relay-gallery Существует также живая демонстрация https://fanjin.io

person Fan Jin    schedule 23.02.2016
comment
Пожалуйста, включите соответствующий код в свой ответ. Stack Overflow не рекомендует полагаться на внешние ссылки в качестве основы ответа, чтобы предотвратить гниение ссылок. Совершенно нормально цитировать только то, что относится к делу, а затем ссылаться на полный репозиторий. - person mech; 23.02.2016

Я нашел объяснение в документации. Вы можете создать подкласс Relay.Mutation и реализовать функцию getFiles.

Кроме того, express-graphql обеспечивает example в своих тестовых примерах того, как обрабатывать это на стороне сервера.

person Nick Retallack    schedule 11.10.2015
comment
Я сделал. Я обновлю это с более подробной информацией, когда у меня будет шанс. - person Nick Retallack; 17.10.2015

Я просто делюсь выводами Марка-Андре Жиру из его блога, который специфичен для Rails, поэтому я постараюсь сделать его более общим и предоставлю подробности ответа, предоставленного @Nick.

Есть 2 части:

  • Код Javascript на стороне клиента
  • Серверный специфичный для сервера код

Код Javascript на стороне клиента

Клиентский код далее состоит из 2 частей:

  1. Мутация для загрузки файла, которая расширяет Relay.Mutation (UploadFileMutation)

    // The actual mutation
    class UploadFileMutation extends Relay.Mutation {
      getFiles() {
        return {
          file: this.props.file,
        };
      }
    
      // ... Rest of your mutation
    }
    
  2. Компонент, который содержит компонент React (FileUploader) для отображения пользовательского интерфейса для выбора файла и вызывает мутацию для загрузки.

    // A react component to upload a file
    class FileUploader extends React.Component {
      onSubmit() {
        const name = this.refs.name.value;
        const file = this.refs.fileInput.files.item(0);
        Relay.Store.update(
          new UploadFileMutation({
            name: name,
            file: file,
          })
        );
      }
    
      // ... Rest of React component, e.g., render()
    }
    

Серверный серверный код

Серверный код также состоит из 2-х частей:

  1. Часть для обработки загрузки загруженного файла в многокомпонентном формате MIME и его передачи в мутацию, определенную в схеме GraphQL. Мы предоставляем примеры NodeJS и Rails, которые должны помочь вам найти решения для других серверов.

Для сервера NodeJS Express (извлечено из express- тестовые примеры graqphl, как указал @Nick):

    import multer from 'multer';

    var app = express();
    var graphqlHTTP = require('express-graphql');

    // Multer provides multipart form data parsing.
    var storage = multer.memoryStorage();

    app.use(urlString(), multer({ storage }).single('file'));

    // Providing the request, which contains the file MIME
    // multipart as `rootValue` to enable it to
    // be accessible from within Schema resolve functions.
    app.use(urlString(), graphqlHTTP(req => {
      return {
        schema: YourMutationSchema,
        rootValue: { request: req }
      };
    }));

Точно так же для сервера без JS, например, RubyOnRails:

    def create
      query_string = params[:query]
      query_variables = ensure_hash(params[:variables]) || {}

      query = GraphQL::Query.new(
        YourSchema,
        query_string,
        variables: query_variables,
        # Shove the file MIME multipart into context to make it
        # accessible by GraphQL Schema Mutation resolve methods
        context: { file: request.params[:file] }
     )
  1. Мутация может получить переданный ей файл MIME multipart.

Для схемы Javascript GraphQL:

    var YourMutationSchema = new GraphQLSchema({
      query: new GraphQLObjectType({
        // ... QueryType Schema
      }),
      mutation: new GraphQLObjectType({
        name: 'MutationRoot',
        fields: {
          uploadFile: {
            type: UploadedFileType,
            resolve(rootValue) {
              // Access file MIME multipart using
              const _file = rootValue.request.file;

              // ... Do something with file
            }
          }
        }
      })
    });

Для схемы Rails GraphQL:

    AddFileMutation = GraphQL::Relay::Mutation.define do
      name "AddFile"
      input_field :name, !types.String

      # ... Add your standard mutation schema stuff here

      resolve -> (args, ctx) {
        # Retrieve the file MIME multipart
        file = ctx[:file]
        raise StandardError.new("Expected a file") unless file

        # ... Do something with file
      }
    end
person nethsix    schedule 13.03.2016

Чтобы добавить к другим ответам, в Relay Modern было небольшое изменение в том, как вы должны отправлять файлы с клиента. Вместо того, чтобы иметь getFiles в вашей мутации и передавать файлы конструктору, вы можете использовать что-то вроде следующего:

UploadFileMutation.js

// @flow

import { commitMutation, graphql } from 'react-relay';

import type { Environment } from 'react-relay';
import type { UploadFileInput, UploadFileMutationResponse } from './__generated__/uploadFileMutation.graphql';

const mutation = graphql`
  mutation UploadFileMutation( $input: UploadFileInput! ) {
    UploadFile(input: $input) {
      error
      file {
        url
      }
    }
  }
`;

const getOptimisticResponse = (file: File | Blob) => ({
  UploadFile: {
    error: null,
    file: {
      url: file.uri,
    },
  },
});

function commit(
  environment: Environment,
  { fileName }: UploadFileInput,
  onCompleted: (data: UploadFileMutationResponse) => void,
  onError: () => void,
  uploadables,
) {
  return commitMutation(environment, {
    mutation,
    variables: {
      input: { fileName },
    },
    optimisticResponse: getOptimisticResponse(uploadables.fileToUpload),
    onCompleted,
    onError,
    uploadables,
  });
}

export default { commit };

Использование в компоненте:

const uploadables = {
  fileToUpload: file, // file is the value of an input field for example
};

UploadFileMutation.commit(
  this.props.relay.environment,
  { fileName },
  onCompleted,
  onError,
  uploadables
);

Параметр конфигурации uploadables в некотором роде скрыт, так как он не упоминается в документах, но его можно найти здесь: https://github.com/facebook/relay/blob/c4430643002ec409d815366b0721ba88ed3a855a/packages/relay-runtime/mutations/commitRelayModernMutation.js#L32

person jonathancardoso    schedule 26.07.2017

Хотя вы определенно можете реализовать загрузку файлов в конечную точку GraphQL API, это считается анти-шаблоном (вы столкнетесь с проблемами с максимальным размером файла и т. д.).

Лучшей альтернативой будет получение подписанного URL-адреса из API GraphQL для загрузки файла непосредственно из клиентского приложения в Amazon S3, Google Cloud Storage и т. д.

Если серверному коду необходимо сохранить URL-адрес в базе данных после завершения загрузки, он может напрямую подписаться на это событие. В качестве примера проверьте уведомление об изменении объекта в Google Cloud.

person Konstantin Tarkus    schedule 05.09.2019