项目存在问题

  • 代码量大,耦合严重
  • 业务开发分工不明确,开发人员要关心非业务的代码
  • 改代码时,可能会影响其他业务,牵一发动全身

优点

  • 架构更清晰,解耦
  • 业务分工明确,开发人员仅专注与自己的业务
  • 提高开发效率
  • 组件、业务独立更新版本,可回滚,持续集成

原则

  1. 单一的职责

    专注于解决一个单一的问题,独立的, 可复用的, 微小的 and 可测试的

  2. 组件命名

    • 有意义的: 不过于具体,也不过于抽象
    • 简短: 2 到 3 个单词
    • 具有可读性: 以便于沟通交流
    1. # bad: Menu Table
    2. # bad: UserCenterLeftPartOperateMenu
    3. # bad: Xmenu
    4. good: UserCenterMenu
  3. 组件表达式简单化

    过于复杂的判断逻辑抽离,不要在render中写臃肿的表达式,保持组件dom简洁,可读。
    bad:

    1. { this.state.hasQueue && this.state.hasQueue.length > 0 && this.state.hasQueue.indexOf(value.id) > -1 ? <a href={TJ.tourl('/directivity?orderId=' + value.id) } className="line-normal-btn fl maincolor mainborder">智能导引单</a> : null }
    2. {((value.examDate && !value.isLocalePay && (value.status == 2 || value.status == 1 || value.status == 0) && !value.isExport) && !value.isHidePrice) ?
    3. <a href="javascript:;" className="line-normal-btn" onClick={this.changeItems.bind(this, value)}>改项目</a>
    4. : null
    5. }

    good:

    1. // 是否可见智能导引单
    2. let isHasQueue = this.state.hasQueue && this.state.hasQueue.length > 0 && this.state.hasQueue.indexOf(value.id) > -1;
    3. // 是否可改期
    4. // 订单状态:...order status
    5. let canChangeItem = function(value) {
    6. return ((value.examDate && !value.isLocalePay && (value.status == 2 || value.status == 1 || value.status == 0) && !value.isExport) && !value.isHidePrice)
    7. }
    8. { isHasQueue && <a href={TJ.tourl('/directivity?orderId=' + value.id)} className="line-normal-btn fl maincolor mainborder">智能导引单</a> }
    9. { !!canChangeItem(value) ? <a href="javascript:;" className="line-normal-btn" onClick={this.changeItems.bind(this, value)}>改项目</a> }

    ps:

    1. {placeholder && <span className="fr text-grey linehight48">{placeholder}</span>}
    2. # 这种简写有问题,当placeholder0的时候,显示不正常; 需要 placeholder boolean 型。
    3. 转化为boolean型:{!!placeholder ...}
  4. 不使用难以理解的「魔鬼数字」

    bad:

    1. // 订单是否可改期
    2. let canChangeItem = function(value) {
    3. ((value.examDate && !value.isLocalePay &&
    4. # (value.status == 2 || value.status == 1 || value.status == 0)
    5. && !value.isExport) && !value.isHidePrice)
    6. }

    good:

    1. const ORDER_STU = {
    2. unpay: 0, // 未支付
    3. paid: 1, // 支付完成
    4. paying: 2 // 已预约
    5. }
    6. (value.status == ORDER_STU.paying || value.status == ORDER_STU.paid || value.status == ORDER_STU.unpay)
  5. 组件 props 原子化

    尽量只使用JavaScript 原始类型(字符串、数字、布尔值) 和 函数。尽量避免复杂的对象

    1. <!-- bad -->
    2. <range-slider :config="complexConfigObject"></range-slider>
    3. <!-- good -->
    4. <range-slider
    5. :values="[10, 20]"
    6. min="0"
    7. max="100"
    8. step="5"
    9. :on-slide="updateInputs"
    10. :on-end="updateResults">
    11. </range-slider>
    12. or
    13. let complexConfigObject = {
    14. .., ..
    15. .., ..
    16. }
    17. <range-slider
    18. {...complexConfigObject} >
    19. </range-slider>
  6. 验证组件的 props,由其对于ui组件

    • 提供默认值

    • 使用 type 属性校验类型

    • 使用 props 之前先检查该 prop 是否存在

      好处:保证你的组件永远是可用的(防御性)

      1. React.createClass({
      2. getDefaultProps()...
      3. propTypes: {
      4. hospitals: React.PropTypes.array.isRequired,
      5. ...
      6. // vue
      7. export default {
      8. props: {
      9. max: {
      10. type: Number, // 这里添加了数字类型的校验
      11. default() { return 10; },
      12. }
      13. ...
  7. 当前component 指向 this

    尽量不要这样:

    1. const self = this; // 一般情况没有必要这样声明
  8. 组件结构规范

    按照一定的习惯组织代码,使得组件便于理解

    react:

    1. //设置数据默认值
    2. getDefaultProps() {}
    3. propTypes: {}
    4. getInitialState() {}
    5. componentWillMount() {}
    6. componentDidMount() {}
    7. componentWillReceiveProps(nextProps) {}
    8. shouldComponentUpdate() {}
    9. componentWillUpdate() {}
    10. componentDidUpdate() {}
    11. componentWillUnmount() {}
    12. selfMethod()... //自定义方法
    13. render() ...

    vue:

    1. export default {
    2. // 不要忘记了 name 属性
    3. name: 'RangeSlider',
    4. // 组合其它组件
    5. extends: {},
    6. // 使用其它组件
    7. components: {},
    8. // 组件属性、变量
    9. props: {
    10. bar: {}, // 按字母顺序
    11. foo: {},
    12. fooBar: {},
    13. },
    14. // 变量
    15. data() {},
    16. computed: {},
    17. // 方法
    18. methods: {},
    19. watch: {},
    20. // 生命周期函数
    21. beforeCreate() {},
    22. mounted() {},
    23. };

    合理的 CSS 书写规则,如 BEM 或 oocss

    使用组件名作为样式作用域空间

    1. BEM:块(block)、元素(element)、修饰符(modifier
    2. <form class="search-form">
    3. <input type="text" class="search-form__username">
    4. <input type="password" class="search-form__password ml10">
    5. <button class="search-form__submit active"></button>
    6. <form>
  9. 事件命名规则

    驼峰命名:oneTwoThree

    动词 + 形容词 / 动词 + 名词

    原生事件传递:handleClick, handleChange …

    1. 编辑器

      统一缩进2格

      js中使用单引号,html中使用双引号

    2. 必要时,添加readme文件

      三种方式 ( react )

    3. 函数式定义的无状态组件(react v0.14)

    4. es5原生方式React.createClass定义的组件,目前我们系统使用方式

    5. es6形式的extends React.Component定义的组件

do it

第一步:划分组件

组件划分

第二步:编写静态版本

  1. render: function() {
  2. if (this.state.loading) {
  3. return null;
  4. }
  5. let headerProps = {
  6. gender: this.state.account.gender,
  7. name: this.state.account.name,
  8. idCard: this.state.account.idCard,
  9. employeeId: this.state.account.employeeId,
  10. cardCount: this.state.amounts.cardCount,
  11. balanceAmount: this.state.amounts.balanceAmount
  12. };
  13. let orderBadgeLabel = (this.state.amounts.unExamOrderCount && this.state.amounts.unExamOrderCount > 0) ? this.state.amounts.unExamOrderCount : '';
  14. let menuList1 = [
  15. {label: '我的订单', location: TJ.tourl('/orderlist'), ...},
  16. {label: '体检人管理', location: TJ.tourl('/healthermanage'), ...}
  17. ]
  18. let menuList2 = [
  19. {label: '个人信息', location: TJ.tourl('/personalinfo'), ...},
  20. {label: '修改密码', location: TJ.tourl('/updatepwd'), icon: 'icon-xiugaimima color-red'},
  21. ]
  22. let menuList3 = [
  23. {label: '了解更多', location: TJ.tourl('/guidance'), ...},
  24. {label: '意见反馈', location: TJ.tourl('/feedback'), ...},
  25. {label: '联系客服', location: 'tel://400-0185-800', ...}
  26. ]
  27. let rightBtn = (<a className="moremenu-icon iconfont icon-shouye" href={TJ.tourl('/welcome')}> </a>);
  28. return (
  29. <div className="container">
  30. <HeaderView title='个人中心' rightBtn={rightBtn}/>
  31. <UcHeaderView {...headerProps} />
  32. <UcBodyView menuList1={menuList1} menuList2={menuList2} menuList3={menuList3} />
  33. <UcFooterView isWeixin={this.state.wxbind} />
  34. </div>
  35. );
  36. }
  37. /*
  38. * 个人中心页面上部
  39. */
  40. var UcHeader = React.createClass({
  41. getDefaultProps() {
  42. return {
  43. gender: 0
  44. }
  45. },
  46. propTypes: {
  47. cardCount: React.PropTypes.number,
  48. balanceAmount: React.PropTypes.number
  49. },
  50. render: function() {
  51. let { name, gender, idCard, employeeId, cardCount, balanceAmount } = this.props;
  52. let accountingItems = [
  53. { label: '体检卡', amount: cardCount, location: '/usercentercard' },
  54. { label: '余额', amount: TJ.formatPrice(balanceAmount), location: '/cashaccount' }
  55. ];
  56. return (
  57. <div>
  58. <AccountInfoView name={name} gender={gender} idCard={idCard} employeeId={employeeId} />
  59. <AccountingView accountingItems={accountingItems} />
  60. </div>
  61. );
  62. }
  63. });
  64. /*
  65. * 菜单项
  66. * location 跳转路径
  67. * icon 图标className
  68. * badgeLabel 红色徽标显示内容
  69. * showArrow 是否显示右边箭头
  70. * placeholder 右边显示内容
  71. */
  72. var MenuView = React.createClass({
  73. getDefaultProps() {
  74. return {
  75. showArrow: true
  76. }
  77. },
  78. propTypes: {
  79. label: React.PropTypes.string.isRequired
  80. },
  81. render: function() {
  82. let {label, location, icon, badgeLabel, showArrow, placeholder} = this.props;
  83. return (
  84. <a href={location} className="common-link grey-bor" style={{padding: '0rem 0.2rem'}}>
  85. <i className={'iconfont ico-margin ' + icon} />
  86. <span className="text-dark">{label}</span>
  87. {showArrow ? <i className="next-link iconfont icon-xiangyou" /> : null}
  88. {badgeLabel ? <em className="morenum-icon fr" >{badgeLabel}</em> : null}
  89. {placeholder ? <span className="fr text-grey linehight48">{placeholder}</span> : null}
  90. </a>
  91. );
  92. }
  93. });

第三步:添加 状态 / 逻辑

  1. // 用户信息
  2. var AccountInfoView = React.createClass({
  3. // 1. 组件内部交互
  4. this.setState({showModal: true});
  5. render: function() {
  6. // 2. 对数据的处理,如果没有名字,显示身份证号,如果没有身份证,显示员工号
  7. let username = (this.props.name || this.props.idCard || this.props.employeeId);
  8. // 3. 变量的转化,简化DOM使用参数
  9. let imgUrl =
  10. (this.props.gender && this.props.gender == 1) ? '/app/base/bg/girl.png' : '/app/base/bg/boy.png';
  11. // 4. 添加事件
  12. shake(e) {
  13. let [obj, i, timer, d] = [$(e.target), 5, null, 5];
  14. clearInterval(timer);
  15. timer = setInterval(function(){
  16. obj.css({"position": "relative", "left": d + "px"});
  17. i--;
  18. d = -d;
  19. if (i === 0) {
  20. clearInterval(timer);
  21. }
  22. },50);
  23. },
  24. return (
  25. <div className="photo-bg mainbackground">
  26. <img src={imgUrl} onClick={this.shake} />
  27. <p>{username}</p>
  28. </div>
  29. );
  30. }
  31. });

第四步:通信

常用几种方式:props传递、pub-sub方式、flux/redux(vuex)

  1. props:

    1. <Parent>
    2. {/* 以属性的形式传入子组件 */}
    3. <Children handleClick={this.operate} />
    4. </Parent>
    5. {/* 子组件调用父组件传入的方法 */}
    6. <Children onClick={this.props.handleClick} />
  2. pub-sub:

    1. # pub:
    2. PubSub.publish('usercneter:testpubsub:success', 'bgred');
    3. # sub:
    4. componentWillMount() {
    5. this.testpubsubToken = PubSub.subscribe('usercneter:testpubsub:success', (msg, data) => {
    6. this.changeBg(data);
    7. });
    8. }
    9. # 注销发布
    10. componentWillUnmount() {
    11. PubSub.unsubscribe(this.testpubsubToken);
    12. }
    13. # key值规则
    14. PubSub.publish('usercneter:testpubsub:success');
    15. `模块:动作:状态`
  3. flux/redux

    利用全局状态管理(store),需要框架支持,目前C端还未使用。

    vuex 管理组件间状态和通信,运维管理系统(ops)、渠道商管理(crm-channel)中已使用。

属性类型验证

  1. React.createClass({
  2. propTypes: {
  3. // You can declare that a prop is a specific JS primitive. By default, these
  4. // are all optional.
  5. optionalArray: React.PropTypes.array,
  6. optionalBool: React.PropTypes.bool,
  7. optionalFunc: React.PropTypes.func,
  8. optionalNumber: React.PropTypes.number,
  9. optionalObject: React.PropTypes.object,
  10. optionalString: React.PropTypes.string,
  11. // Anything that can be rendered: numbers, strings, elements or an array
  12. // containing these types.
  13. optionalNode: React.PropTypes.node,
  14. // A React element.
  15. optionalElement: React.PropTypes.element,
  16. // You can also declare that a prop is an instance of a class. This uses
  17. // JS's instanceof operator.
  18. optionalMessage: React.PropTypes.instanceOf(Message),
  19. // You can ensure that your prop is limited to specific values by treating
  20. // it as an enum.
  21. optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
  22. // An object that could be one of many types
  23. optionalUnion: React.PropTypes.oneOfType([
  24. React.PropTypes.string,
  25. React.PropTypes.number,
  26. React.PropTypes.instanceOf(Message)
  27. ]),
  28. // An array of a certain type
  29. optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
  30. // An object with property values of a certain type
  31. optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
  32. // An object taking on a particular shape
  33. optionalObjectWithShape: React.PropTypes.shape({
  34. color: React.PropTypes.string,
  35. fontSize: React.PropTypes.number
  36. }),
  37. // You can chain any of the above with `isRequired` to make sure a warning
  38. // is shown if the prop isn't provided.
  39. requiredFunc: React.PropTypes.func.isRequired,
  40. // A value of any data type
  41. requiredAny: React.PropTypes.any.isRequired,
  42. // You can also specify a custom validator. It should return an Error
  43. // object if the validation fails. Don't `console.warn` or throw, as this
  44. // won't work inside `oneOfType`.
  45. customProp: function(props, propName, componentName) {
  46. if (!/matchme/.test(props[propName])) {
  47. return new Error('Validation failed!');
  48. }
  49. }
  50. },
  51. /* ... */
  52. });

Props与State对比

Props与State之间也是有很多交集的,譬如:
  • Props与State都是JS对象。
  • Props与State的值的改变都会触发界面的重新渲染。
  • Props与State都是确定性的,无副作用的,即在确定的Props或者State的值的情况下都会得出相同的界面。
差异:

Props顾名思义,更多的是作为Component的配置项存在。Props往往是由父元素指定并且传递给自己的子元素,不过自身往往不会去改变Props的值。另一方面,State在组件被挂载时才会被赋予一个默认值,而常常在与用户的交互中发生更改。往往一个组件独立地维护它的整个状态机,可以认为State是一个私有属性

Mixins:组件的继承

虽然组件的原则就是模块化,彼此之间相互独立,但是有时候不同的组件之间可能会共用一些功能,共享一部分代码。所以 React 提供了 mixins 这种方式来处理这种问题。Mixin 就是用来定义一些方法,使用这个 mixin 的组件能够自由的使用这些方法(就像在组件中定义的一样),所以 mixin 相当于组件的一个扩展,在 mixin 中也能定义“生命周期”方法。

  1. var SetIntervalMixin = {
  2. componentWillMount: function() {
  3. this.intervals = [];
  4. },
  5. setInterval: function() {
  6. this.intervals.push(setInterval.apply(null, arguments));
  7. },
  8. componentWillUnmount: function() {
  9. this.intervals.map(clearInterval);
  10. }
  11. };
  12. var TickTock = React.createClass({
  13. mixins: [SetIntervalMixin], // Use the mixin
  14. getInitialState: function() {
  15. return {seconds: 0};
  16. },
  17. componentDidMount: function() {
  18. this.setInterval(this.tick, 1000); // Call a method on the mixin
  19. },
  20. tick: function() {
  21. this.setState({seconds: this.state.seconds + 1});
  22. },
  23. render: function() {
  24. return (
  25. <p>
  26. React has been running for {this.state.seconds} seconds.
  27. </p>
  28. );
  29. }
  30. });
  31. React.render(
  32. <TickTock />,
  33. document.getElementById('example')
  34. );

React 的 mixins 的强大之处在于,如果一个组件使用了多个 mixins,其中几个 mixins 定义了相同的“生命周期方法”,这些方法会在组件相应的方法执行完之后按 mixins 指定的数组顺序执行。

ref:

https://segmentfault.com/a/1190000003748289

http://www.tuicool.com/articles/2yI3ea6

http://blog.csdn.net/shilu89757/article/details/42418283

https://zhuanlan.zhihu.com/p/25654116

文档更新时间: 2019-07-22 09:54