本章会详细讲述在工作中踩到的一个关于vue-props的坑。在对级页面下,动态绑定props失效的问题以及解决办法。

起因

事情的起因是这样的:需求里要求从A页面中选择并获取到对象列表参数List[object],把List传给B页面中,在B页面中通过el-collapse将List展开,并且对每个List中的参数通过v-model进行赋值。如图所示:

最终效果图

自信满满地一顿骚操作后,发现在collapse-item中对于props值的操作并不生效

于是又哗啦啦地开启了一顿排查。


排查

对于其中v-model绑定props更改并不生效的问题,

同时出现了一些比较奇怪的现象。

  • props的值可以正常传递到collspse中。

  • 点击checkbox,边框会变红,但不会被勾中,之后“卡死”。

  • 在对collspse-item的展开收起中,其实会刷新item里面的想要修改的值,但只会生效一个

    即点击红棋子,然后收起再展开item -》红棋子生效。

    点击红棋子,再点击蓝棋子,然后收起再展开item -》只有蓝棋子生效。

    第二列中的input也是,输入123,然后收起展开item -》只有3生效。

根据这些现象,再继续排查,先排除掉了这些原因:

  • 将collspse组件去掉,发现仍然无法对list修改,排除collspse的问题
  • 通过checkbox的change方法,发现赋值是生效的,但是只生效最新的一个,覆盖掉之前生效的。并且视图不会更新。
  • 将List数据复制到B页面当成本地数据去测试,发现没有问题。

确定了是props的问题


文档

由于之前踩过坑,知道无法直接对props值进行更改或者显示,

所以上述数据中的List都是使用以下形式使用的。

1
2
3
4
5
computed: {
localList() {
return this.propsList
}
}

查阅相关文档,最开始从官方vue文档入手,发现一下子就找到了答案:

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

而解决办法也非常清楚:

  1. 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:

    1
    2
    3
    4
    5
    6
    props: ['initialCounter'],
    data: function () {
    return {
    counter: this.initialCounter
    }
    }
  2. 这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:

    1
    2
    3
    4
    5
    6
    props: ['size'],
    computed: {
    normalizedSize: function () {
    return this.size.trim().toLowerCase()
    }
    }

注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。

原因是我试用了computed去做数据更改,而不是data

所以解决办法是赋值给一个本地data即可。


其他

于是乎我很好奇,从最初的A页面开始,到底往下传递了多少层页面。

查看el-collapse源码,发现其大致结构如下:

A页面获取List -》 List到页面B -》到collapse -》(通过solt)到collapse-item

可以把组件理解成是一个子页面,组件内的solt又算是一个子页面。

而props数据流向只能向下,不能向上,当上层刷新的时候,下层数据也会刷新

所以List到达最底层的item,item里的list数据更新后想向上传递(到collapse)被阻止,但item的展开收起(视图刷新)会对item下的props做一次刷新。


补充(翻车)

在测试中,按文档的写法是没有问题的。但之后问题就出现了。

在本次项目中,B页面是嵌套入A页面中的(nuxt的nuxt-child)。于是乎在就会出现,B页面已经载入了,而B页面中data初始化获取List的时候,List并没有数据。

在B载入完成后,需要A选择/变动后再流入B中,但是B并没有再次初始化获取值。

所以最后还是绕回我原来的写法:

  1. 通过localStorage进行中间存储List
  2. 每一次A对List的改动后都存储进localStorage中
  3. 并且每次改动,都通过ref调用B页面中的refresh方法去刷新getLocalStorage
  4. 曲线救国

参考文档:

评论