Advertisement

Taro小程序兼容H5 API、组件兼容问题

阅读量:

总结在开发过程中的实际情况中所遇到的Taro小程序兼容H5的api、组件以及相关问题


文章目录

  • 一、H5Taro.getCurrentPages 返回空值。
    • 二、Taro.getCurrentPages() 提取并获取当前页面的路由参数信息。
    • 三、以 key 表示组件传递参数的入参属性名。
    • 四、在处理网络请求时出现异常情况:分别进行相应的错误处理。
    • 五、支持多终端自定义的 custom-tab-bar 组件。
    • 六、“ Swipe 动作组件 AtSwipeAction 不支持与h5框架兼容。
    • 七、“ 输入封装为文件时 ref 获取节点会导致 click 操作失效。
    • 八、“ 在H5开发环境中无法正常使用的 AtModal 组件样式。
    • 九、“ 使用 AtIcon 组件配置组件大小。
      总结:

一、H5Taro.getCurrentPages 获取为空

当正常添加页面栈时,在未进行手动刷新的情况下,“h5能够正确地取得”对应的页码列表是有效的。然而,在用户主动触发刷新操作时,“Taro.getCurrentPages”则会返回空数组。通过调用“Taro.nextTick”可以获得当前处于显示状态的网页列表信息;而用于查看历史页码的功能则无法实现

复制代码
      componentDidMount(){
        Taro.nextTick(() => {
           const pages = Taro.getCurrentPages()
        })
      }

二、Taro.getCurrentPages() 获取当前页面路由参数

当在 weapp 和 H5 中通过调用 Taro.getCurrentPages() 获取页面路由参数时会出现不一致的情况。具体来说,在不同端使用 Taro.getCurrentPages() 可以获取到一些特定属性。

path route $taroPath
weapp ×
H5 ×

获取路由参数

复制代码
    Taro.getCurrentPages()[-1]?.$taroPath?.split("?")[0]

三、用 key 作为组件传参的入参属性名

在开发过程中发现了这样一个情况:一些开发人员为了方便起见,在遍历操作中采用了某个组件,并将 key 参数传递给该组件作为 key 参数使用。然而,在小程序环境下这种做法不成问题(不成问题),但在 H5 环境中却出现了错误(错误)。具体原因在于 key 是一个关键字(关键),不能作为 props 参数(参数)接收(接收),因此在组件内部根本无法获取到 key 的实际值(实际值),其值被设为 undefined(无效)。建议大家可以在组件外部设置 key 作为标识符即可(即可),无需对内部进行任何处理(处理)。

复制代码
    {{
    	list.map(item=><my-item key={item.id} />)
    }}

四、Taro.request 网络请求异常处理中,weapp 和 h5 异常处理

我们在项目中规定了 接口的状态码 Status Code 401 作为 token 过期时使用的编码,并且 Taro.request 包裹的响应拦截也需根据修改情况进行相应调整,在不同的端点差异如下:

weapp h5
接口状态码 401 401
触发的回调函数 success fail
响应数据状态码字段 statusCode status

五、Taro 多端自定义 custom-tab-bar 组件

在Taro中,默认版本无法自定义custom-tab-bar功能,请参考官方文档详细了解相关设置。

  1. 将原有代码中的模块重新组织为独立文件:将原有的 custom-tab-bar.py$
  2. 修改模块内的默认行为:令该组件返回值为无。
  3. 在页面中引入新组件并作为组件进行管理:在该页面中引入新的模块文件命名为 components/customTabBar.py 并将其作为标准组件导入使用。
  4. 删除官方提供的自定义 tab-bar.py 文件。
复制代码
    .taro-tabbar__tabbar,
     	tar-bar {
     	display: none !important;
     }

hint:在 CustomTabBar 组件中, 可以通过 Taro.getCurrentInstance() 获取当前页面的路由信息, 并将其与 app.config.js 中定义好的 tabBar.list 对应起来分析, 从而判断并确定哪些 tab 页需要进行高亮显示。

一些机智的同学可能会思考:一旦改为自定义组件后将 custom-tab-bar 设为空组件,则无需配置 tabBar.list 就行了;为什么要手动隐藏默认的 custom-tab-bar 呢?

确实能够实现自定义 tab-bar 的作用。但是存在一个问题,在切换页面的过程中,上一次的页面无法保持其状态而无法保持上一次的状态,并且 tab-bar 页都需要重新加载并重新渲染内容。如果操作速度能够得到提升的话,在这种情况下就不会出现闪烁现象

六、AtSwipeAction 组件不兼容 h5

AtSwipeAction 组件借助 MovableArea 组件来实现相应功能。然而在 h5 环境下,MovableArea 组件并不被支持。如此一来,就只能手动编写一个具备滑动操作功能的组件了。相关代码如下:
index.jsx

复制代码
    import { useRef, useEffect, useState, useCallback, useMemo } from "react";
    import Taro from "@tarojs/taro";
    import { View } from "@tarojs/components";
    import "./index.less";
    
    /*** * 参数参考 taro ui SwipeAction
     */
    const SwipeAction = (props) => {
    const { onOpened = () => { }, onClosed = () => { }, onClick = () => { }, options = [], autoClose = false, disabled, isOpened } = props;
    const [translateX, setTranslateX] = useState(0); // 偏移的 X 距离
    const [startX, setStartX] = useState(0); // 鼠标移入的 X 坐标
    const [isSwiping, setIsSwiping] = useState(false); // 是否触摸滑动
    const actionWidthRef = useRef(0); // 操作节点的 width
    const alreadyXRef = useRef(0); // 已经滑动的距离
    
    useEffect(() => {
        Taro.nextTick(() => {
            Taro.createSelectorQuery()
                .select(".swipe-action__actions")
                .boundingClientRect((rect) => {
                    actionWidthRef.current = rect.width;
                })
                .exec();
        });
    }, []);
    
    useEffect(() => {
        reset(isOpened);
    }, [isOpened]);
    
    // 操作后重置关闭
    const reset = useCallback((is_opened) => {
        if (is_opened) {
            setTranslateX(-actionWidthRef.current);
            alreadyXRef.current = -actionWidthRef.current;
            _onOpened()
        } else {
            setTranslateX(0);
            alreadyXRef.current = 0;
            _onClosed()
        }
    }, [props]);
    
    function _onOpened() {
        Taro.nextTick(() => {
            onOpened();
        });
    }
    function _onClosed() {
        Taro.nextTick(() => {
            onClosed();
        });
    }
    
    // 滑动开始
    const handleTouchStart = useCallback((e) => {
        if (disabled) return false; // 禁止滑动
        setStartX(e.touches[0].clientX);
        setIsSwiping(true);
    }, [disabled]);
    
    // 滑动结束
    const handleTouchEnd = useCallback((e) => {
        setIsSwiping(false);
        if (translateX < -(actionWidthRef.current / 2)) {
            setTranslateX(-actionWidthRef.current);
            alreadyXRef.current = -actionWidthRef.current;
            _onOpened()
        } else {
            reset(false);
            _onClosed()
        }
    }, [translateX, actionWidthRef.current, reset]);
    
    // 滑动中
    const handleTouchMove = useCallback((e) => {
        if (!isSwiping) return;
        const cX = e.touches[0].clientX;
        const diffX = cX - startX + alreadyXRef.current;
        if (diffX <= 0.00001) {
            setTranslateX(diffX);
        }
    }, [isSwiping, startX, alreadyXRef.current]);
    
    const handleClick = useCallback((item, inx, e) => {
        // 添加 options 可配置 click
        const optionsClick =  typeof item.onClick == "function" ? item.onClick : ()=>{}
        onClick(item, inx, e);
        optionsClick(item, inx, e);
        if (autoClose) reset(false);
    }, [props, reset]);
    
    const actions = useMemo(() => {
        return options && options.length ? (
            <View className='swipe-action__actions'>
                {options.map(({ text, className, ...residue }, inx) => (
                    <View
                      {...residue}
                      key={`swipe-action__actions_item_${inx}`}
                      onClick={(e) => handleClick({ text, className, ...residue }, inx, e)}
                      className={`swipe-action__actions_btn ${className}`}
                    >
                        {text}
                    </View>
                ))}
            </View>
        ) : null;
    }, [options, handleClick]);
    
    return (
        <View className={`SwipeActionBx ${props.className}`}>
            <View
              className='swipe-action'
              onTouchStart={handleTouchStart}
              onTouchMove={handleTouchMove}
              onTouchEnd={handleTouchEnd}
              style={{ transform: `translateX(${translateX}px)` }}
            >
                <View className='swipe-action__content'>{props.children}</View>
            </View>
            {actions}
        </View>
    );
    };
    
    export default SwipeAction;

index.less

复制代码
    .SwipeActionBx {
      width: 100%;
      overflow: hidden;
      position: relative;
      display: flex;
      flex-direction: column;
    
      .swipe-action {
    display: flex;
    overflow: hidden;
    position: relative;
    z-index: 10;
    transition: transform 0.3s ease;
      }
    
      .swipe-action__content {
    flex: 1;
    width: 100%;
    background-color: #fff;
      }
    
      .swipe-action__actions {
    display: flex;
    align-items: center;
    color: #fff;
    position: absolute;
    right: 0;
    top: 0;
    z-index: 5;
    height: 100%;
    flex: 1;
    
    .swipe-action__actions_btn {
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
      padding: 16px;
      box-sizing: border-box;
    }
      }
    }

在开发过程中发现了一项关键点:taro ui swipeAction 组件中的所有属性都被系统自动生成,并且将其打包成为组件参数供其他部分调用。
具体而言,在微信开发者工具中遇到了一个问题:当为底部操作栏按钮设置颜色时,在父级元素上会出现按钮宽度超出预期的情况,并能观察到明显的边框效果;然而,在实际应用中发现这一现象并不明显存在。
值得注意的是,在实际应用中发现这一现象并不明显存在。

七、Input作为原生上传文件封装,ref 获取节点 click 无效

场景:默认情况下,在页面中点击其他元素节点时,默认情况下无法直接触发 Input 元素的 click 事件以实现上传功能。

问题分析:在 H5 应用场景中,默认情况下采用 Input 包裹组件的方式时,在某些情况下若需手动操作点击其他非 Input 元素节点并触发其 click 事件来调用上传功能,则可能会遇到无法正常触发的情况。

具体来说,在 CSS 问题兼容处理 所述,在 H5 场景下,默认情况下 Input 标签外层会额外包裹一层 taro-input-core 标签导致相关操作失效,请确保正确获取目标输入标签才能正常调用相关功能。

解决方案:请确保所有输入字段均正确地嵌套或绑定到对应的 HTML 输入标签上,并避免因嵌套或绑定错误导致的操作异常现象发生。

复制代码
    import { Component, createRef } from 'react';
    import Taro from '@tarojs/taro'
    export default class FileUpload extends Component {
    	this.fileInput = createRef()
    	onUploadFiles(){
    		 if (process.env.TARO_ENV === 'h5') {	//H5上传附件
    	      const inputNode = this.fileInput.current;
    	      inputNode.querySelector("input")?.click()
    	    } else {	//weapp 上传附件
    	      const options = {...}
    	      Taro.chooseMessageFile(options)
    	    }
    	}
    	
    	//上传文件
    	onInputChange(){
    		let { files = [] } = this.fileInput.current?.querySelector("input")	//获取上传的文件
    		/***
    		数据处理...
    		**/
    	    files = []	//将 input 附件清空
    	}
    	
    	render(){
    		const { filePorps } = this.props
    		return  <>
    			<Button onClick={()=>this.onUploadFiles()}>上传</Button>
    			<Input { ...filePorps } ref={this.fileInput} type='file' style={{ display: 'none' }} onChange={e => this.onInputChange(e)} > </Input>
    		</>
    	}
    }

八、AtModal 组件样式不兼容 H5

遵循H5规范的AtModal组件采用多个子组件实现功能。其中每个子组件均配备默认样式文件。

  1. 建议使用 Taro.showModal
  2. 自己手写一个 Modal
  3. 修改 AtModal 全局样式
复制代码
    .at-modal__action {
     taro-button-core {
       margin-top: 0;
       border-radius: 0;
       background-color: #fff;
       border: none;
       color: #333;
     }
    
     taro-button-core::after {
       border: none;
     }
    
     taro-button-core:nth-last-child(1) {
       color: #6190E8;
     }
    
     taro-button-core:nth-last-child(1)::after {
       z-index: 30;
       border-radius: 0;
       border-left-width: 1px;
       border-left-style: solid;
       border-left-color: rgb(229, 229, 229);
     }
    }
    
    .at-modal__footer::before {
     z-index: 30;
     border-top-width: 1px;
     border-top-style: solid;
     border-top-color: rgb(229, 229, 229);
    }

九、使用 AtIcon 组件 size 设置

在 AtIcon 组件的 props 属性中定义了一个名为 size 的参数用于指定图标大小,在向 H5 端迁移过程中 size 的计算会出现异常导致 font size 字段为空值 此时应将 font size 值手动设置在 className 属性中

tips : 同一个图标的 value 在微信的图标和H5的图标可能会存在差异。


总结

在实际开发中遇到了一些API与组件兼容的问题。项目进展较为复杂,并且还有很多问题尚未被完全记录下来。这篇文档主要用于记录这些问题,并希望你能从中获得帮助。实在抱歉有些问题未能及时解决,请argsyous再次详细分析一下吧。

全部评论 (0)

还没有任何评论哟~