你会如何在React-Relay应用程序中进行文件上传?

时间:2015-10-10 23:51:07

标签: javascript reactjs graphql relayjs

文件上传似乎是一种变异。它通常伴随着其他数据。但它是一个很大的二进制blob,所以我不确定GraphQL如何处理它。如何将文件上传集成到使用Relay构建的应用程序中?

5 个答案:

答案 0 :(得分:10)

首先,您需要在前端组件中编写中继更新。 像这样:

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

答案 1 :(得分:5)

我找到了an explanation in the docs。您可以继承Relay.Mutation并实现getFiles函数。

此外,express-graphql在其测试用例中提供an example如何在服务器端处理此问题。

答案 2 :(得分:4)

我只是从他的blog分享Marc-Andre Giroux的发现,这是Rails特有的,所以我会尝试使它更通用,并提供@Nick提供的答案的详细信息。

共有两部分:

  • 客户端Javascript代码
  • 服务器端服务器特定代码

客户端Javascript代码

客户端代码还包括两部分:

  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)的组件,用于呈现用于选择文件的UI,并调用突变来执行上载

    // 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()
    }
    
  3. 服务器端服务器专用代码

    服务器端代码也包含两部分:

    1. 处理以MIME多部分格式检索上载文件并将其传递给GraphQL架构中定义的Mutation的部分。我们提供NodeJS和Rails示例,它们可以帮助您为其他服务器派生解决方案。
    2. 对于NodeJS Express服务器(从@Nick指出的express-graqphl测试用例中提取):

          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. Mutation可以检索传递给它的文件MIME多部分
      2. 对于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
        

答案 3 :(得分:1)

要使用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

答案 4 :(得分:0)

虽然您绝对可以实现将文件上传到GraphQL API端点,但它被认为是一种反模式(您会遇到最大文件大小等问题。)

更好的选择是从GraphQL API获取签名的URL,以将文件直接从客户端应用程序直接上传到Amazon S3,Google Cloud Storage等。

如果上传完成后服务器端代码需要将URL保存在数据库中,则可以直接预订此事件。以Google Cloud中的object change notification为例。