React Native图片资源使用的优美方案

图片资源( jpeg、png、svg、webp ... )作为与用户交互的界面元素,在客户端产品中起到了非常重要的角色作用。在应用开发中,移动端与PC的图片使用策略也有所不同。PC端较大的内存容量,快速的渲染能力使各类型图片资源都能得到较好的使用。而移动端由于设备内存,GPU渲染都与PC有较大差别,所以在App应用开发中,需要我们单独分析处理。今天我们聊聊在React Native开发中,如何优美的使用图片资源。

在 React Native 开发中,目前主流的图标解决方式大概有四种:

基本图片格式(png、jpeg)

基本图片格式是开发者最常用的一种图片资源,RN官方提供了在移动端适配的解决方案,直接使用 Image 标签就可以加载网络、本地图片。

【缺点】需要引入多倍图(@2x / @3x)进行适配,使得 jsBundle 体积增大,内存占用的消耗比较明显。热更新时对流量(虽然你不在乎),影响较大。资产变更必须伴随二进制版本。(apk | ipa)

Url

将图片保存在服务端,客户端以URL的方式进行加载。客户端不需要任何处理,例如在RN中加载网络图,只需要将URL传递给Image 组件的 src props

【缺点】缓存较为麻烦,可以依赖 react-native-fast-image 库。

IconFont (react-native-vector-icons)

熟悉Web开发的同学,对于 字体图标 绝对是不陌生的。基于此,开源库 react-native-vector-icons 实现了在RN平台的字体图标解决方案。这种方案简单,引进库和 .ttf 文件,就能使用字体图标了。

【缺点】需要随app打包,文件小,使用便利,不用担心屏幕屏幕尺寸不能热更新,需要引入额外的库

Svg

Svg 拥有体积小(Path),可缩放特性,因此不需要适配屏幕的分辨率尺寸,并且有效降低移动端内存占用问题。同时完美支持bundle热更新

【缺点】RN平台默认并不支持 Svg,幸运的是 react-native-svg 库实现了在RN移动应用中渲染 Svg 图标的能力。

方案分析

多倍图适配,.ttf 文件不能热更新的问题。现有的字体图标管理网站(iconfont、icomoo)也能生成svg文件。使得我们最终直接使用Svg。 react-native-svg 能对svg的标签解析成图片,如何将图片渲染成组件呢?第三方库 react-native-svg-uri 则能把svg文件的xml解析成 Component。迫不及待的集成,引入,运行。iOS完美的显示出来,而将 Android 进行 Release 打包过程中,Logcat抛出如下错误:

原因是在Android平台不支持直接加载 .svg 后缀的文件格式,只能允许加载 png 和 xml 格式的文件。我们打开.svg文件,可以看到如下内容:

<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1554188197278" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4579" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
<defs>
    <style type="text/css"></style>
</defs>
<path d="M790.24975 1.022977H228.123876c-42.453546 0-77.234765 35.292707-77.234765 78.769231v870.553446c39.896104 0 72.11988 33.246753 72.11988 73.654346h35.292707c0-19.948052 15.856144-36.315684 35.804196-36.315684s35.804196 16.367632 35.804196 36.315684h35.804196c0-19.948052 15.856144-36.315684 35.804196-36.315684s35.804196 16.367632 35.804195 36.315684h35.804196c0-19.948052 15.856144-36.315684 35.804196-36.315684s35.804196 16.367632 35.804196 36.315684h35.804196c0-19.948052 15.856144-36.315684 35.804195-36.315684s35.804196 16.367632 35.804196 36.315684h35.292707c0-19.948052 15.856144-36.315684 35.804196-36.315684s35.804196 16.367632 35.804196 36.315684h35.804196c0-40.407592 32.223776-73.142857 71.608391-73.142857V79.280719C867.484515 35.804196 832.703297 1.022977 790.24975 1.022977z m-118.153846 339.628372l-86.953047 88.999001h45.522478c16.879121 0 30.177822 13.298701 30.177822 30.68931s-13.298701 30.689311-30.177822 30.689311h-91.044955v62.401598h91.044955c16.879121 0 30.177822 13.298701 30.177822 30.689311s-13.298701 30.689311-30.177822 30.689311h-91.044955v93.090909c0 17.390609-13.298701 30.689311-30.177823 30.689311s-30.177822-13.298701-30.177822-30.689311v-93.090909H388.21978c-10.22977 0-20.971029-6.649351-26.085914-15.856144s-5.114885-21.482517 0-30.689311c5.114885-10.741259 15.856144-15.856144 26.085914-15.856144h91.044955V489.494505H388.21978c-16.879121 0-30.177822-13.298701-30.177822-30.68931s13.298701-30.689311 30.177822-30.689311h48.07992L345.254745 341.674326c-7.672328-8.183816-10.22977-19.948052-7.672327-30.689311s11.764236-19.948052 21.994006-22.505495c10.22977-4.091908 21.994006 0 30.177822 8.183817l119.688311 122.245754L629.130869 296.663337c7.672328-9.206793 19.436563-11.764236 30.177822-9.206794 10.22977 2.557443 19.436563 11.764236 21.994006 22.505495 2.557443 10.741259-1.022977 22.505495-9.206793 30.689311z" fill="#FF6F5A" p-id="4580">
</path>
</svg>

可以看到,其实 Svg 文件就是以path路径来绘制图标的。所以我们可以放弃 require svg 文件的方式,直接解析 svg 文件中的path 即可。同时也解决了减少 svg 占用空间,频繁 require 静态文件减慢速度的问题。

我们可以用脚本来将 svg文件 批量生成 Path 字符串,然后通过 react-native-svg-uri 来解析 Path xml。同时该库作者也考虑到 Android 的问题,为开发者提供了通过 svg path 字符串渲染图标的Api: svgXmlData 
【注意】react-native-svg-uri 更新太慢,建议不通过 npmyarn 安装,直接复制文件在项目中使用,避免版本问题。

核心实现

通过上面的解析,实现流程分为如下三步:

(1)下载Svg文件

(2)解析所有Svg文件,将svg的path统一存放在 js 文件中

(3)封装 svg 组件,通过 svgXmlData 加载svg path

可以看出第二步的实现起到来承前启后的重要性,我们来看下核心代码。

解析

/**
 * 读取svg文件
 * @param {*} svgFileName svg文件, 例如 home-icon.svg
 * @returns { 'home-icon': '<svg>... <path>...</path> ...</svg>' }
 */
function readSvgFile(svgFileName) {
    return new Promise((resolve, inject) => {
        readFile(path.join(svgFileDir, svgFileName), 'utf8', (error, svgFile) => {
            // eslint-disable-next-line no-useless-escape
            const svgPath = svgFile.replace(/<\?xml.*?\?>|<\!--.*?-->|<!DOCTYPE.*?>/g, '');
            if (error) {
                inject(error);
            }
            resolve({
                [svgFileName.slice(0, svgFileName.lastIndexOf('.'))]: svgPath,
            });
        });
    });
}

/**
 * 读取svg文件夹目录所有svg文件
 * @returns { 'home-icon': '<path>...</path>', 'xxx': '<path>...</path>' ... }
 */
function readSvgDir() {
    return new Promise((resolve, inject) => {
        readdir(svgFileDir, (error, svgFiles) => {
            if (error) {
                inject(error);
            }
            // svgFiles: string[]
            Promise.all(svgFiles.map((svgFileName) => readSvgFile(svgFileName)))
                .then((data) => resolve(data))
                .catch((err) => inject(err));
        });
    });
}

/**
 * 生成 .js 文件
 */
readSvgDir().then((data) => {
    const svgFile = `export default {
        ${
    data.map((item, index) => `${Object.keys(item)[0]}: '${Object.values(item)[0]}'\n`)
}
    }`;
    writeFile(path.resolve(__dirname, `./${GENERATE_SVG_FILE_NAME}`), svgFile, (err) => {
        if (err) {
            throw new Error(err);
        }
    });
}).catch((error) => {
    throw new Error(error);
});

从上述代码可以看到,我们通过读取遍历svg文件夹下的所有svg文件,拿到<svg>... <path>...</path> ...</svg>的字符串,并将其生成js文件统一管理。最终生成的文件内容如下:

export default {
    iconImage: '<svg class="icon" width="128px" height="128.00px" viewBox="0 0 />',
};

封装 SvgIcon 组件

/**
 * svg 图片组件
 * @export
 * @class SvgIcon
 * @extends {PureComponent}
 */
import React, { PureComponent } from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import SvgUri from './SvgUri';
import svgXmlData from './svgXmlData';

export default class SvgIcon extends PureComponent {
    static propTypes = {
        style: PropTypes.object,
        /* eslint-disable react/require-default-props */
        color: PropTypes.object,
        size: PropTypes.shape(
            {
                width: PropTypes.number.isRequired,
                height: PropTypes.number.isRequired,
            }
        ).isRequired,
        icon: PropTypes.string.isRequired,
    }

    static defaultProps = {
        style: {},
    }

    render() {
        const {
            size,
            color,
            style,
            icon,
        } = this.props;
        const svgXmlPath = svgXmlData[icon];
        // eslint-disable-next-line no-nested-ternary
        return svgXmlData
            ? color ? (
                <SvgUri
                    fill={color}
                    style={style}
                    width={size.width}
                    height={size.height}
                    svgXmlData={svgXmlPath}
                />
            ) : (
                <SvgUri
                    style={style}
                    width={size.width}
                    height={size.height}
                    svgXmlData={svgXmlPath}
                />
            ) : <View />;
    }
}

使用

<SvgIcon
    icon={iconImage}
    style={{ marginRight: 5 }}
    size={{ width: 26, height: 26 }}
/>

通过以上实现,我们就优美的解决了图片资源加载渲染问题。详细代码已上传到GitHub:react-native-svg-icon

WebP

默认情况下 React Native 是不支持 GIF 和 WebP 格式的。Android 需要在android/app/build.gradle文件中根据需要手动添加以下模块:

dependencies {
  // 如果你需要支持Android4.0(API level 14)之前的版本
  compile 'com.facebook.fresco:animated-base-support:1.10.0'

  // 如果你需要支持GIF动图
  compile 'com.facebook.fresco:animated-gif:1.10.0'

  // 如果你需要支持WebP格式,包括WebP动图
  compile 'com.facebook.fresco:animated-webp:1.10.0'
  compile 'com.facebook.fresco:webpsupport:1.10.0'

  // 如果只需要支持WebP格式而不需要动图
  compile 'com.facebook.fresco:webpsupport:1.10.0'
}

iOS需要依赖第三方平台来实现Webp的加载,如:react-native-webp-supprot 。推荐大家看下这篇文章:

React Native + WebP: Reducing bundle + binary sizes, increase speed with .webp image format

发布了214 篇原创文章 · 获赞 371 · 访问量 92万+

猜你喜欢

转载自blog.csdn.net/u013718120/article/details/88975393
今日推荐