述职分享 – 刘睿敏
主要工作成果
DDP:react/redux-thunk/koa(ssr)
- 是什么:需求管理平台(需求的创建 -> 评审 -> 开发
- 用户:产品 / 运营 / 技术 / 大佬 everbody
-
价值:
- 解决需求来源不统一(lean/wiki/ 运营直接 d -chat 等等方式提需求等
- 出现问题,快速准确定责(无法快速且准确的确定需求来源方和需求 owner
- http://ddp-test.intra.xiaojuk…
把脉:react/typescript/mobx
- 是什么:滴滴云端诊断分析服务平台,帮助快速发现与定位问题
- 用户:客服 /RD
- 价值:其他业务方把原始的 trace 数据接入到把脉,把脉在原始数据的基础上提供界面,帮助快速发现与定位问题
- http://bamai.xiaojukeji.com/n…
主要工作分享:ddp 前端配置化方案
配置化背景:
一开始 ddp 是一个老老实实的应用,只有网约车一条业务线在用,很多逻辑都硬编码在前端,随后 ERP 想要接入,接入 erp 时,就发现很多问题!!!
- 业务线有不同的业务需求
解决方案:前端硬编码 / 大量的 if…else… 逻辑,造成代码冗余,难以维护,可读性查
不同业务方向下,又有不用角色,对应的文案 / 字段 / 字段类型都不同
const isERP = [3,4].includes(Number(sponsor))
const options = detail.type == 1?STATUS[5]:STATUS[detail.sponsor];
tatusOptions={options || STATUS[2] || []}
同一个字段,在不同页面,fetch 需要获取展示不一样的数据
const isShowAllPmTeams = window.location.pathname.indexOf('search') !== -1 || window.location.pathname.indexOf('mine') !== -1
pmTeamsOptions = (isShowAllPmTeams ? searchPmTeamList : pmTeamList) || []
设计原则:
-
数据驱动页面
- 页面 json 配置化,组件布局和组件属性均为数据的一部分
- 写通用的配置化逻辑,在加载页面的时候拉取配置数据,动态生成页面
-
组件化
-
组件父子化:便于定制和扩展
- 父组件本质是一个容器组件(container)。在内部定义了渲染出子组件排列的规则。同时也承担了通用(子组件的表现)的规则(比如子组件布局 & 数据处理 & 报错 & 提示信息处理等)
- 在父组件中需要定义 component Map 映射,实现一套组件映射关系。
- 子组件是纯 component, 没有额外的适配逻辑。便于普通 react 组件接入
-
组件和页面剥离,功能解耦
- 可复用的基础组件库:快速编辑组件库 / 表单组件库(相似的 props)
- 业务强耦合的业务组件库:便于页面布局(差异性)
-
-
功能插件化
- 页面功能都 基于事件。页面整个设计模式用了监听者模式。把各种行为操作都通过事件。在组件中定义。然后在页面中监听并补充业务逻辑。
- 通用的功能默认内置到基础插件,注入默认功能,同时可插拔
// change 的时候对外暴露 change 事件。同时暴露改子组件相关属性。到页面的部分去处理业务逻辑
-
组件和页面功能易于扩展
- 组件扩展。通过增加子组件,或者包装子组件 HOC
- 页面扩展。通过增加插件,实现特定场景的业务功能
在可配置页面里面,我认为要解决的是三个核心问题:
-
页面布局和样式
布局:
- 整个页面被认为是一个树形结构,由不同的组件构成
- 组件由组件构成,它们是递归构建的,直到最小的组件,这一类的组件逻辑上将无法再被拆分
样式:
-
绝不配置任何有关样式的信息(同一种类型的组件,都具有同样的样式)
- 配置样式会带来配置的急剧复杂化(一个组件的样式,由很多控制选项,而且不同组件之间还会相互影响)
- 配置样式导致样式修改调整都十分困难(相互影响)
-
问题:有两个组件,它们除了样式不同以外,都一样,那么怎么办?(实例:标题和内容的快速编辑)
- 定义一种新的组件?组件类型膨胀的问题 / 无法复用,除了 bug 修两个地方
- 借鉴 antd,配置每个组件的主题(normal, bold, red)/(big/small/middle)
- 页面内容——也就是数据
限制
- 不支持复杂布局
- 不支持复杂交互
有点
- 释放前端资源,终于不用切砖了!
- 节省测试资源,接入新的业务的时候,基本上就是看看配置对不对
场景一:
A 模块使用了 data1, B 模块使用了 data2;A B 模块可以修改对应的 data;这两份 data 结构上不同,但是存在业务上的联系:当 data1 更新后需要 data2 更新;data2 更新同样需要 data1 同步;对应后端的两个不同的 API。
我们整理一下
- A B 使用两份存在联系的 data
- 其中一个更新需要另一个更新
- 两份 data 对应不同的 API 接口
- A B 对应两个不同的 tab 且可能同时存在
方案一
- 当其中 B 数据因操作发生更新时,在页面 /action 中刷新 A 模块的数据
class B extends React.Component {handleUpdateData = () => {const { dispatch, queryA, queryB} = props
dispatch(queryB())
dispatch(queryA())
}
}
- 缺点:
在模块 B 内调用模块 A 的更新逻辑;但这样逻辑就耦合了,我在模块 A 调用模块 B 方法 在模块 B 调用模块 A 的方法;但很有可能这两个模块是没有其他交互的。这违反了低耦合高内聚的原则
而且书写 redux 的一个原则就是 不要调用(dispatch)其他模块的 action
方案二:发布 - 订阅事件系统 pubsub.js
模块 B – 使用 PubSub.publish 发送事件
模块 A – 订阅事件 type
通过事件系统做到了两个模块之间的解耦,作为事件发布方只管发布自己的事件。两个模块在事件系统唯一的联系就是事先定义好事件的 type
class B extends React.Component {handleUpdateData = () => {PubSub.publish('queryA',null);
}
}
class A extends React.Component {componentDidMount(){this.pubsub_token = PubSub.subscribe('queryA', function (topic,message) {dispatch(queryA())
}.bind(this));
}
componentWillUnmount(){PubSub.unsubscribe(this.pubsub_token);
}
}
场景二:
UI 上属于模块的状态数据,依赖子模块的接口返回
方案:
const mergeInLayout = (model, entities) => ({
type: types.MERGE_IN_LAYOUT,
model: model,
entities: entities
})
dispatch(mergeInLayout('cross-domain-review', { status: result.data.reviewStateName}));
case types.MERGE_IN_LAYOUT:
const data = state.modulesDetail[action.model] ?
Object.assign({}, state.modulesDetail[action.model], action.entities)
: action.entities
return Object.assign({}, state, {modulesDetail: Object.assign({}, state.modulesDetail, {[action.model]: data }),
});
// immutable