{"action":"created","issue":{"url":"https://api.github.com/repos/ecomfe/efe/issues/2","repository_url":"https://api.github.com/repos/ecomfe/efe","labels_url":"https://api.github.com/repos/ecomfe/efe/issues/2/labels{/name}","comments_url":"https://api.github.com/repos/ecomfe/efe/issues/2/comments","events_url":"https://api.github.com/repos/ecomfe/efe/issues/2/events","html_url":"https://github.com/ecomfe/efe/issues/2","id":161666776,"number":2,"title":"一种更纯粹的\b组件实现方式","user":{"login":"otakustay","id":639549,"avatar_url":"https://avatars.githubusercontent.com/u/639549?v=3","gravatar_id":"","url":"https://api.github.com/users/otakustay","html_url":"https://github.com/otakustay","followers_url":"https://api.github.com/users/otakustay/followers","following_url":"https://api.github.com/users/otakustay/following{/other_user}","gists_url":"https://api.github.com/users/otakustay/gists{/gist_id}","starred_url":"https://api.github.com/users/otakustay/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/otakustay/subscriptions","organizations_url":"https://api.github.com/users/otakustay/orgs","repos_url":"https://api.github.com/users/otakustay/repos","events_url":"https://api.github.com/users/otakustay/events{/privacy}","received_events_url":"https://api.github.com/users/otakustay/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":0,"created_at":"2016-06-22T12:18:34Z","updated_at":"2016-06-22T15:18:11Z","closed_at":null,"body":"还请 @errorrik 过目……\r\n\r\n---\r\n\r\nInspired by react, redux, mvvm and many many more...\r\n\r\n## 介绍\r\n\r\n一个组件分为“数据”和“事件”两部分，其中数据进一步分为“数据的声明”、“数据的初始值”和“当前数据”\r\n\r\n```js\r\nclass Component {\r\n    data,\r\n    dataTypes,\r\n    initialData,\r\n    events\r\n}\r\n```\r\n\r\n到这一步为止基本就是一个React和Vue的合体，区别在于下面。\r\n\r\n当组件实际渲染后，触发了一定事件时，会调用事件对应的函数，而这个函数是一个“纯函数”，它接受当前的数据，返回新的数据（Redux的`reducer`）：\r\n\r\n```js\r\nlet reduce = async (currentData, fire, ...eventArgs) => newData;\r\n```\r\n\r\n你说为什么偏偏是`currentData`和`fire`两个参数，因为一个组件的输入是数据，输出是事件，所以这相当于`input`和`output`。\r\n\r\n实际的流程是触发事件后，用函数计算出新的数据，把新的数据补丁到原有的数据上，进一步触发视图的更新。\r\n\r\n## 优势\r\n\r\n### 不需要Model的实现\r\n\r\n永别了，我自己写的`emc`……\r\n\r\n### 更加纯粹\r\n\r\n所有的事件处理函数都是纯函数，本身是可单独测着玩的。\r\n\r\n### 更强制地引入Immutable\r\n\r\n我不相再赘述Immutable的作用和优势……反正我认为在我的`diffy-update`的支持下更新Immutable数据并不是那么困难的事。\r\n\r\n### 对绑定同样友好\r\n\r\n绑定是发生在`data`之下的，并不受任何影响，且因为一个`reduce`返回的新对象可能包含多个属性的修改，相当于**默认实现了一定程度上`change`的合并**，不必在纠结所谓的合并不合并的问题。\r\n\r\n### 可组合复用\r\n\r\n这点很重要。\r\n\r\n在这个模式下，一个组件其实被明确地分为几个部分，除了“当前数据”之外，其它部分其实可以灵活的组合，我的一个组件可以写成这样：\r\n\r\n```js\r\nimport {set} from 'diffy-update';\r\nimport {Types} from 'san-ui/Types';\r\n\r\nexport let events = {\r\n    async click(data, fire) {\r\n        fire('click');\r\n    }\r\n\r\n    async duplicate(data) {\r\n        return set(data, 'text', data.text.repeat(2));\r\n    }\r\n};\r\n\r\nexport let dataTypes = {\r\n    text: Types.string\r\n};\r\n\r\nexport let initialData = {\r\n    text: ''\r\n};\r\n\r\nexport default class Label {\r\n    events = events;\r\n    dataTypes = dataTypes;\r\n    initialData = initialData;\r\n}\r\n```\r\n\r\n此后我有了自己的一个组件，虽然长得不一样，也有一堆逻辑不一样，明显地不能继承`Label`，但正好也要个`duplicate`的功能，正好点了以后要触发`click`事件，那就方便了：\r\n\r\n```js\r\nimport {events} from 'Label';\r\n\r\nexport default class BeautifulLady {\r\n    events = events;\r\n    dataTypes...\r\n    initialData...\r\n}\r\n```\r\n\r\n当然我们可以更细化，让`Label`变成3个文件：\r\n\r\n```js\r\n// label/events.js\r\nimport {set} from 'diffy-update';\r\n\r\nexport let click = async (data, fire) => fire('click');\r\n\r\nexport let duplicate = async data => set(data, 'text', data.text.repeat(2));\r\n\r\n\r\n// label/Label.js\r\nimport * as events from './events';\r\n\r\nexport default class Label {\r\n    events = events;\r\n    dataTypes...\r\n    initialData...    \r\n}\r\n```\r\n\r\n这样我可以只复用其中的`duplicate`部分，而不需要`click`，相当方便。\r\n\r\n当然我们更多的时候，面对的是数据结构不尽可同，但是逻辑相似的情况，这时就可以上高阶函数：\r\n\r\n```js\r\n// transformer.js\r\nexport let withMapping = mapping => reduce => async (data, fire) => {\r\n    let newData = await reduce(data, fire);\r\n    let mappedData = Object.entries(newData).reduce(\r\n        (result, [key, value]) => {\r\n            let mappedKey = mapping[key] || key;\r\n            return {[mappedKey]: value, ...result};\r\n        },\r\n        {}\r\n    );\r\n};\r\n\r\n// BeautifulLady.js\r\nimport {withMapping} from 'transformer';\r\nimport {duplicate} from 'label/events';\r\n\r\nlet events = {\r\n    duplicate: withMapping({text: name})(duplicate)\r\n};\r\n\r\nexport default class BeautifulLady {\r\n    events = events;\r\n    dataTypes...\r\n    initialData...\r\n}\r\n```\r\n\r\n以上仅仅是说明了`events`这一块如何通过组合来达到比继承更强大的复用性，其实`dataTypes`和`initialData`同样可以复用，但考虑到这只是普通的数据结构，并不特别适合去复用，就不在此展示。\r\n\r\n## 劣势\r\n\r\n很显然，这需要一定的技术素养才能掌握。\r\n\r\n过强的复用性可能让代码的组织变乱，各个组件间对`events`的依赖显得比较奇怪。\r\n\r\n`events`本身是组件内部的一种实现，这样放出去给人复用并不适合大部分情况，通常只是在共享逻辑但定制外观的时候才有用。"},"comment":{"url":"https://api.github.com/repos/ecomfe/efe/issues/comments/227777636","html_url":"https://github.com/ecomfe/efe/issues/2#issuecomment-227777636","issue_url":"https://api.github.com/repos/ecomfe/efe/issues/2","id":227777636,"user":{"login":"errorrik","id":2851010,"avatar_url":"https://avatars.githubusercontent.com/u/2851010?v=3","gravatar_id":"","url":"https://api.github.com/users/errorrik","html_url":"https://github.com/errorrik","followers_url":"https://api.github.com/users/errorrik/followers","following_url":"https://api.github.com/users/errorrik/following{/other_user}","gists_url":"https://api.github.com/users/errorrik/gists{/gist_id}","starred_url":"https://api.github.com/users/errorrik/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/errorrik/subscriptions","organizations_url":"https://api.github.com/users/errorrik/orgs","repos_url":"https://api.github.com/users/errorrik/repos","events_url":"https://api.github.com/users/errorrik/events{/privacy}","received_events_url":"https://api.github.com/users/errorrik/received_events","type":"User","site_admin":false},"created_at":"2016-06-22T15:18:11Z","updated_at":"2016-06-22T15:18:11Z","body":"看起来除了一些细节可能要斟酌，复用是很容易。我唯一担心的是，如果采用这个组件构架，组件开发者是否能贯彻这个思路不玩脱"}}
{"actor":{"display_login":"errorrik"}}