[React组件封装][优化]文件选择按钮(解决安卓下type为file的input标签不触发onChange的问题)

公众号:程序员波波

之前已经写过“[React组件封装][实例]文件选择按钮组件封装”这篇文章。目的是封装一个文件选择按钮。

但是在真正运用的时候发现,还是存在问题的。主要是安卓端的微信浏览器input标签不触发onChange函数。

在经过一些查阅后,发现可能原因是安卓端的WebView不支持input标签为file,不会触发onChange,而微信安卓端使用了WebView,所以会有这样的问题。

该如何解决呢?

好在微信提供了专门的图片选择接口,这样至少保证FileButton在安卓微信端能够实现图片的选择。

使用微信的jssdk可以在微信浏览器内实现图片选择功能。

使用接口前需要先加载jweixin-1.4.0.js这个文件,然后还需要到服务器端去签名。(服务端具体的签名规则在微信的官方文档中可以查阅到)

然后我这里封装了一个WeixinTools,来完成上面的步骤。

WeixinTools.js:

import Tools from "../Tools";
import Strings from "../StringUtil";
import { UserOperation } from "../UserUtil";
import Http from "../Http";

class WeixinTools {
    static configJsSdk() {
        let arg = {
            cmd: UserOperation.GET_JS_SDK_CONFIG,
            data: {
                url: window.location.href,
            }
        }
        Http.post(Tools.getUrl('/user-operation/'), arg, (data)=>{
            console.log(data)
            if (data.result_id == 0) {
                let ans = data.message
                let js_sdk_config = ans.js_sdk_config
                WeixinTools.jssdk.config({
                    debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
                    appId: Strings.APP_ID, // 必填,公众号的唯一标识
                    timestamp: js_sdk_config.timestamp, // 必填,生成签名的时间戳
                    nonceStr: js_sdk_config.noncestr, // 必填,生成签名的随机串
                    signature: js_sdk_config.signature,// 必填,签名
                    jsApiList: [
                        'chooseImage',
                        "previewImage",
                        "uploadImage",
                        "downloadImage",
                        "getLocalImgData"
                    ] // 必填,需要使用的JS接口列表
                })
                WeixinTools.jssdk.ready(function(){
                    console.log('wx js sdk ready')
                    // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
                });
                WeixinTools.jssdk.error(function(res){
                    console.log('wx js sdk error')
                    console.log(res)
                    // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
                });
            }
        })
    }
}

var _jssdkDependScripts = [
    "/static/Weixin/jweixin-1.4.0.js",
]

if (Tools.isWeiXin()) {
    Tools.asyncLoadScripts(_jssdkDependScripts, ()=>{
        WeixinTools.jssdk = jWeixin
    })
}

export default WeixinTools

调用 WeixinTools.configJsSdk()这个方法,就可以实现jssdk的初始化。

实际使用:

然后将之前FileButton组件中的代码进行改造,对应安卓微信端使用chooseImage这个接口来实现选择图片。

但是微信选择完图片后回调接口只会返回一个localIds,这个并不是实际的文件,而是一个文件的标识符一样的东西。

通过getLocalImgData这个接口可以使用localId来获取图片的base64数据,不过在安卓端需要手动加上'data:image/jpeg;base64,'前缀。然后通过Tools.jpegBase64ToBlob将base64转化成blob类型,就能统一成FileButton原来的接口了。

Tools.jpegBase64ToBlob方法:

static jpegBase64ToBlob(urlData) {
        try {
            var arr = urlData.split(',')
            var mime = arr[0].match(/:(.*?);/)[1] || 'image/jpeg';
            // 去掉url的头,并转化为byte
            var bytes = window.atob(arr[1]);
            // 处理异常,将ascii码小于0的转换为大于0
            var ab = new ArrayBuffer(bytes.length);
            // 生成视图(直接针对内存):8位无符号整数,长度1个字节
            var ia = new Uint8Array(ab);
            
            for (var i = 0; i < bytes.length; i++) {
                ia[i] = bytes.charCodeAt(i);
            }

            return new Blob([ab], {
                type: mime,
            });
        }
        catch (e) {
            var ab = new ArrayBuffer(0);
            return new Blob([ab], {
                type: 'image/jpeg',
            });
        }
    }

最终版FileButton:

import React, { Component } from 'react';
import PropTypes from 'prop-types'
import CSSModules from 'react-css-modules';

import Button from '@material-ui/core/Button';
import Tools from '../../../common_js/Tools'

import styles from './FileButton.css'
import WeixinTools from '../../../common_js/weixin/WeixinTools';

class FileButton extends Component {
    constructor(props) {
        super(props);
        this.button = React.createRef()
        this.getFiles = this.getFiles.bind(this)
        this.onChange = this.onChange.bind(this)
        this.onClick = this.onClick.bind(this)
    }

    click() {
        this.button.current.click()
    }

    onClick() {
        const { onChange } = this.props
        WeixinTools.jssdk.chooseImage({
            count: 1, // 默认9
            sizeType: ['original'], // 可以指定是原图还是压缩图,默认二者都有
            sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
            success: function (res) {
                let localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
                WeixinTools.jssdk.getLocalImgData({
                    localId: localIds[0],
                    success: function (res) {
                        let localData = 'data:image/jpeg;base64,' + res.localData
                        // let localData = 'data:image/jpeg;base64,' + res.localData.replace(/\r|\n/g, '');
                        if (onChange) {
                            onChange([Tools.jpegBase64ToBlob(localData)])
                        }
                    }
                });
            }
        });
    }

    getFiles() {
        let ans = []
        let files = this.refs.fileLoader.files
        for (let i = 0; i < files.length; i++) {
            ans.push(files[i])
        }
        return ans
    }

    onChange(e) {
        if (!Tools.isNone(this.props.onChange)) {
            this.props.onChange(this.getFiles())
        }
        this.refs.fileLoader.value = ''
    }

    componentDidMount() {
        if (Tools.isWeiXin() && Tools.isAndroid()) {
            WeixinTools.configJsSdk()
        }
    }

    render() {
        let { accept, onChange, children, multiple, ...other } = this.props
        if (Tools.isNone(accept)) {
            accept = 'image/*'
            if (Tools.isChrome()) {
                accept = 'image/jpeg,image/gif,image/png,image/bmp'
            }
        }

        if (Tools.isWeiXin() && Tools.isAndroid()) {
            return (
                <Button {...other} onClick={this.onClick} >
                    {this.props.children}
                </Button>
            );
        }

        return (
            <Button component='label' {...other} ref={this.button}>
                {
                    this.props.multiple == true ?
                        <input styleName='input-area' ref='fileLoader' onChange={this.onChange} type="file" accept={accept} multiple />
                    :
                        <input styleName='input-area' ref='fileLoader' onChange={this.onChange} type="file" accept={accept} />
                }
                {this.props.children}
            </Button>
        );
    }
}

FileButton.propTypes = {
    onChange: PropTypes.func,
    accept: PropTypes.string,
}

export default CSSModules(FileButton, styles);

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
React中,我们可以使用去抖动(debounce)函数来处理组件的`onchange`事件,以呈现输入框(`input`)、文本区(`textarea`)或其他元素。 去抖动函数在代码中引入可以通过第三方库(如Lodash)或自定义实现。这个函数的作用是延迟执行一个函数,只有在指定的时间内没有再次触发时才真正执行。这在处理输入框等需要实时响应但又不希望过于频繁触发的场景中很有用。 首先,我们需要在组件的状态(`state`)中设置一个变量来保存输入框的值,例如`value`。 然后,我们需要编写一个`handleChange`函数来处理输入框的`onchange`事件。在这个函数中,我们要使用去抖动函数来延迟执行一个更新状态(`setState`)的操作。具体实现可以参考以下示例代码: ```javascript import React, { Component } from 'react'; import _ from 'lodash'; // 引入去抖动函数库 class MyComponent extends Component { constructor(props) { super(props); this.state = { value: '', }; // 定义去抖动后的函数 this.debouncedHandleChange = _.debounce(this.handleChange, 300); } handleChange = () => { // 更新输入框的值 this.setState({ value: event.target.value }); }; render() { return ( <div> <input type="text" value={this.state.value} onChange={this.debouncedHandleChange} /> </div> ); } } ``` 在上面的示例中,我们在`constructor`中初始化了一个值为空的`value`状态。然后,我们定义了`debouncedHandleChange`函数,它将`handleChange`函数作为参数传给去抖动函数库的`debounce`方法,并设置了一个300毫秒的延迟时间。 最后,在渲染方法中,我们通过`onChange`事件将`debouncedHandleChange`函数绑定到输入框上,实现了去抖动的效果。 这样,当用户在输入框中输入内容时,`handleChange`函数不会立即执行,而是延迟300毫秒。只有在这段时间内,没有再次输入内容才会真正执行`handleChange`函数,并更新`value`状态。 通过类似的方式,我们也可以应用去抖动函数于其他元素上的`onchange`事件,实现类似的效果。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值