{"id":24112501,"url":"https://github.com/homkai/deef","last_synced_at":"2025-10-26T10:47:57.060Z","repository":{"id":57211757,"uuid":"72235607","full_name":"homkai/deef","owner":"homkai","description":"基于redux、react函数式组件，简单、健壮、强代码组织的框架","archived":false,"fork":false,"pushed_at":"2018-07-27T08:49:17.000Z","size":286,"stargazers_count":42,"open_issues_count":0,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-12-16T04:31:09.296Z","etag":null,"topics":["best-practice","functional","react","redux","stateless"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/homkai.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-10-28T19:21:01.000Z","updated_at":"2021-06-20T06:57:09.000Z","dependencies_parsed_at":"2022-09-07T06:50:59.722Z","dependency_job_id":null,"html_url":"https://github.com/homkai/deef","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/homkai%2Fdeef","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homkai%2Fdeef/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homkai%2Fdeef/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homkai%2Fdeef/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/homkai","download_url":"https://codeload.github.com/homkai/deef/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233435905,"owners_count":18675954,"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":["best-practice","functional","react","redux","stateless"],"created_at":"2025-01-11T03:32:05.282Z","updated_at":"2025-10-26T10:47:56.986Z","avatar_url":"https://github.com/homkai.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# deef\n基于redux、react函数式组件，简单、健壮、强代码组织的框架\n\ndeef从“响应一个个用户事件”出发，进而抽象成状态数据及数据流转，最后表现成UI渲染，达成交互\n\n\n## 代码分层：\n状态决定展现，交互就是改状态：model state =\u003e UI render =\u003e callback handler =\u003e model reducer\n- model：定义状态和改状态的接口\n- getUIState：从model中取UI展现依赖的状态\n- UI：函数式组件，渲染界面并暴露出用户交互的callbacks\n- callbacks：响应处理一个个用户事件\n- *connect(getUIState, callbacks)(UI)：打包得到一个可以支持具体业务的组件*\n\n\n## 特点\n- 概念少，逻辑清晰统一，代码组织约束性强，对新人友好\n- 代码颗粒度更小，函数式编程，不使用this，复用组合更灵活\n- 相比class component，UI组件就是单纯的render逻辑\n    - 没有生命周期、组件内部state对数据流的干扰，交互响应流-\u003e状态数据流-\u003eUI渲染流是一一对应的\n    - 纯函数，没有this上下文依赖，不易出错，方便复用组合\n    - 极大地与react解耦\n\n\n### 代码组织强约束\n- 分层清晰，职责分明，数据类型约束性强\n- 限制了UI中拿不到dispatch，dispatch只可能在callbacks中使用\n- callbacks屏蔽了this，支持命名规范检查，如限制必须onXXX\n\n\n## Demo\n\n```bash\n$ cd examples/xxx\n$ npm install\n$ npm start\n```\nhttp://location:8881\n\n参见 examples文件夹\n\n## Getting Started\n```js\nimport deef from 'deef';\n\n// 1. Create app\nconst app = deef();\n\n// 2. Register models\nconst model = {};\napp.model(model);\n\n// 3. Connect components with state\nconst App = app.connect(getUIState, callbacks)(UI);\n\n// 4. Start app, onRendered在App渲染后触发，可以理解为入口callback\napp.start('#root', App, onRendered);\n```\n*app.connect在业务代码里非常常用，推荐将const app = deef()等逻辑放到单独一个模块，并配置webpack alias以方便引用，可参考examples*\n\n## Usages\n\n### model\n```js\nconst model = {\n    // namespace是区别不同model的唯一标识\n    namespace: 'count',\n    // 定义这个model的状态数据\n    state: {\n        num: 0\n    },\n    // 定义改状态的接口\n    reducers: {\n        add(state, action) {\n            // reducers里的function是纯函数，输入当前state和action，输出nextState\n            // action是callback handler里dispatch的，action要求{type: 'modelNamespace/reducerName', payload: xxx}的格式\n            // 比如这里action是{type: 'count/add', payload: 1}\n            return {\n                ...state,\n                num: state.num + action.payload\n            };\n        }\n    }\n};\n```\n*redux的约定：在任何地方都不可以通过引用直接改当前状态，如state.num=1是不对的*\n\n### UI\n```js\nconst UI = ({num, tab, ...callbacks}) =\u003e {\n    // 第一个参数是外部传入的props，包括展现依赖的state和响应交互的callbacks，要求通过es6解构的方式直观取出依赖的状态，把callbacks放到后面，如果callbacks少的话，就直接摆出来，如果大于两个的话，要求使用\"...callbacks\"的方式，将callbacks整合，然后再解构这个callbacks。\n    const {onAdd, onSwitchTab} = callbacks;\n    // 拿到一个UI组件，看前两行就直观知道该UI依赖的所有的state和callbacks。\n\n    return (\u003cdiv\u003e\n        \u003ch1\u003e{num}\u003c/h1\u003e\n        \u003cbutton onClick={onAdd}\u003e\u003c/button\u003e\n    \u003cdiv\u003e);\n};\n```\n*UI是纯函数组件（stateless functional component），不可以直接处理交互，要通过callbacks暴露出去*\n\n### getUIState 根据model计算UI依赖的state\n```js\nconst getUIState = (store, ownProps) =\u003e {\n    // store是所有model的状态汇聚成的store，是plain object，第一层是model的namespace\n    // ownProps是引用组件时显式传入的props，如\u003cUI stateX=\"test\" /\u003e中的stateX\n    // 如果UI依赖的stateX需要根据model里定义的stateY、stateZ等计算所得，且该计算逻辑较为复杂，或者这个计算逻辑是可复用的，需将该逻辑放到Component/selector.js\n    return {\n        // 要求return一个plain object，会注入到UI的props\n        num: store.count.num,\n        // 可以整合配置里的一些常量进去\n        ...config.VALIDATION_RULES\n    }\n};\n```\n\n### callbacks 响应一个个用户事件来处理交互\n```js\nconst callbacks = {\n    // 这些callbacks会注入到UI 命名必须是onXXX的形式\n    onAdd({dispatch, getState}, event) {\n        // 固定传入第一个参数{dispatch, getState}\n        // dispatch方法传入action{type: 'model/reducer', payload: {}}来调用model的reducer改变model中的状态\n        // getState().modelNamespace即可拿到某一model的状态\n        // event是UI组件调用时传入的第一个参数，有更多的参数，依次往后排\n        dispatch({type: 'count/add', payload: event.target.value});\n    },\n    // 可以把一些公共的callbacks抽出来，放到Component/handler.js里，方便多处复用\n    ...commonCallbacks\n};\n```\n\n### connect 连接model与UI，返回一个支持具体业务的组件\n```js\nrequire('app').connect(getUIState, callbacks)(UI)\n```\n\n## 编码规范\n### 目录结构\n- ComponentName 首字母大写的文件夹\n    - index.js // connect\n    - UI.js 或 UI/(index.js+Section.ui.js)\n    - style.less与UI.js或UI/index.js同层级，直接在UI中引入\n    - config.js 该层级及以下公共配置\n    - handler // 可复用的callback handler，或者暴露给外部调用的方法\n    - selector // 可复用的一部分getUIState逻辑\n    - common // 该层级及以下公共的东西\n    - components 下级组件\n\n### UI\n- 使用纯函数，保持简单纯粹的渲染逻辑\n- 从props中显式列出所有state和callbacks，不可在业务中使用props.xxx\n- 最先解构ownProps，再解构state，再解构callbacks，callbacks比较多时，单独解构callbacks\n- 渲染dom逻辑外，仅可出现整理下层组件props的逻辑，杜绝在UI中定义callback，再简单的callback也要放到connect\n- callback依赖ownProps时，使用_.partial注入\n- UI中不可直接使用config的值，必须从props传入\n- 当render的逻辑比较多时，分成多部分，并放到UI文件夹，其他部分命名为Section.ui.js，并在index.js整合\n- style.less直接在UI中引入，样式文件尽可能独立，不要包含下层组件的样式\n\n### getUIState\n- 可复用的从model中取具体业务含义的值的逻辑放到selector.js中去，export的每一项是function getXXX()\n\n### callbacks\n- plan object，不可使用this，每一项是onXXX({dispatch, getState}, …args){}\n- 可复用的callback handler，或者暴露给外部调用的方法应放到handler.js中，export的每一项是function({dispatch, getState}, …args)，标识符为动词短语；此外可export.callbacks = {...};\n\n## 性能\n- 基于[react-redux-hk](https://github.com/homkai/react-redux-hk)，自动分析getUIState依赖的state，依赖的state没有改变时，不会重新计算getUIState，不会触发UI的re-render\n\n\n## 配套\n- [deef-router](https://github.com/homkai/deef-router)，参考examples/todomvc\n- [deef-plugin-connect-with-context](https://github.com/homkai/deef-plugin-connect-with-context)，deef plugin 基于deef形式构建大型公共业务组件\n\n## 使用\n- [百度信息流推广平台]","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhomkai%2Fdeef","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhomkai%2Fdeef","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhomkai%2Fdeef/lists"}