# Tina **Repository Path**: youarefortunate/Tina ## Basic Information - **Project Name**: Tina - **Description**: 让原生小程序拥有全局响应式状态管理器的库 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2025-10-13 - **Last Updated**: 2025-10-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### Tina - 微信小程序全局响应式状态管理器解决方案 #### 前言 小程序开发有时难免也会遇到复杂的业务场景,如跨页面的数据传递,非父子组件的数据同步,多个子孙组件的数据复用等等,此时globalData或者triggerEvent/selectComponent已经无法很好地提供支持,相反,它们会导致业务逻辑代码和模板代码迅速膨胀到难以阅读和维护,同时也容易产生难以追踪的bug隐患,成熟的前端框架都有对应的数据管理器便于数据进行管理,Tina的功能是让小程序拥有vuex一样的功能,让小程序的数据能够进行模块化管理,全组件全页面响应式且可以使用computed及watch功能 #### 特别说明 由于Tina的组件实例是在Component构造器上进行扩展和封装,有赞的vant-ui的组件也是在Component构造器上进行扩展和封装,两者在同一项目中使用会造成冲突,这一点要特别注意 #### 安装 方式一: 通过 npm 安装 (推荐) ``` //在项目根目录下初始化 npm init -y //安装tina-weapp npm install tina-weapp //构建 在开发者工具中: 工具 ---> 构建npm ``` #### 引入 在入口文件app.js中引入tina-weapp ``` // app.js import 'tina-weapp' App({ onLaunch() { }, globalData: { } }) ``` #### 方式二: 下载代码 直接通过 git 下载 tina-weapp 源代码,并将lib下的目录文件放在项目根目录下 ``` git clone https://gitee.com/Tinans/Tina.git ``` #### 引入 在入口文件app.js中引入下载的代码文件 ``` // app.js import "./weapp/index" App({ onLaunch() { }, globalData: { } }) ``` #### 使用Store ``` import { Store } from 'tina-weapp' import app from './modules/app' import user from './modules/user' import getters from './getters' export default new Store ({ modules: { app, user }, getters }) ``` #### 引入Store 请在app.js中放在proxyData中,如下示例 ``` // app.js import store from './store/index' App({ proxyData: { store }, onLaunch() { }, globalData: { } }) ``` #### 使用axios 使用方式跟axios一样,支持请求拦截,响应拦截,取消请求,headers配置,添加 authKey,配置默认config等,在Tina中使用如下: 配置拦截 ``` import { axios } from 'tina-weapp' const instance = axios.create({ baseURL: 'https://www.test.com/api/', timeout: 10000 }) //请求拦截 instance.interceptors.request.use( config => { config.headers['token'] = 'myToken' return config }, error => { console.error(error) return Promise.reject(error) } ) //响应拦截 instance.interceptors.response.use( response => { if (response.data.code == 1) { console.log(response.data.msg) } return response }, error => { console.warn(error) return Promise.reject(error) } ) export default instance ``` #### 引入配置文件 如若添加拦截配置,请在app.js中重新引入并放在proxyData中,如下示例 ``` // app.js //你的拦截配置文件 import axios from './config/request/index' App({ proxyData: { axios } }) ``` 在页面中或组件中使用axios ``` // pages/home/index.js getData: function() { this.$axios({ url: 'user/banner', }).then(res => { console.log('data', res.data) }).catch(err => console.log('err', err)) const CancelToken = this.$axios.CancelToken let cancel const source = CancelToken.source() //取消请求方式一 this.$axios.get('user/banner', { cancelToken: source.token, }).then(res => { console.log('data', res.data) }).catch(err => console.log('err', err)) source.cancel('测试取消请求') //取消请求方式二 this.$axios.get('user/banner', { cancelToken: new CancelToken(function executor(c) { cancel = c }) }).then(res => { console.log('data', res.data) }).catch(err => console.log('err', err)) cancel('主动取消请求') }, ``` #### 使用computed, watch computed及watch使用,使用方法跟在vue中使用是一样的 ``` // pages/home/index.js import { mapState, mapGetters } from 'tina-weapp' Page({ data: { num: 50 }, onLoad() { let unwatch = this.$watch('total', function (value) { console.log('total', value) }, { immediate: true }) unwatch() }, computed: { count: we => we.$store.state.user.count, total() { return this.data.count + 50 }, ...mapState('user', { userInfo: state => state.userInfo }), ...mapGetters(["doubleNumber"]), ...mapGetters('user', { doubleCount: 'doubleCount' }), }, watch: { count: function (newVal, oldVal) { console.log('newVal', newVal, 'oldVal', oldVal) }, num: function (newVal, oldVal) { console.log('newVal', newVal, 'oldVal', oldVal) }, total: 'doSomething', doubleCount: [ function handle1 (val, oldVal) { console.log('111111') }, function handle2 (val, oldVal) { console.log('222222', val, oldVal) } ], userInfo: { handler(a, b) { console.log('新值', a, '旧值', b) }, deep: true, immediate: true } }, doSomething(x, y) { console.log(`watching, ${x}, ${y}`) }, }) ``` #### 使用全局自定义方法 Tina提供了一些全局方法,方便开发者全局(页面组件均可)调用 1. routeTo方法: routeTo方法是一个全局方法,若写在标签上可直接跳转(不用在js里写跳转方法),要写跳转路径url,跳转类型可写可不写,不写默认是navigateTo方式跳转,若是其他方式跳转可参考以下代码: ```html 全局router使用示例 全局route带参跳转 // pages/home/index.js /** * routeTo使用示例 */ navigateTo(e) { const { type } = e.currentTarget.dataset const params = { id: 10, price: 99, count: 2, weight: '50kg' } if (type) { this.$route('/pages/mine/index', type, params) } else { this.$route('/pages/mine/index', params) } } ``` 2. eventBus事件总线: ``` //添加事件侦听 this.$bus.on('add', this.sum) //调用事件回调 this.$bus.emit('add', 50, 60) //只添加一次侦听 this.$bus.once('add', this.sum) //移除事件侦听 this.$bus.off('add', this.sum) ``` 3. 获取和修改globalData内的数据: ``` // app.js App({ onLaunch() { }, globalData: { date: '2020-01-01', bookList: [ { bookName: 'Gone with the wind', info: { author: 'Margaret Mitchell', } } ], bookInfo: { bookName: 'Gone with the wind', info: { author: 'Margaret Mitchell', } } } }) // pages/home/index.js /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { //获取 const global = this.$global() const time = this.$global('date') let info = this.$global('bookInfo.info') let bookName = this.$global('bookList[0].bookName') //修改 this.$global('bookList[0].bookName', '乱世佳人') this.$global('bookList[0].title', '飘') this.$global('bookInfo.info.author', 'unknow') }, ``` #### 使用Sass, Less 1. 使用Less, 可以参考[这篇文章](https://juejin.cn/post/6856649674020012045),vscode其他插件如highlight-matching-tag,auto-close-tag,auto-rename-tag等经常使用的插件都可以这么引入,这几个插件不用修改配置文件,添加后重启开发工具就可以了 2. 使用Sass, 跟使用Less的步骤一样,下载Easy Sass插件,设置配置文件如下: ``` { "easysass.formats":[ { "format": "expanded", // 没有缩进的、扩展的css代码 "extension": ".wxss" //转化的后缀名 }, ] } ``` 配置完重启开发工具,在同名wxss同级文件建立跟wxss同名的文件,编写sass或者less后会自动编译保存到同名wxss文件 #### setDataDiff 原生小程序常见的setData操作错误[参考官方说法](https://developers.weixin.qq.com/miniprogram/dev/framework/performance/tips.html): 1. 频繁的去 setData 2. 每次 setData 都传递大量新数据 3. 后台态页面进行 setData setDataDiff的效果如下: ``` const data = [ { id: '10001', name: 'aaa' }, { id: '10002', name: 'bbb' }, { id: '10003', name: 'ccc' } ] const newData = [ { id: '10001', name: 'aaa', age: 20 }, { id: '10002', name: 'bbb' }, { id: '10003', name: 'ccc' }, { id: '10004', name: 'eee' }, { id: '10005', name: 'fff' }, { id: '10006', name: 'ttt' } ] const diffResult = diffData(data, newData) //diff之后如下 { 'data[0].age': 20, 'data[4].id': '10004', 'data[4].name': 'eee', 'data[5].id': '10005', 'data[5].name': 'fff', 'data[6].id': '10006', 'data[5].name': 'ttt' } ``` 小程序setData的数据在JSON.stringify后超过1MB引起页面卡顿非常明显,而且官方也说明一次性setData的量不能太多,最理想的条件是setData的数据在JSON.stringify后不超过 256KB,小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView 进行渲染;逻辑层采用JsCore线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端做中转,逻辑层发送网络请求也经由微信客户端转发,而遇到长列表渲染时非常棘手,长列表通常通过分页加载,一般处理分页我们是通过将原来的数据和最新一页的数据进行合并后setData,当加载到第100页时要setData一百页的数据,这个数据量非常大会引起页面卡顿甚至卡死。在Tina中经过setDataDiff后,取的是setData前后数据的差集,加载到第一百页,setData的数据也只是第一百页的数据,这样就可以达到优化setData效果