普通视图

发现新文章,点击刷新页面。
昨天 — 2025年9月6日首页

前端上传切片优化以及实现

作者 光影少年
2025年9月6日 18:16

你说的 前端上传切片优化以及实现,主要涉及到大文件分片上传(chunk upload)、并发控制、断点续传、秒传、重试机制等。下面我给你梳理一下实现思路和优化点。


🔹 场景与痛点

  • 大文件上传(>100MB):直接上传会超时或失败。
  • 网络不稳定:中断后需重传。
  • 上传速度慢:需要并发分片上传。
  • 服务端压力大:需要合理控制并发与分片大小。

🔹 基础实现流程

  1. 文件切片
    使用 Blob.slice 方法将文件分割为固定大小的分片(比如 2MB/5MB)。

    function createFileChunks(file: File, chunkSize = 2 * 1024 * 1024) {
      const chunks: Blob[] = [];
      let cur = 0;
      while (cur < file.size) {
        chunks.push(file.slice(cur, cur + chunkSize));
        cur += chunkSize;
      }
      return chunks;
    }
    
  2. 计算文件唯一标识(hash)
    通常用 MD5/SHA1 或者基于文件名 + 大小 + 上次修改时间。
    可以在浏览器端用 spark-md5

    import SparkMD5 from "spark-md5";
    
    async function calculateHash(chunks: Blob[]) {
      const spark = new SparkMD5.ArrayBuffer();
      for (const chunk of chunks) {
        const buffer = await chunk.arrayBuffer();
        spark.append(buffer);
      }
      return spark.end(); // 文件hash
    }
    
  3. 上传分片
    每个分片通过 FormData 上传:

    async function uploadChunk(chunk: Blob, index: number, fileHash: string) {
      const formData = new FormData();
      formData.append("chunk", chunk);
      formData.append("index", String(index));
      formData.append("fileHash", fileHash);
    
      return fetch("/upload", {
        method: "POST",
        body: formData,
      });
    }
    
  4. 合并文件
    前端所有分片上传完成后,调用后端 /merge 接口,通知服务端进行文件合并。


🔹 优化点

  1. 并发控制
    使用 Promise.all 并发上传,但需要限制最大并发数:

    async function limitUpload(chunks, limit = 5) {
      const pool: Promise<any>[] = [];
      let i = 0;
    
      async function run() {
        if (i >= chunks.length) return;
        const task = uploadChunk(chunks[i], i, "fileHash").then(run);
        pool.push(task);
        i++;
      }
    
      const workers = Array(limit).fill(null).map(run);
      await Promise.all(workers);
    }
    
  2. 断点续传

    • 上传前向服务端查询已上传的分片列表。
    • 跳过已完成的分片,仅上传剩余分片。
  3. 秒传

    • 上传前计算 hash
    • 询问服务端该文件是否已存在,存在则直接返回成功。
  4. 失败重试

    • 针对失败的分片,做 最多 N 次重试
    async function retry(fn, retries = 3) {
      while (retries--) {
        try {
          return await fn();
        } catch (e) {
          if (!retries) throw e;
        }
      }
    }
    
  5. 上传进度显示

    • 每个分片上传时用 XMLHttpRequest.onprogressfetch + ReadableStream 计算进度。
    • 进度 = 已上传分片大小 / 总文件大小。

🔹 前端完整流程

  1. 选择文件 → 切片 → 计算 hash
  2. 调用 /checkFile → 返回已上传分片。
  3. 跳过已完成分片,继续上传剩余分片(带并发控制 & 重试机制)。
  4. 上传完后请求 /merge
  5. 前端实时展示进度条。

🔹 技术选型

  • 切片与上传:原生 Blob.slice + fetch/axios
  • hash计算spark-md5(大文件可用 Web Worker 避免卡 UI)。
  • 断点续传:前端记录进度 / 服务端存储分片状态。
  • 进度显示XMLHttpRequest.onprogressaxios.onUploadProgress

昨天以前首页

React Native 中 useMemo 的使用与优化

作者 冯志浩
2025年8月27日 18:28

在 React Native 开发中,性能优化一直是开发者关注的重点之一。React 提供了许多内置的钩子(hooks)来帮助开发者提升应用的性能,其中 useMemo 就是一个十分有效的工具。本文将介绍 useMemo 的基本使用方法,并通过实际案例展示它在 React Native 中的应用场景,最后讨论如何有效地利用 useMemo 来优化 React Native 应用的性能。

什么是 useMemo

useMemo 是 React 官方提供的一个钩子,它的作用是用于缓存计算结果,以避免在每次渲染时都重复计算相同的值。它接收两个参数:

  • 第一个参数:是一个函数,返回需要缓存的计算结果。
  • 第二个参数:是一个依赖项数组,只有当依赖项发生变化时,useMemo 才会重新计算缓存的值。

如果依赖项没有发生变化,useMemo 会返回缓存的值,而不是重新执行计算。

为什么需要 useMemo

在 React 中,每次组件重新渲染时,都会重新计算组件中的所有状态和变量。对于一些开销较大的计算,重复执行会导致性能问题,尤其是在列表渲染或复杂计算时。useMemo 可以帮助我们缓存计算结果,减少不必要的计算,从而提升性能。

useMemo 在 React Native 中的应用

列表渲染优化

在 React Native 中,渲染长列表时,性能问题往往是最常见的瓶颈。假设我们有一个包含大量数据的列表,并且每个列表项的渲染需要进行复杂的计算。使用 useMemo 可以缓存这些计算结果,避免每次渲染都重新计算。

示例代码如下:

import React, { useMemo, useState } from 'react';
import { FlatList, Text, View } from 'react-native';

const expensiveCalculation = (data) => {
  // 假设这是一个复杂的计算过程
  return data.map(item => item.name.toUpperCase());
};

const MyComponent = ({ data }) => {
  // 使用 useMemo 缓存计算结果
  const computedData = useMemo(() => expensiveCalculation(data), [data]);

  return (
    <FlatList
      data={computedData}
      keyExtractor={(item) => item.id.toString()}
      renderItem={({ item }) => (
        <View>
          <Text>{item}</Text>
        </View>
      )}
    />
  );
};

在上面的代码中,expensiveCalculation 代表一个复杂计算函数,useMemo 会确保只有在 data 变化时才重新计算,而不是每次渲染时都执行计算。

减少不必要的组件重新渲染

组件重渲染可能会引发性能问题。通过使用 useMemo,我们可以缓存某些组件的 props 或状态,避免它们因父组件重新渲染而被不必要地重新创建。

示例代码如下:

import React, { useMemo, useState } from 'react';
import { Button, Text, View } from 'react-native';

const ExpensiveComponent = ({ value }) => {
  console.log('ExpensiveComponent rendered');
  return <Text>{value}</Text>;
};

const ParentComponent = () => {
  const [count, setCount] = useState(0);

  // 使用 useMemo 缓存组件的 props
  const expensiveValue = useMemo(() => {
    return `The count is ${count}`;
  }, [count]);

  return (
    <View>
      <ExpensiveComponent value={expensiveValue} />
      <Button title="Increment" onPress={() => setCount(count + 1)} />
    </View>
  );
};

在这个例子中,expensiveValue 只会在 count 改变时重新计算。这样,即使父组件重新渲染,ExpensiveComponent 也不会因为父组件的更新而重新渲染,从而减少了不必要的渲染。

依赖项变化优化

useMemo 还可以用来优化依赖项较多的复杂计算。通常,复杂的逻辑涉及多个依赖项,而每次其中一个依赖项变化时,都会重新计算。这时候,useMemo 可以帮助我们精确控制何时重新计算。

示例代码如下:

import React, { useMemo, useState } from 'react';

const ComplexCalculation = ({ a, b, c }) => {
  const result = useMemo(() => {
    console.log('Calculating...');
    return a + b + c;
  }, [a, b, c]);

  return <Text>{`Result: ${result}`}</Text>;
};

const MyComponent = () => {
  const [a, setA] = useState(0);
  const [b, setB] = useState(0);
  const [c, setC] = useState(0);

  return (
    <View>
      <ComplexCalculation a={a} b={b} c={c} />
      <Button title="Increment A" onPress={() => setA(a + 1)} />
      <Button title="Increment B" onPress={() => setB(b + 1)} />
      <Button title="Increment C" onPress={() => setC(c + 1)} />
    </View>
  );
};

在上面的例子中,ComplexCalculation 组件只会在 abc 的值发生变化时重新计算。即使其他状态变化,也不会影响计算的结果。

性能优化建议

尽管 useMemo 是一个非常强大的工具,但也需要注意使用场景和方法不当可能导致性能下降。以下是一些优化建议:

  • 不要过度使用 useMemouseMemo 在提高性能的同时也会带来一定的开销,因此在一些简单的计算中,过度使用它可能会适得其反。对于一些性能开销不大的计算,直接计算即可,而不必引入 useMemo
  • 精确控制依赖项:确保依赖项数组中列出所有需要观察的变量,避免漏掉某些依赖,导致缓存的计算值不准确。
  • useCallback 配合使用:useMemouseCallback 都用于缓存数据和函数,二者可以结合使用。例如,可以缓存一个回调函数,避免每次重新定义。

总结

useMemo 是 React Native 中非常有用的性能优化工具,能够缓存计算结果,减少不必要的重新计算和渲染。通过合理的使用 useMemo,我们可以显著提高应用的响应速度和流畅度。然而,使用时要根据实际场景来权衡是否使用,避免不必要的性能开销。在复杂的应用中,合理利用 useMemo 与其他优化手段结合,才能发挥最大效用。

❌
❌