{"id":28708524,"url":"https://github.com/tunkjs/tunk","last_synced_at":"2025-08-26T06:37:16.891Z","repository":{"id":57382203,"uuid":"69519993","full_name":"tunkjs/tunk","owner":"tunkjs","description":"tunkjs是一个具有状态管理功能的前端架构优化框架，提供了一个让数据处理逻辑与交互逻辑完美解耦与灵活通信的模式。","archived":false,"fork":false,"pushed_at":"2018-11-05T06:49:07.000Z","size":1209,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-20T06:48:46.615Z","etag":null,"topics":["dispatch","mixins","oop","react","state-management","store-tunk","tunk","tunk-action","tunk-loader","tunk-vue","vue"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tunkjs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-09-29T01:51:16.000Z","updated_at":"2023-09-13T09:40:20.000Z","dependencies_parsed_at":"2022-09-26T16:50:16.220Z","dependency_job_id":null,"html_url":"https://github.com/tunkjs/tunk","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/tunkjs/tunk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tunkjs%2Ftunk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tunkjs%2Ftunk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tunkjs%2Ftunk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tunkjs%2Ftunk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tunkjs","download_url":"https://codeload.github.com/tunkjs/tunk/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tunkjs%2Ftunk/sbom","scorecard":{"id":902319,"data":{"date":"2025-08-11","repo":{"name":"github.com/tunkjs/tunk","commit":"9c8a922064a83851b8f1728d2e2a602272cddfa1"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.6,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'","Warn: branch protection not enabled for branch '3.5'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-24T16:03:21.679Z","repository_id":57382203,"created_at":"2025-08-24T16:03:21.680Z","updated_at":"2025-08-24T16:03:21.680Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272186211,"owners_count":24888333,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-26T02:00:07.904Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dispatch","mixins","oop","react","state-management","store-tunk","tunk","tunk-action","tunk-loader","tunk-vue","vue"],"created_at":"2025-06-14T18:35:14.602Z","updated_at":"2025-08-26T06:37:16.885Z","avatar_url":"https://github.com/tunkjs.png","language":"JavaScript","readme":"\n\u003cdiv style=\"text-align:center; margin-bottom:50px;\"\u003e\n\u003cimg style=\"width: 200px;\" src=\"https://github.com/tunkjs/gitbook-tunkjs/blob/master/img/logo1x.png?raw=true\" alt=\"tunk logo\"\u003e\n\u003c/div\u003e\n\n\n#### tunkjs是一个具有状态管理功能的前端架构优化框架，提供了一个让数据处理逻辑与交互逻辑完美解耦与灵活通信的模式。 \n\n### 开发初衷\n\ntunk旨在优化前端架构、提高开发体验、掰直学习曲线、降低web前端项目开发维护成本，为此做了一定的封装让其拥有必要的特性、减少特有的规则及编码细节、精简接口，入门只需要掌握几个方法的使用便可接手tunk架构项目的业务开发。\n\ntunk架构下，你的**前端代码一般会被分为两层：数据服务层与视图表现层**，数据服务层由N个**数据服务模块**组成，视图层由仅仅负责数据展现与交互处理。视图组件面向数据服务层进行通信，包括发起服务模块的action执行，以及订阅状态更新。\n\n接到一个业务需求你通常要做两件事，一个是根据业务需求和接口的数据逻辑**设计模块类**，另一件事是**写视图组件**。\n\ntunk尽可能精简要暴露的API及处理的细节，让框架自身存在感更低，让你轻松上手，专注于业务的实现。\n\n通过限制模块更新状态树的范围，无需向store描述变更，状态变更也完全可预测。\n\n### 安装\n\n````javascript\nnpm install tunk -S\n````\n除了tunk你还需要安装**视图框架绑定组件**\n\n跟vue绑定\n\n````javascript\nnpm install tunk-vue -S\n````\n跟react或react native绑定\n\n````javascript\nnpm install tunk-react -S\n````\n跟微信小程序绑定\n\n````javascript\nnpm install tunk-wechat -S\n````\n\n### 实例\n\n----\n\n\u003e 场景：开发一个用户管理列表，列表中弹框查看用户详细信息\n\n### 写个数据服务模块\n\n````javascript\nimport {create, action} from 'tunk'\n// 创建userAdmin服务模块\n@create \nclass userAdmin {\n\tconstructor(){\n                // state属性仅用于在构造器中定义当前模块负责维护的状态字段\n                // 服务模块被创建后，Store状态树创建'userAdmin'节点，节点初始内容来自state\n                // 下面仅定义list为【状态字段】\n                this.state = {\n                        list:[]\n                }\n        }\n        // @action 定义一个请求用户列表数据的Action\n        // 只有userAdmin模块的action可以更新'userAdmin'节点的状态\n        // 并且只能更新已存在的状态字段，即 list 字段\n\t@action\n\tfetchList(param){\n                // request 是tunk-request组件提供的模块内置方法\n\t\tconst res = this.request(...);\n        \n                // 返回的结果可更新Store状态树 userAdmin 节点下的list字段，触发状态变更钩子\n\t\treturn {list: res.list};\n\t}\n\t@action\n\tasync getUserDetails(id){\n\t\tconst res = await this.request(...);\n                // details没有定义为状态字段，action处理结果的details字段不会更新到Store中\n                // 发起的action执行可获得返回结果，如：\n                // const details = await this.getUserDetails(id).details;\n\t\treturn {details: res.data};\n\t}\n\n\tsomeFunc(){\n                // 获取当前模块的状态\n\t\tconst state = this.getState();\n\t}\n\t...\n}\n````\n\n如果你的构建环境不支持修饰器和async/await，譬如微信小程序，你可以这样写一个模块\n\n````javascript\n// 注意：这里首字母大写\nimport {Create, Action} from 'tunk'\n\nCreate('userAdmin', {\n\t// 注意：构造器 采用constructor(){}的写法会导致意外出错\n\tconstructor: function(){\n\t\tthis.state = {\n\t\t\tlist:[]\n\t\t};\n\t},\n\tfetchList: Action(function(param) {\n        return this.request(...).then((res)=\u003e{\n\t\t\t\treturn {list: res.list};\n\t\t\t});\n\t\t});\n\t},\n\tgetUserDetails: Action(function(id){\n\t\treturn this.request(...).then((res)=\u003e{\n\t\t\treturn {details: res.data};\n\t\t});\n\t}),\n\tsomeFunc(){\n\t\tconst state = this.getState();\n\t}\n\t...\n});\n````\n\n----\n\n### 下面开发个视图组件\n\n**tunk与视图框架配合工作，需要跟视图框架绑定的组件，如tunk-vue、tunk-react、tunk-wechat**\n\n这些绑定组件负责定义视图组件如何 **触发Action** 及如何 **将新状态注入到视图组件**。\n\n下面你可以挑你要用到的视图框架的实例来阅读\n\n#### Vue\n````html\n\u003ctemplate\u003e\n  \u003cul\u003e\n\t  \u003cli v-for=\"item in list\"\u003e\n\t\t ...\n\t\t \u003cbutton @click=\"showUserDetails(item.id)\"\u003e查看用户信息\u003c/button\u003e \n\t  \u003c/li\u003e\n  \u003c/ul\u003e\n  ...\n\u003c/template\u003e\n\u003cscript\u003e\nexport default {\n\t// 状态订阅配置\n\tstate: {\n\t\t// list 是模块userAdmin定义的状态字段，可以被视图组件订阅\n\t\t// 组件被初始化后this.list将被注入当前 userAdmin.list 的状态\n\t\tlist: 'userAdmin.list'\n\t},\n\t// 代理action设置\n\tactions:{\n\t\t// 创建getDetails方法可调起模块的getUserDetails\n\t\tgetDetails: 'userAdmin.getUserDetails',\n\t},\n\tcreated(){\n\t\t// 通过dispatch方式调起action\n\t\tthis.dispatch('userAdmin.fetchList');\n\t},\n\tmethods:{\n\t\tasync showUserDetails(id){\n\t\t\t// 调用action代理方法，并获得action执行结果\n\t\t\tconst details = await this.getDetails(id).details;\n\t\t\t// 也可以通过dispatch调起action\n\t\t\t// const details = await this.dispatch('userAdmin.getUserDetails', id).details;\n\t\t}\n\t}\n}\n\u003c/script\u003e\n````\n\n\u003e \n\n#### React\n````javascript\nimport { connect } from 'tunk-react'\n@connect({ // 状态订阅配置\n\t// list 是模块userAdmin定义的状态字段，可以被视图组件订阅\n\t// 组件被初始化后this.list将被注入当前 userAdmin.list 的状态\n\tlist: 'userAdmin.list'\n}, {// 代理action设置\n\t// 创建getDetails方法可调起模块的getUserDetails\n\tgetDetails: 'userAdmin.getUserDetails'\n})\nexport default class UserAdmin extends Component {\n\tconstructor() {\n\t\t// 通过dispatch方式调起action\n\t\tthis.dispatch('userAdmin.fetchList');\n\t}\n\tasync showUserDetails(id) {\n\t\t// 调用action代理方法，并获得action执行结果\n\t\tconst details = await this.getDetails(id).details;\n\t\t// 也可以通过dispatch调起action\n\t\t// const details = await this.dispatch('userAdmin.getUserDetails', id).details;\n\t}\n    render() {\n\t\t// 以prop的方式注入到当前组件\n\t\tconst { list } = this.props;\n\t\treturn (\n\t\t\t\u003cul\u003e\n\t\t\t\t{list.map(item =\u003e (\u003cli key=\"item.id\"\u003e\n\t\t\t\t\t...\n\t\t\t\t\t\u003cbutton onClick={this.showUserDetails.bind(this, item.id)}\u003e查看用户信息\u003c/button\u003e \n\t\t\t\t\u003c/li\u003e))}\n\t\t\t\u003c/ul\u003e\n\t\t\t...\n\t\t)\n\t}\n}\n\n````\n\n#### 微信小程序\n````javascript\nimport {Page} from 'tunk-wechat'\nPage({\n\t// 状态订阅配置，Page隐藏状态下不会被注入状态\n\t// onShow时会重新注入已订阅的且已变更的状态\n    state: {\n\t\t// list 是模块userAdmin定义的状态字段，可以被视图组件订阅\n\t\t// 组件被初始化后this.list将被注入当前 userAdmin.list 的状态\n        list: 'userAdmin.list'\n\t},\n\t// 代理action设置\n\tactions:{\n\t\t// 创建getDetails方法可调起模块的getUserDetails\n        getDetails: 'userAdmin.getUserDetails'\n\t},\n\t// list有新状态准备注入前调用\n\tonBeforeStateChange(newState){\n\t\t// state订阅的状态数据，会被注入到this.data中\n\t\tconst oldListState = this.data.list;\n\t\t// 返回结果可控制setData的内容\n\t\t// 若没有定义onBeforeStateChange或没有返回Object内容，则默认注入newState\n    \treturn {list: newState.list.concat(oldListState)}\n\t},\n\tonLoad(){\n\t\t// 通过dispatch方式调起action\n    \tthis.dispatch('userAdmin.fetchList');\n\t},\n\t\n\tshowUserDetails(id){\n\t\t// 若action为同步函数，可直接获得结果，若为异步需在then方法中获得\n\t\t// 调用action代理方法，并获得action执行结果\n\t\tthis.getDetails(id).then(data =\u003e {\n\t\t\tconst details = data.details;\n\t\t\t...\n\t\t});\n\t\t// 也可以通过dispatch调起action\n\t\t// this.dispatch('userAdmin.getUserDetails', id).then(...);\n\t\t\n\t}\n}\n````\n\n#### 视图组件与tunk数据服务层通信\n\n##### A. 两种方式触发模块的Action\n\n1. 通过在设置action属性（vue/微信小程序）或connect（react）设置action注入配置，向视图组件注入Action代理方法，向视图组件注入Action代理方法\n2. 使用 `this.dispatch('moduleName.actionName', [arg1, arg2, ...])`，支持异步\n\n##### B. 两种方式获得Action处理结果\n\n1. **被动注入**：通过设置属性`state`，可订阅不同模块的状态\n2. **主动获取**：`dispatch`方法调起action，支持返回action执行结果，支持异步\n\n### tunk状态流\n\n\u003cdiv style=\"text-align:center; margin-bottom:50px;\"\u003e\n\u003cimg src=\"https://github.com/tunkjs/gitbook-tunkjs/blob/master/img/tunk-flow.png?raw=true\" alt=\"tunk logo\"\u003e\n\u003c/div\u003e\n\n\n### 通信 \n\n\n#### 视图组件与模块间通信\n\n模块间通信和模块与组件间通信是完全解耦的，所有模块共同构成一个数据服务层，视图组件面向这个数据服务层进行通信。\n\n**视图组件调起action的两种方式：**\n\n1. 视图组件通常会被提供一个类似dispatch方法，这个方法仅支持调起action，如：`this.dispatch('moduleName.actionName', arg1, arg2, ...);`\n2. 跟视图框架绑定的组件通常会支持`actons`组件属性，用来自动生成该组件可直接调用的action代理方法\n\n**视图组件获得数据的两个途径：**\n\n1. **被动注入** ：订阅不同模块的状态字段，当action引起了状态变更，订阅的组件会被注入新状态\n2. **主动获取** ：视图组件通常会被提供一个类似dispatch方法，这个方法仅支持调返回action返回的结果\n\n不同视图框架绑定组件的实现大同小异，具体可查看相关实例\n\n* [tunk-react](https://github.com/tunkjs/gitbook-tunkjs/blob/master/doc/plugins/tunk-react.md)\n* [tunk-vue](https://github.com/tunkjs/gitbook-tunkjs/blob/master/doc/plugins/tunk-vue.md)\n* [tunk-wechat](https://github.com/tunkjs/gitbook-tunkjs/blob/master/doc/plugins/tunk-wechat.md)\n\n\n\n### 要点\n\n\n1. 对任何状态管理器来说，如果数据不会被复用或者数据量过大，不推荐你将这部分数据定义为状态来维护，状态快照的生成需要做到引用隔离，而引用隔离摆脱不了深克隆的性能损耗。\n\n2. “主动获取”的方式不会生成状态快照，因此效率较高，可以理解这部分数据为“临时状态”，一般用完即焚，可视为传统状态流的补充。\n\n3. 我们推荐你尽可能的把数据处理逻辑从视图层剥离开来，除了分离关注带来的好处外，假设你开发完wap版本的应用，又准备开发嵌入到原生app的RN版本或者微信小程序版本，只要包装好数据源，数据服务层是可以直接复用的。\n\n\n----\n\n[tunk](https://github.com/tunkjs/gitbook-tunkjs)\n\n\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftunkjs%2Ftunk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftunkjs%2Ftunk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftunkjs%2Ftunk/lists"}