npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

dd-store

v1.11.0

Published

钉钉E应用状态管理 - 解决跨页面跨组件间通信,简洁小巧高性能

Readme

dd-store - 钉钉E应用状态管理

  • dd-stores - dd-store升级版,支持页面/组件多状态管理,更加灵活独立,适用于支付宝等阿里系小程序!【推荐】

前言

E应用是钉钉主推的开发企业应用的小程序应用,公司好几款产品也都是使用E应用开发,但E应用和其他小程序一样,都是没有官方实现的状态管理库,一开始我写了一个Emitter类,用事件监听方式去实现全局共享状态管理,解决了各种跨页面组件通信,但这种方式相对繁琐且不够直观,当页面状态比较多时,光监听函数就得写一堆,特别影响代码整洁性。于是乎网上寻找有没有更好的解决方案,最终找到了westore库,这是由腾讯开源团队研发的微信小程序解决方案,其中针对状态管理的实现很不错,而且还使用专门为小程序开发的JSON Diff 库保证每次以最小的数据量更新状态,比原生setData的性能更好。但由于小程序框架API的差异,不能直接拿来使用。

所以要在E应用上使用,还需要改动一下,于是在看了源码之后,基于其原理重写了一版,并去除了一些其他功能,只保留状态管理部分,总代码量从500行精简到了200行,另外根据自身理解做了如下优化改进:

1、优化渲染效率

每次使用this.update()触发渲染的时候,只对当前页面进行渲染,其他后台态页面只在再次显示的时候进行更新。这样可以大大减少同时setData的频次,提高渲染效率。

2、提供多种store使用方案

默认方案:采用单例store,即全局只使用一个store。

westore采用的就是这种方案,该方案好处是适用范围广,各种大小项目都能使用,对于大型项目,可以通过组合页面store的方式轻松管理。 不过在大项目实际使用中,会存在一些问题,如下:

1、性能问题。因为每次更新时都会使用diff库进行数据比对,如果全局只用一个store,那每次都会进行全量数据比对,也就是说项目状态越多,比对耗时就越长,虽然diff库性能非常好,但如果能做到极致,岂不美哉。

2、美观性。为了方便管理,越大的项目,可能会拆分更多的js文件,最后合并到store.data上,然后使用的时候你可能会经常看到如下代码:

<view a:for="{{pageA.someData.list}}"></view>
<view>{{pageA.someData.pageNo}} / {{pageA.someData.totalPage}}</view>
<view a:if="{{pageA.someData.isAdmin && pageA.someData.list.length > 0}}"></view>
...

你会发现,页面A所有状态绑定视图上时都要套一层pageA属性,如果属性名比较长或者数据层级较深,再加上if条件组合较多,整个页面单单变量名就占了很多空间,影响美观且不方便阅读,另外使用独立文件里的方法也会有同样的问题。其实相对于需要全局使用的状态,页面/领域状态是占绝大比例的。所以,如果能少一层嵌套,整体代码会优雅美观很多。

方案二:支持多例store,即每个页面可以选择使用独立的store。

在默认方案的基础上,增加了每个页面可以根据需要引入独立的store,使用方式和默认方案一样,但相比默认方案,大大减少了每次diff比对的数据量,一定程度提升了些性能;视图数据绑定或方法调用少了一层嵌套,代码更加美观。不过虽然优化了上面的问题点,却存在一个比较大的问题,就是全局store和页面store二者只能选其一,不能同时使用,一般用在页面不需要使用任何全局状态的场景。如果页面需要使用到某个全局状态,就只能把整个页面状态合并到全局状态上使用,这样可能越往后,又变成使用默认方案了。为了解决这个问题,于是有了方案三。

方案三(推荐):支持多例store,并支持页面同时使用全局store和页面store。

在方案二的基础上,将全局store命名为$store,与页面store区分开来,让页面可以同时是使用页面store和全局store,解决了上面两个方案存在的问题,并保留了它们的优势。

安装

npm i dd-store --save

API

  • create.Page(option) 创建页面
  • create.Component(option) 创建组件
  • setStore(store) 设置全局store // 方案一
  • setGlobalStore(store) 设置全局store // 方案三
  • this.update() 更新页面或组件,在页面、组件、store内使用
  • store.update() 在非页面非组件非store的js文件中使用,需要引入相应的store

另外创建页面/组件时可选配置option.useAll,如果配置为true,则会自动注入全局globalStore.data和全部store.data内字段值到页面/组件,无需手动声明依赖

注意:update已作为更新函数被占用,所以在store、页面、组件上不要重定义update字段,避免覆盖。

使用

使用默认方案

创建store

store其实是一个包含data属性的对象,可以使用任意方式来定义该对象。 注:函数属性中的this指向store.data对象。

class Store {

  data = {
    language: "zh_cn",
    userName: '李狗蛋',
    deptName: '化肥质检部门',
    corpName: '富土康化肥厂',
    // 函数属性 - 可直接绑定到视图上
    description() {
      return `我是${this.userName},我在${this.corpName}工作`
    },
    a: {
      b: {
        // 深层嵌套也支持函数属性
        c() {
          return this.language + this.description
        }
      }
    }
  }

  onChangeLang() {
    if(this.data.language === 'zh_cn') {
      this.data.language = 'en_US'
    } else {
      this.data.language = 'zh_cn'
    }
    this.update()
  }
}

export default new Store()

在app.js中注入全局store

在app.js使用setStore设置全局store,所有通过create.Page()/create.Component()创建的页面/组件都能通过this.store取到全局store的引用。

import { setStore } from 'dd-store'
import store from '/store'

setStore(store)

App({
  onLaunch(options) {},
});

创建页面

使用create.Page创建页面。在data上按需声明需要共享的属性(store.data内的属性),注意这里data只是声明属性,设置的默认值无效,默认值请在store.data设置。如果页面配置了useAll=true,则会自动注入全部store.data内字段,无需手动声明。另外定义store.data没有的属性,则默认为页面私有状态,只能使用默认的this.setData(obj)方式更新。

import create from 'dd-store'

create.Page({

  // store, // 全局store的引用,在app.js通过setStore设置后,自动绑定进来
  
  useAll: true, // 是否开启自动注入store.data全部字段,默认false

  data: {
    // 按需声明状态属性,设置的默认值无效,如需设置请在store.data内设置
    userName: null,
    corpName: null,

    // 定义store.data没有的属性,则默认为页面私有状态
    pageName: 'xxx'
  },
  
});

创建组件

使用create.Component创建组件

import create from 'dd-store'

create.Component({

  useAll: true, // 是否开启自动注入store.data内全部字段,默认false

  data: {
    // 和页面一样,此处只按需声明状态属性,设置的默认值无效,如需设置请在store.data内设置
    userList: null,
    deptList: null,

    //定义store.data没有的属性,则默认为组件私有状态
    commName: 'xxx'
  },

});

更新状态

直接更改store.data内的值,最后调用this.upadte()即可,非常人性化。

// 页面、组件内使用
this.store.data.language = 'zh_cn'
this.store.data.userName = '李狗蛋'
this.store.data.userList[0].name = '张三疯'
this.update()
// store内使用
this.data.language = 'zh_cn'
this.data.userName = '李狗蛋'
this.data.userList[0].name = '张三疯'
this.update()
// 其他js文件使用
import store from '/store'
store.data.language = 'zh_cn'
store.data.userName = '李狗蛋'
store.data.userList[0].name = '张三疯'
store.update()

使用方案二

方案二只是基于默认方案增加了页面独立store的引入,页面和页面内组件的this.store指向新的store,使用上完全没差别。

import create from 'dd-store'
import pageStore from '/stores/pageStore'

create.Page({
  
  store: pageStore, // 局部页面store,会覆盖方案一的全局store

  useAll: true, // 是否开启自动注入store.data全部状态,默认false

  data: {
    // 按需声明状态属性,设置的默认值无效,如需设置请在store.data内设置
    userName: null,
    corpName: null,

    // 定义store.data没有的属性,则默认为页面私有状态
    pageName: '页面名称'
  },
  
});

使用方案三(推荐)

方案三基于方案二添加一个新的全局store,命名为$store,即在页面/组件上通过this.$store取到globalStore的引用,另外globalStore.data将绑定到页面/组件的data.$data上,通过$data.xxx渲染到视图层。

在app.js中注入globalStore

在app.js使用setGlobalStore设置全局store,所有通过create.Page()/create.Component()创建的页面/组件都能通过this.$store取到globalStore的引用。

import { setGlobalStore } from 'dd-store'
import globalStore from '/stores/globalStore'

setGlobalStore(globalStore)

App({
  onLaunch(options) {},
});

页面使用

只要在页面data上声明$data就可以使用,如果配置了useAll=true,则自动添加$data到data,无需手动声明。

import create from 'dd-store'
import pageStore from '/stores/pageStore'

create.Page({

  // $store, // 全局globalStore的引用,在app.js通过setGlobalStore设置绑定后会自动注入,无需手动添加

  store: pageStore, // 配置页面store,若不配置,则默认使用通过setStore配置的store(如果有的话)

  useAll: true, // 是否开启自动注入store.data内全部字段 和 $store.data,默认false

  data: {
    // 声明使用全局状态,为了避免和store.data内字段命名冲突,这里$data取自$store.data整体
    $data: null,

    // 按需声明状态属性,设置的默认值无效,如需设置请在store.data内设置
    userName: null,
    corpName: null,

    // 定义store.data没有的属性,则默认为页面私有状态
    pageName: 'xxx'
  },
  
});

组件使用

同上create.Page。另外如不独立配置组件store,则默认注入所在根页面的store

import create from 'dd-store'

create.Component({

  // store: componentStore, // 配置组件store,如若不配置,则默认使用所在页面的store

  useAll: true, // 是否开启自动注入store.data内全部字段 和 $store.data,默认false

  data: {
    // 声明使用全局状态
    $data: null,

    // 此处只按需声明状态属性,设置的默认值无效,如需设置请在store.data内设置
    userList: null,
    deptList: null,

    //定义store.data没有的属性,则默认为组件私有状态
    commName: 'xxx'
  },

});

更新全局状态

直接更改globalStore.data内的值,最后调用this.upadte()即可,和页面store使用一样。

// 页面、组件内使用
this.$store.data.language = 'zh_cn'
this.update()
// globalStore内使用
this.data.language = 'zh_cn'
this.update()
// 其他js文件使用
import globalStore from '/stores/globalStore'
globalStore.data.language = 'zh_cn'
globalStore.update()

以上三个方案使用互不冲突,可以任意使用,一起使用也可以。

使用建议

1、对于小项目,可以考虑使用默认方案,当然也不一定非要使用状态管理,你可以先使用框架默认方式开发,至于什么时候需要使用,业务需求自会告诉你。

2、对于大项目,特别是超大型项目,状态多业务复杂,推荐使用方案三进行状态管理。另外个人建议将页面状态和逻辑提取到store上,页面只负责处理用户事件的监听和回调,这样的好处是:

  • 保持页面代码简洁,可以快速对每个页面的用户事件一目了然,更好把控业务。
  • 页面组件可以更轻松共享状态和复用页面逻辑
  • 当页面store之间存在相同逻辑,可以将其提取封装在抽象类上,然后使用继承方式轻松实现store间逻辑复用。
  • 状态逻辑在独立的js上,更易于代码测试,对使用函数式编程非常友好

以上是个人建议,小伙伴可以根据业务需求自由选择,当然也可以看心情选择,毕竟敲代码,最重要的就是开心啦。

在使用过程中如果遇到问题或有什么建议可以随时在Issues进行反馈,或钉钉联系我:linjinchun。(ps:请保持使用最新npm包)

快捷链接