wechat-mini-program-experience

经历了一次小程序开发,遇到了一些开发时遇到的坑,首先说明一下技术框架是使用的京东的 taro 进行的开发,并且使用的 hooks 的开发方式

Taro 的坑

首先来看看使用 taro 框架会有哪些坑是需要避免的呢

  1. taro hooks缺少

    我们在使用的时候,发现 taro 的生命周期当中有一个生命周期没有对应的 hook,这个生命周期就是 componentWillUnmount,并且源码中也没有能够去增加新关于生命周期的 hook 的功能,所以如果要用到这个生命周期的话,请使用 class 的写法;

  2. 自定义组件在 taro 的组件上不能直接使用外部传入的 classname

    需要使用到 custom-class 或者 css module 解决这个问题

  3. 自定义组件的 props 不能传入复杂的渲染函数

    // 比如一个带有头像的电话列表
    
    const tels = [
      {
        FIRST_CHAR: 'A'
        list: [{
          avatar: 'imgsrc',
          tel: '12345678900'
        },{
          avatar: 'imgsrc',
          tel: '12345678900'
        },{
          avatar: 'imgsrc',
          tel: '12345678900'
        },{
          avatar: 'imgsrc',
          tel: '12345678900'
        }]
      }
    ];
    
    // 这样的一个数据结构,因为想做一个通用性更大的组件,所以在设计的时候想要把第二层渲染list的渲染函数写成一个从外部传入的函数
    
    const render = () => {
      return (<Indexes
        renderItem={
          item => (<View><Image src={item.avatar} /><Text>{item.tel}</Text></View>)
        }
      />);
    }
    
    // 但是并不能这样子写,taro不会进行渲染,所以只能够将列表的渲染方法写在组件内部
    list.map((listItem) => (
      <View
        key={`index-${listItem.key}`}
        className="index-list-item"
        id={`index-${listItem.key}`}
      >
        <View className="item-title">{listItem.title}</View>
        // {renderItem()} 如果是使用上面那种方式 直接在这里写成这样就可以了
        {listItem.items.map((i) => {
          return (
            <View
              key={i.name}
              className="item-container"
              hoverClass="item-container-hover"
              onClick={() => onClick(i)}
            >
              {needIcon && ( // 因为不确定 icon 的确切的值,所以就交给 CustomImage 组件内部处理,外面只需要管需不需要icon的属性
                <CustomImage
                  src={i.icon}
                  mode="aspectFit"
                  custom-class="icon"
                  defaultImage={needIcon && defaultIcon}
                />
              )}
              <Text>{i.name}</Text>
            </View>
          );
        })}
      </View>
    
      // 所以复杂的渲染还是需要写在组件内部
    
      // 另外如果需要传入 tsx 作为 props 的话,需要使用一个 View包裹起来
    
      <Component tsx={<View>something</View>} />

在开发中需要保持的一些思维

  1. 在使用 useEffect 的时候需要有函数的思维

    使用一个 useEffect 时会在后面加上其依赖,当依赖变化,就会触发一次 useEffect 的回调,所以在使用 setState 改变依赖的时候,需要有一个函数式的思维,改变变量进而产生不一样的结果

      // 比如翻页请求下一页时,需要的是改变页码就能够触发函数,而不是不断的调用函数
      const [page, setPage] = useState(1);
    
      useEffect(() => {
        // ... 获取相应页码的数据
      }, [page]);
    
      const changePage = () => setPage(prePage => prePage + 1)
  2. 当从一个页面进入到另一个页面做了某些操作之后,回到当前页面需要更新当前页的一些数据

    这是一个很常见的场景,比如我在个人信息页面,我进入修改页面修改了某些数据,当我返回时期望的是看到我已经修改的数据展示在页面上,这种场景在实用 hook 并且没有实用 redux 技术下,只能够实用 useDidShow 这个 hook 去进行页面数据的刷新。

      useDidShow(() => {
        // ... 再次获取数据
      });
  3. 多次不间断触发查询条件,应该以最后一次结果为准展示

    这个场景是在一个能够使用筛选的列表中遇到的,当我不断切换筛选条件并且在网络环境较差的时候,页面中的渲染会有多次渲染,渲染次数和切换的次数一样,原因是因为没有取最后一次结果作为渲染的依据。所以在有类似情况出现时,应该使用唯一标识去拿到最后一次请求的结果再进行页面的渲染,具体实施方法有很多,我使用的是使用一个数字代表请求次数,每次请求次数加一,请求完成次数减一,直到次数归零,才进行页面渲染。

      useEffect(() => {
        setLoading(true);
        queryQueen++;
        searchStationByKeyword().then((res: StationCollection) => {
          queryQueen--;
          if (res.chcList && queryQueen === 0) {
            setLoading(false);
          }
        });
      }, []);
  4. 对于 Image 组件需要有错误处理

    很多同学在使用图片的时候都没有对于图片进行错误处理,导致只要遇到图片链接不正确或者没有链接的时候,页面中的展示就会出现问题,所以不论在什么情况下开发,对于图片也要有错误处理。

      interface IProps extends StandardProps, ImageProps {
        defaultImage?: string;
        lazyLoad?: boolean;
      }
    
      const CustomImage: Taro.FC<IProps> = (props) => {
        const { src, mode, defaultImage, lazyLoad = true } = props;
        const [rightSrc, setRightSrc] = useState(defaultImage || imagePlaceholder);
    
        useEffect(() => {
          if (src) {
            setRightSrc(src);
          }
        }, [src]);
    
        return (
          <Image
            lazyLoad={lazyLoad}
            src={rightSrc}
            mode={mode}
            className="custom-class"
            onError={() => setRightSrc(imagePlaceholder)}
          />
        );
      };

小程序中的一些注意事项

  1. scroll-view 中的定位问题

    在 scroll-view 中不能使用 absolute 以及 fixed 布局,使用之后不会生效,解决方法就是不要再scroll-view 中使用这两种布局,可以放在与 scroll-view 同层的位置去使用这两种布局。

    scroll-view 能够很好的帮助我们构造一个 infinity-scroll 组件,可以很方便的实现上滑加载更多,下拉刷新的功能,并且能够使用微信 dom 节点上的 ID 进行类似于 scrollIntoView() 的滚动效果。

  2. canvas 的使用注意事项

    a. 首先第一点就是 canvas 的 type="2d" 的模式不会进行绘制,原因尚未知晓,解决方法就是不设定 type,而是使用 canvas.getContext('2d')

    b. canvas 在绘制时如果要设置一些样式,需要配合 context.save() context.restore(),因为 canvas 的样式是依据最近的一次,如果想要各个设置不互相干扰,就需要在设置前后使用上面两个 API 进行保存,实在是因为样式混杂不好处理,那么就在每次 draw 的时候进行整块画布清空 context.clearRect()

    c. canvas 的所有计算完毕之后,一定要使用 context.draw() 进行绘制,否则画布上不会出现任何图像。

    d. 小程序的 canvas 的宽高就是其 style 的宽高,与 HTML 的不一样。

    e. 在调试 canvas 的时候需要在真机上面进行调试,因为小程序 canvas 使用的是原生组件,模拟器上的样子不一定是真机上的样子,并且如果有 canvas 尽量不要放在浮层中,可能会因为 canvas 是原生组件而层级并不会在浮层上展示,同样因为是原生组件,所以也不能在 scroll-view 中使用 canvas,canvas 不会随着 scroll-view 滑动而滑动。

    f. 如果需要在小程序中进行 canvas 的动画,需要调用 canvas.requestAnimationFrame(),如果不生效,可以使用 setInterval(),但是小程序的动画在真机上可能会很卡,原因尚未知晓,尽量不要设计过于复杂的动效在小程序上。

埋点小技巧

  1. PV 埋点

    将埋点放在生命周期中最为合适,我们使用的是 hook 所以我们可以提取成为一个简单的自定义 hook,然后在每个页面调用一下即可,但是由于 taro 的 hook 中没有对应 componentWillUnmount,所以我们直接复写 page 的 onUnload 方法。

  2. fromWhere 埋点

    需要记录页面之间的跳转关系,可以利用路由,将路由与页面名字对应,然后能在当前页面拿到上次页面的路由信息,就能够知道上个页面的名字,进而进行记录,在设定记录的名称的时候也考虑到是否记录来源就足够了,因为有的组件在多个地方使用,就不需要再在每个使用的地方进行点击事件的记录,直接在跳转到的页面记录是从哪个地方来的,就能够达到相同的效果。

Last updated