淘宝小程序数据流转


前言

最近做了好几个小程序,感觉自己review之前代码太难受了,数据和目录结构都比较乱,所以在这篇文章里总结一些用到的方法,方便我自己的同时,也希望能与阅读这段文字的大家共勉。


一、淘宝小程序的数据流转

这可以参考一下淘宝的 官方文档。组件生命周期示意图如下:

组件生命周期

生命周期函数具体信息见下表:

生命周期参数说明
onInit组件创建时触发
deriveDataFromPropsnextProps组件创建时和更新前触发
didMount组件创建完毕时触发
didUpdate(prevProps,prevData)组件更新完毕时触发
didUnmount组件删除时触发

淘宝/支付宝小程序的逻辑层的数据更新与vue的处理有些类似,元素渲染时会追踪数据依赖,只有依赖的数据更新时,该组件才会重渲染。这一点还是做得想当不错的,但是对于它的新 props 也只做了浅对比,意思是如果你仅仅简单的用setData更新一个json对象里的value,那别说组件,就当前页面都不会刷新。详细的更新方法我后续再写。

说回生命周期上,值得注意的是其中的 deriveDataFromProps方法。

deriveDataFromProps 在组件创建和更新时都会触发。在deriveDataFromProps 中可以:
访问 this.isthis.$idthis.$page 等属性
访问 this.datathis.props 等属性
访问组件 methods 中的自定义属性
调用 this.setDatathis.$spliceData 修改数据
可以使用 nextProps 参数获取将要更新的 props 参数

deriveDataFromProps中的nextProps是最新的props,可以将其传递给初始化或者更新函数处理数据并setData/setState更新页面,而不用在 didUpdate 时setData/setState(这样页面至少会触发两次didUpdate),在一些需要快速处理并刷新页面的地方十分好用(例如画板元素拖动、倒计时等),往往可以节省不少性能,当然也需要在 deriveDataFromProps 做好是否需要更新数据的判断。
它类似于 react 中的 getDerivedStateFromProps,对于它的介绍可以参阅 这篇文章

举个栗子!

  // 页面初始化函数
  onInit () {
      this._refreshData();
  },
  
  deriveDataFromProps (nextProps) {
      const {updateTime: lastTime} = this.props;
      const {updateTime: nowTime} = nextProps;
      // 判断是否需要更新 对于一些不容易触发又时常需要更新的组件,我比较喜欢给它们传当前时间戳
      if (lastTime !== nowTime) {
          this._refreshData(nextProps);
      }
  },

  // 更新数据函数
  _refreshData (props = this.props) {
      const {id} = props;
      this.setData({id});
  }

二、更新方法

方法很多,总共分作两大类吧。一个是顺着数据流(最好如此)——由父及子,一个是逆着数据流——由子及父。

1.由父及子

(1)官方常见式

具体可以参考 官方文档,例如:

    // /pages/index/index.js 页面 父级
    Page({
        data: {
            id: 0,
            otherProps: {
                // ...arga
            }
        }
        plus() {
            this.counter.plus();
        },
        // saveRef 方法的参数 ref 为自定义组件实例,运行时由框架传递给 saveRef
        saveRef(ref) {
            // 存储自定义组件实例,方便以后调用
            this.counter = ref;
        },
        // 一些更新数据的函数
        refreshData () {
            this.setData({
                id: 2
            })
        }
    });
<!-- /pages/index/index.axml -->
<my-component ref="saveRef" id="{{id}}" other-props="{{otherProps}}"/>
<button onTap="plus">+</button>

    // /components/myComponent/myComponent.js 组件 子级
    didUpdate() {
        console.log(this.props) // 可以看到传入的参数 此处大致是 {'id': 2, otherProps: {}}
    },
    deriveDataFromProps (nextProps) {
      const {id: lastId} = this.props;
      const {id: newId} = nextProps;
      console.log({lastId, newId}) // 此处大致是 {'lastId': 0, 'newId': 2}
      // 一些其他处理
  },

2.由子及父(反了老子)

(1)this.$page.setData

this.setData类似。受大佬点拨:

既然this.$page可以拿到page下绑定的函数,那是不是也可以拿到page.data,那是不是可以调用this.$page.setData

实践证明,确实可以!不需要绑定,页面下的子组件也可以这样使用:

    // ...
    const {counter} = this.$page.data;
    this.$page.setData({
        [`userInfo.${key}`]: value, // 可以更新指定object key下的value
        counter: counter + 1
    });

当然,复杂情况下,这可能会使得数据混乱(毕竟组件们都可以拿到this.$page,都可以更新值)。建议梳理一下数据流向再决定是否使用。

3.全局绑定

这个方法比较通用,无论是父组件更新孙组件还是子组件更新父组件都可以使用。
原理和上文的this.$page.data类似:通过绑定在page上,使得页面内的所有组件都可以调用当前page的函数。

那首先就需要在page上提供一个绑定的变量。值得注意的是如果未在此处声明,则绑定的时候会报undefined。

// page/index/index.js
Page({
    componentHandle: {
        testFunction: null,
         // 如果想在这个函数下其他key绑定函数,则可以这样定义
        keysFunction: {}
    },
    onLoad () {
        // 绑定page下的函数, 这样组件就都可以调用了
        this.componentHandle.testFunction = this.testFunction.bind(this);
    },
    testFunction (newData = {}) {
        this.setData({
            data: newData
        })
    }
})

然后在对应的地方绑定函数

// components/something/something.js page下的子组件
Component({
     // 或者didMount
    onInit() {
     // 绑定组件下的refreshTestComponent,当然,当存在多个同样的组件时要注意用不同key区分
      this.$page.componentHandle.keysFunction[`refreshTestHandle`] = this.refreshTestComponent.bind(this);
    },
    didUnmount() {
        // 组件被卸载的时候别忘了清除它
        const {keysFunction} = this.$page.componentHandle;
        if (keysFunction && keysFunction.refreshTestHandle) {
            delete this.$page.componentHandle.keysFunction.refreshTestHandle;
        }
    },
    methods: {
        refreshTestComponent(newProps = {}) {
            const {data: newData} = newProps;
            this.setData({
                data: newData
            })
        },
    }
})

最后就可以在任意地方调用了。

    // 有点长...
    this.$page.componentHandle.keysFunction.refreshTestHandle({...newData});

当然,保险一些可以这样调用:

    const {keysFunction} = this.$page.componentHandle;
    if (keysFunction && keysFunction.refreshTestHandle && typeof refreshTestHandle === 'function') {
        this.$page.componentHandle.keysFunction.refreshTestHandle({...newData});
    }

总结

方法毕竟有好几种,感谢你们能看到最后的这里。当然,如果可以的话我也想知道大家用的方法、我的不足之处或者我没描述清楚的地方。 我还是很想和大家一起交流的啦!(毕竟我也是在抱大佬大腿学技术…

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值