{"id":21971203,"url":"https://github.com/xcodebuild/vanex","last_synced_at":"2025-03-22T23:09:36.664Z","repository":{"id":83287881,"uuid":"97317018","full_name":"xcodebuild/vanex","owner":"xcodebuild","description":"更好用的基于mobx的数据流管理框架","archived":false,"fork":false,"pushed_at":"2017-07-15T13:29:22.000Z","size":136,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-28T02:33:23.903Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":false,"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/xcodebuild.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-07-15T13:17:21.000Z","updated_at":"2017-08-24T01:57:29.000Z","dependencies_parsed_at":null,"dependency_job_id":"96c46bab-e075-4c88-b6b0-6d7595adfd3f","html_url":"https://github.com/xcodebuild/vanex","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xcodebuild%2Fvanex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xcodebuild%2Fvanex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xcodebuild%2Fvanex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xcodebuild%2Fvanex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xcodebuild","download_url":"https://codeload.github.com/xcodebuild/vanex/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245031509,"owners_count":20549925,"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","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":[],"created_at":"2024-11-29T14:49:21.856Z","updated_at":"2025-03-22T23:09:36.645Z","avatar_url":"https://github.com/xcodebuild.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# vanex\n\n基于`mobx \u0026 mobx-react`的React store管理框架，提供简单快捷的开发范式。使用模式类似dva，但用起来比dva更简单，开发效率更高！  \n\ngithub地址：https://github.com/abell123456/vanex\n\n## 特点\n\n`三个API`搞定问题！简单易上手，开发效率高。\n\n## 如何使用\n\n\u003e vanex提供了一键初始化的`start`方法，入口文件可以像下面这样开始：\n\n```js\nimport React from 'react';\nimport App from './App';\n\nimport {\n    start,\n} from 'vanex';\n\n// model\nimport user from './models/User';\nimport todos from './models/Todos';\n\nstart({\n    component: App,\n    container: '#root',\n    models: {\n        user,\n        todos\n    }\n});\n```\n\n所以，只需要把你的model（类似于tarot的module）、React Container Component、Middleware（可选）、Relation传递进来，应用就能跑起来了。\n\n介绍下几个概念：  \n\n- `model`： 数据管理，区别于tarot，其只有：name命名空间以及data、action两个核心部分，action部分可以同时存放类似于Reducers以及Effects两个部分的操作（作为优化，后续这里可以做拆分）；\n\n- `middleware`：中间件，用于辅助异步处理。model重定义的一个action最终被执行的流程是这样的：首先其会被mobx的action函数包一层，以避免掉每次更改数据都会触发一次UI的重新渲染，然后其会被各个中间件依次执行，而每个中间件都有before/after/error三个操作，可以在不同的操作中对每一种操作做统一的处理；\n\n- `relation`：用于不同model之间的通信，基于监听订阅模式。\n\n\u003e 基于vanex的开发范式的`container Component`也是UI Component，UI Component像下面这样：\n\n```js\nimport React, {Component, PropTypes} from 'react';\n\n// load middlewares\nimport './middlewares';\n\n// components\nimport UserLogin from './components/UserLogin';\nimport UserDetail from './components/UserDetail';\nimport Todos from './components/Todos';\n\nimport {\n    inject,\n    observer,\n} from 'vanex';\n\n// 注意先observer，后inject\n@inject('user')\n@observer\nexport default class App extends Component {\n    render() {\n        // const user = this.props.user.toJSON();\n        console.log(this.props.user.toJSON());\n        const {user} = this.props;\n\n        console.log('user.isLogin:', user.isLogin);\n\n        if (user.isLogin !== true) {\n            return \u003cUserLogin /\u003e;\n        }\n\n        return (\n            \u003cdiv\u003e\n                \u003cUserDetail /\u003e\n                \u003cTodos /\u003e\n            \u003c/div\u003e\n        );\n    }\n}\n```\n\n这里的`oberser`来自于mobx的observer，`inject`则来自于mobx-react。如果想给一个Component同时注入多个model，则可以像下面这样：  \n\n```js\n// start\nimport React from 'react';\nimport App from './App';\n\nimport {\n    start,\n} from 'vanex';\n\n// model\nimport user from './models/User';\nimport todos from './models/Todos';\n\nstart({\n    component: App,\n    container: '#root',\n    models: {\n        user,\n        todos\n    }\n});\n```\n\n```js\nimport {\n    inject,\n    observer,\n} from 'vanex';\n\n@inject(\n    stores =\u003e ({\n        user: stores.user,\n        todos: stores.todos,\n    })\n)\n@oberser\nclass MyComponent extends Component{\n    constructor(props, context) {\n        super(props, context);\n    }\n\n    render() {\n        const {\n            user,\n            todos,\n        } = this.props;\n\n        return (\n            \u003cdiv\u003e{user.name}\u003c/div\u003e\n        );\n    }\n}\n```\n\nmobx的observer API，用于将React Component变成observable的（动态收集依赖），在对model中的某些数据做了操作之后，如果被修改的数据刚好被该React组件使用到了，就会触发该组件的重新渲染，这也就是mobx能细粒度控制数据的原因所在。  \n\nmobx-react的inject API，用于指定将哪些model注入进React Component(this.props.modelName)，也就指定了该组件基于哪些数据做Observeable。\n\n## model\n\n代码类似于下面这样：\n\n```js\nimport TodoItem from './TodoItem';\nimport * as api from '../api';\n\nexport default {\n    name: 'Todos',\n    data: {\n        list: [],\n    },\n    syncs: {\n        add(text, userId) {\n            // 类似于Vue，对数组的操作会触发UI的重新渲染\n            this.list.push(new TodoItem({\n                text,\n                userId\n            }));\n        },\n    },\n    effects: {\n        async getByUserId(userId) {\n            let todos = await api.getTodosByUserId(userId);\n            todos = todos.map(todo =\u003e new TodoItem(todo));\n            // 类似于Vue，对数组的操作会触发UI的重新渲染\n            this.list = this.list.concat(todos);\n        },\n    }\n};\n```\n\nmodel由以下几个部分组成：\n\n- 1、name: 当前model的命名空间；\n- 2、constants: 不可变常量；\n- 3、data: 可操作数据部分；\n- 4、syncs: 同步操作数据部分；\n- 5、effects: 异步处理部分；\n- 6、init: 初始化model后的回调方法；\n- 7、autorun: 每次对数据进行操作后都会自动执行的方法。\n\n## 触发action\n\n### model内部触发\n\nmodel内部定义的Data数据，会被赋值到model实例上，所以任何在Data中定义的数据都可以通过this.xxx的方式来引用，如下：\n\n```js\n\nimport fetch from 'whatwg-fetch';\n\nconst pageSize = 20;\n\nexport default {\n    name: 'Applications',\n\n    data: {\n        dataSource: [\n\n        ], // 列表显示的数据\n\n        detailPageVisible: false,\n\n        campaignDetail: {},\n    },\n\n    syncs: {\n        validate(value) {\n            value = value ||'';\n\n            // xxxx\n\n            return {\n                code: 200\n            };\n        },\n    },\n\n    effects:{\n        async getList(payload = {}) {\n            const {\n                currentPage = 1,\n            } = payload;\n\n            const url = `/applications/list/${currentPage}?pageSize=${pageSize}`;\n\n            let res = await fetch(url);\n\n            res = res.body;\n\n            const validateRes = this.validate(res);\n\n            if(validateRes.code == 200) {\n              this.dataSource = res.data; // 这样就会触发对应Component的重新渲染\n              this.currentPage = res.currentPage;\n              this.totalItem = res.totalItem;\n              this.totalPage = res.totalPage;\n            }\n\n            return res;\n        },\n    }\n};\n```\n可以看到，更改数据则是直接给model实例赋值即可，简单直接高效，而且多次赋值只会触发一次的重新渲染。你能想象如果一个页面是一个list列表，用户对列表中某一个进行操作后，需要修改这一项的数据及显示，只需要执行类似于：\n\n```js\nthis.props.home.list[2].name = 'New Name';\n```\n\n的代码就能完成name的数据处理及页面展示更改吗？想想就激动是不是。  \n\n有的同学会有：`syncs和effects里面多次对model直接赋值会触发UI的多次渲染`的担心，其实不会的，我们队syncs以及effects里面的每一个方法都用会使用mobx的`action`做了一层包装，从而来避免这个问题。  \n\n另外，我们也提供`this.set()`的辅助方法来方便的为model改值，所以你还可以这样做：\n\n```js\nthis.set({\n  dataSource: res.data,\n  currentPage: res.currentPage,\n  totalItem: res.totalItem,\n  totalPage: res.totalPage,\n});\n```\n这里会使用mobx的`runInAction`来统一执行，从而保证UI渲染只执行一次。\n\n### 组件内触发\n\n如下，简单直接：\n```js\nimport { inject, observer } from 'vanex';\n\n@inject('applications')\n@observer\nclass Applications extends Component {\n    constructor(props, context) {\n        super(props, context);\n    }\n\n    clickHandler() {\n      this.props.applications.getList(); // 直接执行\n    }\n\n    render() {\n      return (\n        \u003cdiv onClick={::this.clickHandler}\u003e\u003c/div\u003e\n      );\n    }\n}\n```\n\n## 开发组件\n\n有时候，我们并不想执行页面渲染，而是用Vanex来开发一个组件，这时，还是可以使用`start` API，只要不传如`container`值，就会返回一个React Component。\n\n```js\nimport React from 'react';\nimport { render } from 'react-dom';\nimport App from './App';\n\n// load middlewares\nimport middlewares from './middlewares';\n\nimport {\n    start,\n} from 'vanex';\n\n// model\nimport user from './models/User';\nimport todos from './models/Todos';\n\n// relation\nimport relation from './relations';\n\n// 验证start返回一个组件\nconst MyComponent = start({\n    component: App,\n    models: {\n        user,\n        todos\n    },\n    middlewares,\n    relation\n});\n\nrender(\u003cMyComponent data={{a: 1}} /\u003e, document.querySelector('#root'));\n\n```\n\n\n## 特点\n\n- 简单易上手，开发效率高；\n- MVVM：Vanex实现了基于React的MVVM开发范式，简单直接，开发效率高；\n- 更改store数据：直接赋值；\n- 触发action：直接执行store的action；\n- 性能优化：自动做掉。\n\n具体例子参考[example](https://github.com/abell123456/vanex/tree/master/example).\n\n## 为什么基于mobx的开发范式更简单高效？\n\nMobx的实现思想和Vue几乎一样，所以其优点跟Vue也差不多：通过监听数据（对象、数组）的属性变化，可以通过直接在数据上更改就能触发UI的渲染，从而做到MVVM、响应式、上手成本低、开发效率高，在数据管理上需要再详细阐述下其区别。\n\nRedux是建议全局唯一Store的，多个Reducers也会在传递给react-redux之前被合并成一个root reducer，任何数据的更改（通过Reducer）都会通过这一个store来触发整个UI树的重新渲染，如果不做任何的性能优化（pureRender等），就算VD(Virtual Dom)有了再高的效率提升，当页面数据量、DOM数量大了，性能消耗也是非常大的。另外一点，Redux实现的对数据的管理是pull方式的，就是说其只能等待应用派发某个行为（Action），然后重新触发UI的渲染，而做不到对行为的可预期；Mobx则不一样，他是基于监听数据的属性变化来实现的，而且是多store的，对于任何的数据变更都是第一时间知道的，所以其实现方式是基于push的监听订阅模式而实现，这样，他就可以做到对数据的可预测以及细粒度的控制，甚至可以通过修改React组件生命周期的方式来减少性能的消耗，而无需使用者对这些细节关心。当然这一切肯定是有了mobx对组件做observe操作才能实现的，所以也就有了observer用的越多，应用性能越高的说法。\n\n## 加群交流\n\n![vanex使用交流群](https://github.com/abell123456/vanex/blob/master/img/IMG_1434.JPG)  \n\n或者加我微信联系加群：`13332922437`。\n\n## 感谢\n\nVanex的部分实现参考自MVVM框架：[mobx-roof](https://github.com/mobx-roof/mobx-roof)。\n\n## 落地\n\n- 1、[内容创作投放平台](https://kol.alibaba.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxcodebuild%2Fvanex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxcodebuild%2Fvanex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxcodebuild%2Fvanex/lists"}