前言
最近做了好几个小程序,感觉自己review之前代码太难受了,数据和目录结构都比较乱,所以在这篇文章里总结一些用到的方法,方便我自己的同时,也希望能与阅读这段文字的大家共勉。
一、淘宝小程序的数据流转
这可以参考一下淘宝的 官方文档。组件生命周期示意图如下:
生命周期函数具体信息见下表:
生命周期 | 参数 | 说明 |
---|---|---|
onInit | 无 | 组件创建时触发 |
deriveDataFromProps | nextProps | 组件创建时和更新前触发 |
didMount | 无 | 组件创建完毕时触发 |
didUpdate | (prevProps,prevData) | 组件更新完毕时触发 |
didUnmount | 无 | 组件删除时触发 |
淘宝/支付宝小程序的逻辑层的数据更新与vue的处理有些类似,元素渲染时会追踪数据依赖,只有依赖的数据更新时,该组件才会重渲染。这一点还是做得想当不错的,但是对于它的新 props
也只做了浅对比,意思是如果你仅仅简单的用setData更新一个json对象里的value,那别说组件,就当前页面都不会刷新。详细的更新方法我后续再写。
说回生命周期上,值得注意的是其中的 deriveDataFromProps方法。
deriveDataFromProps 在组件创建和更新时都会触发。在deriveDataFromProps 中可以:
访问this.is
、this.$id
、this.$page
等属性
访问this.data
、this.props
等属性
访问组件 methods 中的自定义属性
调用this.setData
、this.$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});
}
总结
方法毕竟有好几种,感谢你们能看到最后的这里。当然,如果可以的话我也想知道大家用的方法、我的不足之处或者我没描述清楚的地方。 我还是很想和大家一起交流的啦!(毕竟我也是在抱大佬大腿学技术…