From 9119e008764bb0ecad3034148e62af3b2e95bd43 Mon Sep 17 00:00:00 2001 From: Devarshi Raval Date: Wed, 31 Oct 2018 23:08:47 +0530 Subject: [PATCH 1/2] Update readme.md English headings for readme --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 7c1c949..ca090b6 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@ -# Westore - 微信小程序解决方案 -> [1KB javascript](https://github.com/dntzhang/westore/blob/master/packages/westore/utils/create.js)覆盖状态管理、跨页通讯、插件开发和云数据库开发 +# Westore - WeChat applet solution +> [1KB javascript](https://github.com/dntzhang/westore/blob/master/packages/westore/utils/create.js) Overrides state management, cross-page communication, plugin development, and cloud database development --- - [Omi 4.0![](https://raw.githubusercontent.com/dntzhang/cax/master/asset/hot.png)](https://github.com/Tencent/omi) - 同样是 Web Components,同样是 Path Updating From d9f3fb8204cc09c2d15b9d46282636faea393307 Mon Sep 17 00:00:00 2001 From: Devarshi Raval Date: Wed, 31 Oct 2018 23:24:38 +0530 Subject: [PATCH 2/2] Update readme.md Complete English --- readme.md | 796 ++++++++---------------------------------------------- 1 file changed, 120 insertions(+), 676 deletions(-) diff --git a/readme.md b/readme.md index ca090b6..64d5b4d 100644 --- a/readme.md +++ b/readme.md @@ -1,732 +1,176 @@ - # Westore - WeChat applet solution > [1KB javascript](https://github.com/dntzhang/westore/blob/master/packages/westore/utils/create.js) Overrides state management, cross-page communication, plugin development, and cloud database development --- -- [Omi 4.0![](https://raw.githubusercontent.com/dntzhang/cax/master/asset/hot.png)](https://github.com/Tencent/omi) - 同样是 Web Components,同样是 Path Updating -- [Packages 简介](#packages-简介) -- [前言](#前言) -- [云开发![](https://raw.githubusercontent.com/dntzhang/cax/master/asset/hot.png)](./westore-cloud.md) -- [插件开发](./westore-plugin.md) -- [普通开发](#普通开发) - - [定义全局 store](#定义全局-store) - - [创建页面](#创建页面) - - [绑定数据](#绑定数据) - - [更新页面](#更新页面) - - [创建组件](#创建组件) - - [更新组件](#更新组件) - - [setData 和 update 对比](#setdata-和-update-对比) - - [跨页面同步数据](#跨页面同步数据) - - [纯组件](#纯组件) - - [调试](#调试) - - [超大型小程序最佳实践](#超大型小程序最佳实践两种方案) +- [Omi 4.0![](https://raw.githubusercontent.com/dntzhang/cax/master/asset/hot.png)](https://github.com/Tencent/omi) - Same as Web Components, The same is Path Updating +- [Introduction to Packages] (#packages-introduction) +- [Preface] (#Foreword) +- [Cloud Development![](https://raw.githubusercontent.com/dntzhang/cax/master/asset/hot.png)](./westore-cloud.md) +- [plugin development] (./westore-plugin.md) +- [Ordinary development] (#general development) +- [Define global store] (#define global-store) +  - [Create Page] (#Create Page) +  - [Bind Data] (#Binding Data) +  - [Update Page] (#Update Page) +  - [Create component] (#Create component) +  - [Update Component] (#Update Component) +  - [setData and update comparison] (#setdata- and -update-comparison) +  - [Cross-page sync data] (#cross-page sync data) +  - [Pure component] (#pure component) +  - [Debug] (#Debug) +  - [Ultra Large Program Best Practices] (#Super Large Program Best Practices) - [API](#api) -- [注意事项](#注意事项) -- [原理](#原理) - - [JSON Diff](#json-diff) - - [Update](#update) - - [函数属性](#函数属性) +- [Precautions] (#Notes) +- [Principle] (#principle) +  - [JSON Diff](#json-diff) +  - [Update](#update) +  - [Function Attributes] (#Function Attributes) - [License](#license) -## Packages 简介 +## Packages Introduction -| Package | 介绍 | -| ------ | ------ | -| westore | 小程序演示项目 | -| westore-cloud | 小程序 + 腾讯云演示项目(隐形云☁️) | -| westore-plugin | 小程序插件开发演示项目 | -| westore-proxy | 小程序底层使用 Proxy 演示项目 | -| westore-test | 测试 westore API 的小程序 | -| westore-web | 小程序开发 Web 的项目(规划中..只提交了 WXML 编译器)) | +| Package | Introduction | +| ------ | ------ | +| westore | Applet Demo Project | +| westore-cloud | Applet + Tencent Cloud Demo Project (Stealth Cloud) | +| westore-plugin | Applet Plugin Development Demo Project | +| westore-proxy | The bottom of the applet uses the Proxy demo project | +| westore-test | Test the weaver API applet | +| westore-web | Apple Development Web Project (Planning: only submitted WXML compiler)) | -后面考虑通过 westore-web 编译直接把小程序编译成 [omi](https://github.com/Tencent/omi) 项目。 +Later consider compiling the applet directly into the [omi](https://github.com/Tencent/omi) project via westore-web compilation. -## 前言 +## Foreword -众所周知,小程序通过页面或组件各自的 setData 再加上各种父子、祖孙、姐弟、姑姑与堂兄等等组件间的通讯会把程序搞成一团浆糊,如果再加上跨页面之间的组件通讯,会让程序非常难维护和调试。虽然市面上出现了许多技术栈编译转小程序的技术,但是我觉没有戳中小程序的痛点。小程序不管从组件化、开发、调试、发布、灰度、回滚、上报、统计、监控和最近的云能力都非常完善,小程序的工程化简直就是前端的典范。而开发者工具也在持续更新,可以想象的未来,组件布局的话未必需要写代码了。而且据统计,开发小程序使用最多的技术栈是使用小程序本身的开发工具和语法,所以最大的痛点只剩下状态管理和跨页通讯。Westore 的方案: +As we all know, the small program through the page or component's respective setData plus the communication between the various father and son, grandparents, siblings, aunts and cousins ​​and other components will make the program a mess, if you add cross-page Communication between components can make the program very difficult to maintain and debug. Although there are many techniques for compiling and transferring small programs on the market, I feel that there is no pain point in punctuating small programs. Small programs are perfect for componentization, development, debugging, publishing, grayscale, rollback, reporting, statistics, monitoring, and recent cloud capabilities. The engineering of small programs is a model for the front end. The developer tools are also constantly updated, imaginable future, component layout does not necessarily need to write code. Moreover, according to statistics, the most used technology stack for developing small programs is the development tools and syntax of the applet itself, so the biggest pain point is only state management and cross-page communication. Westore's plan: ![data-flow](./asset/data-flow2.jpg) -非纯组件的话,可以直接省去 triggerEvent 的过程,直接修改 store.data 并且 update,形成缩减版单向数据流。 +For non-pure components, you can directly eliminate the triggerEvent process, directly modify store.data and update to form a reduced version of the unidirectional data stream. -受 [Omi 框架](https://github.com/Tencent/omi) 的启发,且专门为小程序开发的 [JSON Diff 库](https://github.com/dntzhang/westore/blob/master/packages/westore/utils/diff.js),所以有了 westore 全局状态管理和跨页通讯框架让一切尽在掌握中,且受高性能 JSON Diff 库的利好,长列表滚动加载显示变得轻松可驾驭。总结下来有如下特性和优势: +[JSON Diff Library] inspired by [Omi Framework] (https://github.com/Tencent/omi) and developed specifically for applets (https://github.com/dntzhang/westore/blob/master/ Packages/westore/utils/diff.js), so with the westore global state management and cross-page communication framework to keep everything under control, and the high-performance JSON Diff library is good, long list scrolling display becomes easy to control . Summarized the following features and advantages: -* 和 Omi 同样简洁的 Store API -* 超小的代码尺寸(包括 json diff 共100多行) -* 尊重且顺从小程序的设计(其他转译库相当于反其道行) -* 增强 data 数据绑定,函数属性可直接绑定到 WXML -* this.update 兼容 setData 同样的语法 -* this.update 比原生 setData 的性能更优,更加智能 -* Westore 专为小程序插件开发[定制了模板](https://github.com/dntzhang/westore/tree/master/packages/westore-plugin) -* Westore 集成了腾讯云开发 +* The same simple Store API as Omi +* Ultra-small code size (including json diff more than 100 lines) +* Respect and obey the design of small programs (other translation libraries are equivalent to the opposite) +* Enhanced data data binding, function properties can be directly bound to WXML +* this.update is compatible with setData with the same syntax +* this.update is better and more intelligent than native setData +* Westore developed [applied template] for applet plugins (https://github.com/dntzhang/westore/tree/master/packages/westore-plugin) +* Westore integrates Tencent cloud development -总结下小程序的痛点: +Summarize the pain points of the small program: -* 使用 this.data 可以获取内部数据和属性值,但不要直接修改它们,应使用 setData 修改 -* setData 编程体验不好,很多场景直接赋值更加直观方便 -* setData 卡卡卡慢慢慢,JsCore 和 Webview 数据对象来回传浪费计算资源和内存资源 -* 组件间通讯或跨页通讯会把程序搞得乱七八糟,变得极难维护和扩展 +* Use this.data to get internal data and property values, but don't modify them directly, you should use setData to modify +* setData programming experience is not good, many scenes directly assigned value is more intuitive and convenient +* setData kaka is slow and slow, JsCore and Webview data objects are passed back and forth to waste computing resources and memory resources +* Inter-component communication or cross-page communication can make the program messy and become extremely difficult to maintain and expand. -所以没使用 westore 的时候经常可以看到这样的代码: +So you can often see such code when you don't use westore: ![not-westore](./asset/not-westore.png) -使用完 westore 之后: +After using westore: ![westore](./asset/westore2.png) -上面两种方式也可以混合使用。 +The above two methods can also be mixed. -可以看到,westore 不仅支持直接赋值,而且 this.update 兼容了 this.setData 的语法,但性能大大优于 this.setData,再举个例子: +As you can see, westore not only supports direct assignment, but this.update is compatible with the syntax of this.setData , but the performance is much better than this.setData, another example: ``` js -this.store.data.motto = 'Hello Westore' -this.store.data.b.arr.push({ name: 'ccc' }) -this.update() +This.store.data.motto = 'Hello Westore' +This.store.data.b.arr.push({ name: 'ccc' }) +This.update() ``` -等同于 +Equivalent to ``` js -this.update({ - motto:'Hello Westore', - [`b.arr[${this.store.data.b.arr.length}]`]:{name:'ccc'} +This.update({ +  Motto: 'Hello Westore', +  [`b.arr[${this.store.data.b.arr.length}]`]:{name:'ccc'} }) ``` -这里需要特别强调,虽然 this.update 可以兼容小程序的 this.setData 的方式传参,但是更加智能,this.update 会先 Diff 然后 setData。原理: +It is important to emphasize here that although this.update is compatible with the applet's this.setData method, it is more intelligent, this.update will first Diff and then setData. principle: ![](./asset/update2.jpg) ## API -Westore API 只有四个, 大道至简: +There are only four Westore APIs, Avenue to Jane: -* create(store, option) 创建页面 -* create(option) 创建组件 -* this.update([data]) 更新页面或组件,其中 data 为可选,data 的格式和 setData 一致 -* store.update([data]) 更新页面或组件,在非页面非组件的 js 文件中使用 -* store.method(path, fn) 更新或扩展函数属性,注意这里不能直接赋值的方式修改函数属性,需要使用 store.method -* store.onChange = fn 监听 store data 的变化回调,一般可在里面写一些上报或监控数据变化的其他公共逻辑 +* create(store, option) create page +* create(option) create component +* this.update([data]) updates the page or component, where data is optional and the format of data is the same as setData +* store.update([data]) updates the page or component, used in non-page non-component js files +* store.method(path, fn) Update or extend the function properties. Note that you cannot modify the function properties directly by assigning them. You need to use store.method. +* store.onChange = fn listens for store data change callbacks, which can be used to write some other common logic for reporting or monitoring data changes. -纯组件使用小程序自带的 Component,或使用 `create({ pure: true })`。create的方式可以使用 update 方法,Component 方式不行。 +The pure component uses the Component that comes with the applet, or uses `create({ pure: true })`. The create method can use the update method, the Component method does not work. ## 普通开发 -### 定义全局 store - -```js -export default { - data: { - motto: 'Hello World', - userInfo: {}, - hasUserInfo: false, - canIUse: wx.canIUse('button.open-type.getUserInfo'), - logs: [], - b: { - arr: [{ name: '数值项目1' }] , - //深层节点也支持函数属性 - fnTest:function(){ - return this.motto.split('').reverse().join('') - } - }, - firstName: 'dnt', - lastName: 'zhang', - fullName: function () { - return this.firstName + this.lastName - }, - pureProp: 'pureProp', - globalPropTest: 'abc', //更改我会刷新所有页面,不需要再组件和页面声明data依赖 - ccc: { ddd: 1 } //更改我会刷新所有页面,不需要再组件和页面声明data依赖 - }, - globalData: ['globalPropTest', 'ccc.ddd'], - logMotto: function () { - console.log(this.data.motto) - }, - //默认 false,为 true 会无脑更新所有实例 - //updateAll: true -} -``` - -页面和组件上同样需要声明依赖的 data,这样 westore 会按需局部更新。如 Page 的 data: - -```js -data: { - motto: null, - userInfo: null, - hasUserInfo: null, - canIUse: null, - b: { arr: [ ] }, - firstName: null, - lastName: null, - pureProp: null, - //privateProp 你也可以定义 store.data 没有的属性,该属性的变更只能通过 this.setData 进行更新视图 - privateProp: '私有数据', - xxxx: '私有数据2' -} -``` - -页面和组件上声明的 data 的值会被 store 上的值覆盖掉。所以页面和组件默认值在 store.data 上标记,而不是在组件和页面的 data。纯组件在组件内部的 data 定义默认值。所以归纳一下: - -* store.data 用来列出所有属性和默认值 -* 组件的私有 data 也可以放在自身的 data 上,不用挂在 store.data 上,但是不能通过 this.update 更新,只能通过 setData -* 组件和页面的 data 用来列出依赖的 store.data 的属性 (westore会记录path),按需更新 -* 如果小程序页面和组件很少,可以 updateAll 设置成 true,并且组件和页面不需要声明 data,也就不会按需更新 -* 纯组件的 data 和 store.data 没有关系,所有其 data 用来列出所有属性和默认值 -* globalData 里声明的 path,只要修改了对应 path 的值,就会刷新所有页面和组件 - -比起原生小程序增强的功能是提供了 data 函数属性,比如上面的 fullName,在小程序 WXML 直接绑定: - -```jsx -{{fullName}} -``` - -### 创建页面 - -```js -import store from '../../store' -import create from '../../utils/create' - -const app = getApp() - -create(store, { - //只是用来给 westore 生成依赖 path 局部更新 - data: { - motto: null, - userInfo: null, - hasUserInfo: null, - canIUse: null, - b: { arr: [ ] }, - firstName: null, - lastName: null, - pureProp: null - }, - onLoad: function () { - if (app.globalData.userInfo) { - this.store.data.userInfo = app.globalData.userInfo - this.store.data.hasUserInfo = true - this.update() - } else if (this.data.canIUse) { - app.userInfoReadyCallback = res => { - this.store.data.userInfo = res.userInfo - this.store.data.hasUserInfo = true - this.update() - } - } else { - wx.getUserInfo({ - success: res => { - app.globalData.userInfo = res.userInfo - this.store.data.userInfo = res.userInfo - this.store.data.hasUserInfo = true - this.update() - } - }) - } - } - -}) -``` - -创建 Page 只需传入两个参数,store 从根节点注入,所有子组件都能通过 this.store 访问。 - -### 绑定数据 - -```jsx - - - - - - - {{userInfo.nickName}} - - - - {{motto}} - - {{fullName}} - - -``` - -和以前的写法没有差别,直接把 `store.data` 作为绑定数据源。 data 的函数属性也可以直接绑定。 - -### 更新页面 - -```js -this.store.data.any_prop_you_want_to_change = 'any_thing_you_want_change_to' -this.update() -``` - -### 创建组件 - -```js - -import create from '../../utils/create' - -create({ - ready: function () { - //you can use this.store here - }, - - methods: { - //you can use this.store here - } -}) - -``` -和创建 Page 不一样的是,创建组件只需传入一个参数,不需要传入 store,因为已经从根节点注入了。 - -### 更新组件 - -```js -this.store.data.any_prop_you_want_to_change = 'any_thing_you_want_change_to' -this.update() -``` - -### setData 和 update 对比 - -拿官方模板示例的 log 页面作为例子: - -```js -this.setData({ - logs: (wx.getStorageSync('logs') || []).map(log => { - return util.formatTime(new Date(log)) - }) -}) -``` - -使用 westore 后: - -``` js -this.store.data.logs = (wx.getStorageSync('logs') || []).map(log => { - return util.formatTime(new Date(log)) -}) -this.update() -``` - -看似一条语句变成了两条语句,但是 this.update 调用的 setData 是 diff 后的,所以传递的数据更少。 - -### 跨页面同步数据 - -使用 westore 你不用关心跨页数据同步,你只需要专注 this.store.data 便可,修改完在任意地方调用 update 便可: - -```js -this.update() -``` - -### 纯组件 - -这里说的组件便是自定义组件,使用原生小程序的开发格式如下: - -```js - -Component({ - properties: { }, - - data: { }, - - methods: { } -}) -``` - -使用 Westore 之后: - -```js -import create from '../../utils/create' - -create({ - properties: { }, - - data: { }, - - methods: { } -}) -``` - -看着差别不大,但是区别: - -* Component 的方式使用 setData 更新视图 -* create 的方式直接更改 store.data 然后调用 update -* create 的方式可以使用函数属性,Component 不可以,如: - -```js -export default { - data: { - firstName: 'dnt', - lastName: 'zhang', - fullName:function(){ - return this.firstName + this.lastName - } - } +### Define global store + +```js +Export default { +  Data: { +    Motto: 'Hello World', +    userInfo: {}, +    hasUserInfo: false, +    canIUse: wx.canIUse('button.open-type.getUserInfo'), +    Logs: [], +    b: { +      Arr: [{ name: 'value item 1' }] , +      //Deep nodes also support function properties +      fnTest:function(){ +        Return this.motto.split('').reverse().join('') +      } +    }, +    firstName: 'dnt', +    lastName: 'zhang', +    fullName: function () { +      Return this.firstName + this.lastName +    }, +    pureProp: 'pureProp', +    globalPropTest: 'abc', //Change I will refresh all pages, no need to declare data dependencies on components and pages +    Ccc: { ddd: 1 } //Change I will refresh all pages, no need to declare components and page depend on data dependencies +  }, +  globalData: ['globalPropTest', 'ccc.ddd'], +  logMotto: function () { +    Console.log(this.data.motto) +  }, +  //default false, true will not update all instances without brain +  //updateAll: true } ``` -绑定到视图: - -```jsx -{{fullName}} -``` - -有一些组件区别于业务组件,叫纯组件。如 tip、alert、dialog、pager、日历等,与业务数据无直接耦合关系。 -组件的显示状态由传入的 props 决定,与外界的通讯通过内部 triggerEvent 暴露的回调。 -triggerEvent 的回调函数可以改变全局状态,实现单向数据流同步所有状态给其他兄弟、堂兄、姑姑等组件或者其他页面。 - -Westore里可以使用 `create({ pure: true })` 创建纯组件(当然也可以直接使用 Component),比如 : - -```js - -import create from '../../utils/create' - -create({ - pure : true, - - properties: { - text: { - type: String, - value: '', - observer(newValue, oldValue) { } - } - }, - - data: { - privateData: 'privateData' - }, - - ready: function () { - console.log(this.properties.text) - }, - - methods: { - onTap: function(){ - this.store.data.privateData = '成功修改 privateData' - this.update() - this.triggerEvent('random', {rd:'成功发起单向数据流' + Math.floor( Math.random()*1000)}) - } - } -}) -``` - -需要注意的是,加上 `pure : true` 之后就是纯组件,组件的 data 不会被合并到全局的 store.data 上。 - -组件区分业务组件和纯组件,他们的区别如下: - -* 业务组件与业务数据紧耦合,换一个项目可能该组件就用不上,除非非常类似的项目 -* 业务组件通过 store 获得所需参数,通过更改 store 与外界通讯 -* 业务组件也可以通过 props 获得所需参数,通过 triggerEvent 与外界通讯 -* 纯组件与业务数据无关,可移植和复用 -* 纯组件只能通过 props 获得所需参数,通过 triggerEvent 与外界通讯 - -大型项目一定会包含纯组件、业务组件。通过纯组件,可以很好理解单向数据流: - -![data-flow](./asset/data-flow2.jpg) - -### 调试 +The pages and components also need to declare the dependent data so that westore will be partially updated as needed. Such as Page data: ```js -console.log(getApp().globalData.store.data) -``` - -### 超大型小程序最佳实践(两种方案) - -不排除小程序被做大得可能,接触的最大的小程序有 60+ 的页面,所以怎么管理?这里给出了两个最佳实践方案。 - -* 第一种方案,拆分 store 的 data 为不同模块,如: - -```js -export default { - data: { - commonA: 'a', - commonB: 'b', - pageA: { - a: 1 - xx: 'xxx' - }, - pageB: { - b: 2, - c: 3 - } - }, - xxx: function () { - console.log(this.data) - } -} -``` - -* 第二种方案,拆分 store 的 data 到不同文件且合并到一个 store 暴露给 create 方法,如: - -a.js - -```js -export default { - data: { - a: 1 - xx: 'xxx' - }, - aMethod: function (num) { - this.data.a += num - } -} -``` - -b.js - - -```js -export default { - data: { - b: 2, - c: 3 - }, - bMethod: function () { - - } -} -``` - -store.js - -```js -import a from 'a.js' -import b from 'b.js' - -export default { - data: { - commonNum: 1, - commonB: 'b', - pageA: a.data - pageB: b.data - }, - xxx: function () { - //you can call the methods of a or b and can pass args to them - console.log(a.aMethod(commonNum)) - }, - xx: function(){ - - } -} -``` - -当然,也可以不用按照页面拆分文件或模块,也可以按照领域来拆分,这个很自由,视情况而定。 - -## 注意事项 - -解决元素组件状态不同步的问题,比如 switch 需要通过绑定 bindtap 去记录状态,不然无法 diff 出更改: - -```html - -``` - -```js - switchTap() { - this.store.data.value = !this.store.data.value - this.update() - }, - restore() { - this.store.data.value = true - this.update() - } -``` - -原本是用户交互状态不影响 data 的,需要特别注意同步 data 到 store.data,不然无法 diff 出 patch,多谢 @i7soft。 - -## 原理 - -``` - --------------- ------------------- ----------------------- -| this.update | → | json diff | → | setData()-setData()...| → 之后就是黑盒(小程序官方实现,但是 dom/apply diff 肯定是少不了) - --------------- ------------------- ----------------------- -``` - -虽然和 Omi 一样同为 this.updata 但是却有着本质的区别。Omi 的如下: - -``` - --------------- ------------------- ---------------- ------------------------------ -| this.update | → | setState | → | jsx rerender | → | vdom diff → apply diff... | - --------------- ------------------- ---------------- ------------------------------ -``` - -都是数据驱动视图,但本质不同,原因: - -* 小程序 store 和 dom 不在同一个环境,先在 js 环境进行 json diff,然后使用 diff 结果通过 setData 通讯告诉 webview -* Web 里使用 omi 的话 store 和 dom 在同一环境,setState 直接驱动的 vdom diff 然后把 diff 结果作用在真实 dom 上 -* Native 里使用 omi 的话,omi 通过 jsbridge 向 客户端发送的是 dom 指令而非 数据 - -### JSON Diff - -先看一下我为 westore 专门定制开发的 [JSON Diff 库](https://github.com/dntzhang/westore/blob/master/packages/westore/utils/diff.js) 的能力: - -``` js -diff({ - a: 1, b: 2, c: "str", d: { e: [2, { a: 4 }, 5] }, f: true, h: [1], g: { a: [1, 2], j: 111 } -}, { - a: [], b: "aa", c: 3, d: { e: [3, { a: 3 }] }, f: false, h: [1, 2], g: { a: [1, 1, 1], i: "delete" }, k: 'del' -}) -``` - -Diff 的结果是: - -``` js -{ "a": 1, "b": 2, "c": "str", "d.e[0]": 2, "d.e[1].a": 4, "d.e[2]": 5, "f": true, "h": [1], "g.a": [1, 2], "g.j": 111, "g.i": null, "k": null } -``` - -![diff](./asset/diff.jpg) - -Diff 原理: - -* 同步所有 key 到当前 store.data -* 携带 path 和 result 递归遍历对比所有 key value - -``` js -export default function diff(current, pre) { - const result = {} - syncKeys(current, pre) - _diff(current, pre, '', result) - return result -} -``` - -同步上一轮 state.data 的 key 主要是为了检测 array 中删除的元素或者 obj 中删除的 key。 - -### 小程序 setData - -setData 是小程序开发中使用最频繁的接口,也是最容易引发性能问题的接口。在介绍常见的错误用法前,先简单介绍一下 setData 背后的工作原理。setData 函数用于将数据从逻辑层发送到视图层(异步),同时改变对应的 this.data 的值(同步)。 - -其中 key 可以以数据路径的形式给出,支持改变数组中的某一项或对象的某个属性,如 array[2].message,a.b.c.d,并且不需要在 this.data 中预先定义。比如: - -```js -this.setData({ - 'array[0].text':'changed data' -}) -``` - -所以 diff 的结果可以直接传递给 `setData`,也就是 `this.update`。 - -#### setData 工作原理 - -小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。 - -而 evaluateJavascript 的执行会受很多方面的影响,数据到达视图层并不是实时的。 - -常见的 setData 操作错误: - -* 频繁的去 setData -* 每次 setData 都传递大量新数据 -* 后台态页面进行 setData - -上面是官方截取的内容。使用 webstore 的 this.update 本质是先 diff,再执行一连串的 setData,所以可以保证传递的数据每次维持在最小。既然可以使得传递数据最小,所以第一点和第三点虽有违反但可以商榷。 - -### Update - -#### 页面生命周期函数 - -| 名称 | 描述 | -| ------ | ------ | -| onLoad | 监听页面加载 | -| onShow | 监听页面显示 | -| onReady | 监听页面初次渲染完成 | -| onHide | 监听页面隐藏 | -| onUnload | 监听页面卸载 | - -### 组件生命周期函数 - -| 名称 | 描述 | -| ------ | ------ | -| created | 在组件实例进入页面节点树时执行,注意此时不能调用 setData | -| attached | 在组件实例进入页面节点树时执行 | -| ready | 在组件布局完成后执行,此时可以获取节点信息(使用 SelectorQuery ) | -| moved | 在组件实例被移动到节点树另一个位置时执行 | -| detached | 在组件实例被从页面节点树移除时执行 | - -这里区分在页面中的 update 和 组件中的 update。页面中的 update 在 onLoad 事件中进行实例收集。 - -```js -const onLoad = option.onLoad -option.onLoad = function () { - this.store = store - rewriteUpdate(this) - store.instances[this.route] = [] - store.instances[this.route].push(this) - onLoad && onLoad.call(this) -} -Page(option) -``` - -组件中的 update 在 ready 事件中进行行实例收集: - -```js -const ready = store.ready -store.ready = function () { - this.page = getCurrentPages()[getCurrentPages().length - 1] - this.store = this.page.store - this.setData.call(this, this.store.data) - rewriteUpdate(this) - this.store.instances[this.page.route].push(this) - ready && ready.call(this) -} -Component(store) -``` - -rewriteUpdate 的实现如下: - -``` js -function rewriteUpdate(ctx) { - ctx.update = (patch) => { - let needDiff = false - let diffResult = patch - if (patch) { - for (let key in patch) { - updateByPath(ctx.store.data, key, patch[key]) - if (typeof patch[key] === 'object') { - needDiff = true - } - } - } else { - needDiff = true - } - if (needDiff) { - diffResult = diff(ctx.store.data, originData) - } - for (let key in ctx.store.instances) { - ctx.store.instances[key].forEach(ins => { - ins.setData.call(ins, diffResult) - }) - } - ctx.store.onChange && ctx.store.onChange(diffResult) - for (let key in diffResult) { - updateByPath(originData, key, diffResult[key]) - } - } -} -``` - -westore 会收集所有页面和组件的实例,在开发者执行 this.update 的时候遍历所有实例进行 setData。 - -### 函数属性 - -``` js -function defineFnProp(data) { - Object.keys(data).forEach(key => { - const fn = data[key] - if (typeof fn == 'function') { - fnMapping[key] = fn - Object.defineProperty(globalStore.data, key, { - enumerable: true, - get: () => { - return fnMapping[key].call(globalStore.data) - }, - set: (value) => { - fnMapping[key] = value - } - }) - } - }) +Data: { +  Motto: null, +  userInfo: null, +  hasUserInfo: null, +  canIUse: null, +  b: { arr: [ ] }, +  firstName: null, +  lastName: null, +  pureProp: null, +  //privateProp You can also define properties that are not available in store.data. Changes to this property can only be updated via this.setData . +  privateProp: 'private data', +  Xxxx: 'Private Data 2' } ``` -通过 defineProperty 重写了属性的 get 和 set,fnMapping 存放所有 key 和函数的映射。这里一定要设置 enumerable 为 true,因为 diff 的时候需要遍历。 +The value of data declared on the page and component is overwritten by the value on the store. So page and component defaults are marked on store.data instead of data on components and pages. The pure component defines the default value for the data inside the component. So sum up: -## License -MIT [@dntzhang](https://github.com/dntzhang) +* store.data is used to list all properties and defaults +* The component's private data can also be placed on its own data, not on store.data, but not through this.update, only through setData +* The data of the component and page is used to list the properties of the dependent store.data (westore will record the path), updated as needed +* If the applet page and components are few, updateAll can be set to true, and components and pages do not need to declare data, so they will not be updated as needed. +* pure component data and store.data no