React Native Web 常见问题解决方案

前言

总结一下在给公司react-native项目使用react-native-web添加网页端支持时遇到的一些问题,至于添加 WEB 支持的方案可查看如何让 React Native 项目支持 WEB 网页端

undefined (reading 'style')

Uncaught TypeError: Cannot read properties of undefined (reading 'style')

webpack构建时没有输出错误,在浏览器的日志中提示不能在undefined上读取style属性。

这是由于react-native0.62 版本移出了propTypes,可见0.62 Breaking changes,因此react-native-web也同样移出了propTypes

所以ViewPropTypesText.propTypes等都是不存在的,而一些三分包中并没有跟随着改变,就导致了这个问题,比如react-native-easy-toast 2.0库中:

import {
    View,
    ViewPropTypes as RNViewPropTypes,
} from 'react-native'
const ViewPropTypes = RNViewPropTypes || View.propTypes;

Toast.propTypes = {
    // ViewPropTypes 为 undefined
    style: ViewPropTypes.style,
}

解决方案

修改react-native-web依赖包中的文件,添加需要的propTypes,例子可见react-native-web-example/patches

  1. node_modules/react-native-web/dist/index.js中添加导出:

    export const ViewPropTypes = { style: () => {} };
    
  2. node_modules/react-native-web/dist/exports/Image/index.js中添加:

    Image.propTypes = { style: () => {}, source: () => {}};
    
  3. node_modules/react-native-web/dist/exports/Text/index.js中添加:

    Text.propTypes = { style: () => {} };
    

推荐使用patch-package创建补丁文件,方便管理依赖包的修改。

  1. 安装patch-package依赖

    npm i patch-package
    

    yarn add patch-package

  2. 创建补丁文件

    npx patch-package react-native-web
    

    yarn patch-package react-native-web

更多使用请看它的官方文档patch-package

__DEV__is not defined

Uncaught ReferenceError: __DEV__ is not defined

一些依赖库使用到__DEV__的环境变量,如react-native-gesture-handler库,在react-native中它是存在的,但是我们用webpack打包时没有定义这个变量,所以我们添加一下配置就可以了。

解决方案

webpack配置文件添加DefinePlugin插件来定义__DEV__变量:

const webpack = require('webpack')

return {
    // 省略其他设置
    plugins: [
        new webpack.DefinePlugin({
            __DEV__: true
        }),
    ],
}

你也可以根据process.env.NODE_ENV去设置__DEV__的值,不过影响并不大。

图片不显示

react-native运行原生应用时正常显示,但是网页端不显示,主要是因为在使用Image组件时没有设置图片的宽高属性,或者在webpack的图片加载器没有设置esModule: false

解决方案

建议在webpack解析图片时直接使用react-native-web-image-loader加载器,它可以将图片素材解析成react-native需要的对象,包含了widthheight等属性。

return {
    // 省略其他配置
    module: {
        rules: [
            {
                test: /\.(png|jpe?g|gif)$/,
                options: {
                    name: '[name].[hash:8].[ext]',
                    outputPath: 'images',
                    scalings: { '@2x': 2, '@3x': 3 },
                    esModule: false,
                },
                loader: 'react-native-web-image-loader',
            }
        ],
    },
}

样式失效或异常

网页端显示效果和原生APP显示不一样,或者从StyleSheet.create创建的样式对象取值报错,这是因为react-native-web解析StyleSheet.create方法创建的样式后是一个样式id,是一个number数字并不是对象,请看下方例子:

import React from 'react'
import { StyleSheet, Text, View } from 'react-native'

const styles = StyleSheet.create({
    container: {
        flex: 1
    },
    textWrapper: {
        width: 100
    }
})

const Example = () => {
    console.log(styles.container)
    // WEB 输出: number
    // Native 输出: {"flex": 1}

    const { width } = styles.textWrapper
    console.log(width)
    // WEB: undefined
    // Native: 100

    return (
        <View style={styles.container}>
            <Text style={styles.textWrapper}>
                Hello, Anand's Blog!
            </Text>
        </View>
    )
}

export default Example

react-native-webStyleSheet.create返回的是一个样式对象的id,是为了优化性能。

解决方案

所以,如果项目存在从样式对象里取值,你需要使用StyleSheet.flatten方法包裹一下:

console.log(StyleSheet.flatten(styles.container))
// WEB、Native: {"flex": 1}

const { width } = StyleSheet.flatten(styles.textWrapper)
console.log(width)
// WEB、Native: 100

StyleSheet.flatten方法可以让样式id取得真正的样式对象。

使用 Nodejs 内建模块

比如公司的项目使用了jwt-simple库,这个库用到了crypto模块,就需要让网页上也能使用,通常使用crypto-browserify就可以了。

解决方案

webpack 5不会自动添加node的内建模块,我们通过配置fallback进行添加。

return {
    // 省略其他配置
    resolve: {
        fallback: {
            crypto: require.resolve('crypto-browserify'),
            // 我的项目在处理 crypto 时,还用到了 stream 和 vm
            stream: require.resolve('stream-browserify'),
            vm: require.resolve("vm-browserify"),
        },
        extensions: ['.web.js', '.js'],
    },
}

自己安装下用到的依赖,另外,如果使用了rn-nodeify对依赖进行了hack的话,在运行网页端时需要重新装依赖。

Buffer is not defined

Uncaught ReferenceError: Buffer is not defined

解决方案

使用webpack提供的ProvidePlugin插件配置一下,让项目使用Buffer时调用依赖buffer库的Buffer对象。

return {
    plugins: [
        new webpack.ProvidePlugin({
            Buffer: ['buffer', 'Buffer'],
            process: 'process/browser',
        }),
    ],
}

字体资源缺失

比如依赖库react-native-vector-icons会使用到ttf字体进行图标显示,如果缺失字体就会只显示一个方块。

解决方案

首先我们需要将字体文件通过copy-webpack-plugin插件拷贝到打包后到文件夹里让网页端可以请求到。

return {
    // 省略其他配置
    plugins: [
        new CopyPlugin({
            patterns: [
                // 添加用到的字体,或者直接将 Fonts 文件夹全拷贝过来
                { from: getResolvePath('node_modules/react-native-vector-icons/Fonts/Ionicons.ttf') },
                { from: getResolvePath('node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf') },
            ],
        }),
    ],
}

然后在项目引入的index.css文件添加@font-face:

// index..web.js
import 'index.css'
/* index.css */
@font-face {
    src: url(/Ionicons.ttf);
    font-family: Ionicons;
}

Alert 方法无效

到现在react-native-web 0.17.1版本,Alert方法也是没用实现的,是一个空壳,可见react-native-web#1026状态,所以你在使用Alert.alert这些方法时,实际是没用任何作用的。

解决方案

我们可以自己实现一个简单的Alert或者使用另外一个代替react-native-webAlert。因为使用alert方法会阻塞应用,不推荐使用,我们可以使用Modal组件进行显示。

首先修改webpack配置,通过alias别名让使用Alert时不使用react-native-web库导出的,而使用我们自定义的组件。

return {
    resolve: {
        alias: {
            // 省略其他
            'react-native-web/dist/exports/Alert': resolvePath('web/polyfills/Alert')
        },
    },
}

实现一个简单的Modal组件,可参考react-native-web-example案例。


版权声明:

Anand's Blog文章皆为站长Anand Zhang原创内容,转载请注明出处。

包括商业转载在内,注明下方要求的文章出处信息即可,无需联系站长授权。

请尊重他人劳动成果,用爱发电十分不易,谢谢!

请注明出处:

本文出自:Anand's Blog

本文永久链接:https://anandzhang.com/posts/frontend/21