[uni-app vite 版本] uni-transition 动画异常

pbossiut  于 2022-11-19  发布在  uni-app
关注(0)|答案(3)|浏览(450)

重现仓库: https://github.com/aimergenge/uni-vite-repro

重现步骤:

  1. 启动项目
  2. 首页点击 showPop 按钮
  3. 弹出来的东西里有一个 add 按钮,点击它,计数会加一
    这时候,popup 就莫名其妙的 close 了!!

vue2 版本的 uni-app 是没有这个问题的,我是在 vue2 升级 vue3 的过程中发现这个问题的。

这个问题根本的原因是,uni-transition 的组件状态跟实际上的 dom 状态是不一致的,这当然是由于它实现动画的方式决定的。
既然组件状态跟实际dom状态不一致,一旦uni-transition组件update就有可能导致dom样式重置为组件状态所指定的那样。

接下来我一步一步分析,为什么popup会close:

  1. 点下 add 按钮的时候,count++
  2. view.content 组件会重新渲染,为什么它会重新渲染?因为传给它的default slot中直接依赖了count这个响应式属性(不信你可以把 outer:{{count}} 套一个 view,popup 就不会 close 了)。
  3. 在 patch popup-local 的时候,由于 view 这个内置组件是手写的 render 函数,没有使用 createBlock,因此退出了 vue3 的优化渲染模式(就是源码中的 optimized),而在非优化模式下,shouldComponentUpdate 的判断逻辑是,如果传了插槽并且这个插槽没有 $stable 标志,就执行组件的 update 方法。那么这里的 popup-local 显然是有插槽的并且由于是模版编译出的渲染函数,不可能存在 $stable 标志,那么显而易见的,popup-local 是需要 update 的。
  4. 接下来就要看 popup-local 的模版了:
<view
  v-if="showPopup"
  class="uni-popup"
  :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']"
  @touchmove.stop.prevent="clear"
>
  <uni-transition
    v-if="maskShow"
    class="uni-mask--hook"
    :mode-class="['fade']"
    :styles="maskClass"
    :duration="duration"
    :show="showTrans"
    @click="onTap"
  />
  <uni-transition :mode-class="ani" :styles="transClass" :duration="duration" :show="showTrans">
    <view class="uni-popup__wrapper-box" @click.stop="clear">
      <slot />
    </view>
  </uni-transition>
</view>

首先,最顶层依然是一个 view 组件,它目前还是在优化模式下(因为是由编译器生成的),在优化模式下,shouldComponentUpdate 的逻辑是,如果有动态插槽的话就需要更新,动态插槽的情况有很多,就包括这里的将传入的插槽再往下层传递的情况。总之,这个 view 组件要 update。
5. view update 的话,就要判断 uni-transition 需不需要更新,这里还是同理,非优化模式下,有传入插槽,因此会更新。
6. uni-transition update 的话,那就要看 uni-transition 的模版:

<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>

可以看到,顶层一个 view 并且有动态插槽,那么也需要更新对吧。view 的模版:

var index$h = /* @__PURE__ */ defineBuiltInComponent({
  name: "View",
  props: extend({}, hoverProps),
  setup(props2, {
    slots
  }) {
    const {
      hovering,
      binding
    } = useHover(props2);
    return () => {
      const hoverClass = props2.hoverClass;
      if (hoverClass && hoverClass !== "none") {
        return createVNode("uni-view", mergeProps({
          "class": hovering.value ? hoverClass : ""
        }, binding), [slots.default && slots.default()], 16);
      }
      return createVNode("uni-view", null, [slots.default && slots.default()]);
    };
  }
});

那么接下来就会进入 patchElement 的逻辑了。
这时候 view 上的 style 属性虽然是一个 string, 但在 fallthrough 到 uni-view 上的时候,会被(具体发生在 cloneVnode 的代码中)normalize 成一个对象。uni-view 的 vnode 的 patchFlag 本来是 0,但由于有 extraProps,就被迫变成了 16,也就是 FULL_PROPS patch。FULL_PROPS patch 的过程中一定会看到两个 style 对象不是同一个,因此就触发了 diff,导致 uni-view 的样式被重置。

lymnna71

lymnna711#

导致样式重置的节点有很多,因此我也不清楚应该在哪个节点进行必要的改动,比如可以:

  • 把内置组件 view 的渲染函数改成使用 createBlock(但不知道会不会有别的影响)
  • 重新实现 uni-transition
sf6xfgos

sf6xfgos2#

@mehaotian have time to fix this? or it has been fixed so you can close this?

q8l4jmvw

q8l4jmvw3#

遇到了这个问题,非常奇特!

相关问题