为什么我们的代码难以维护草稿

有没有想过这个问题,为什么我们的代码难以维护,为什么我们偏向于开发全新的系统,却不愿意改造现有系统,甚至是我们自己开发的系统。这个想法突然从我脑钻了出来,然后我思考了大概十分钟。

我认为我们系统难以维护的原因主要有三个:

一. 系统抽象级别不够

其实很多时候我们太急于实现功能,导致代码太过于具体,没有得到有效的抽象或者抽象层次不够。抽象层次不够会导致代码难以复用,难以修改,难以阅读。

1.1 代码封装层次

比如我要实现一个actionSheet,根据系统状态不同展示不同的actionSheet,并且根据用户最终选择的action,执行响应的操作。 这是一个在移动端非常常见的交互行为。

看一下我们不同的抽象级别的实现:

function handleEvent(e) {
   // 使用者只需要构造mapper即可,无需关心其他逻辑
   const buttonMapper = {
      "2": [
        {
          text: "action one",onClick: this.onButtonClick.bind(this,employeeStatus,username)
        },{
          text: "action two",onClick: this.props.leaveJob
        }
      ],"3": [
        {
          text: "action three",onClick: this.props.leaveJob
        }
      ]
      ]
    };
   return showActionSheet(buttonMapper,e.target.value)
}


// 系统抽象(将actionSheet抽象)
function showActionSheet(buttonMapper,idx = 0, title = "",cancelButton = "取消") {

    const actions = buttonMapper[idx];
    const BUTTONS = actions.map(q => q.text);

    actionSheet({
      title,cancelButton,//取消按钮文本
      otherButtons: BUTTONS,onSuccess: ({ buttonIndex }) => {
        actions[buttonIndex].onClick();
      }
    });
}

如果代码抽象层次太低,会导致代码很难复用,难以阅读和维护。 比如下面的代码:

function handleEvent(e) {
      const BUTTONS = isTrue
        ? ["action one","action two"]
        : ["action three"];
        actionSheet({
        title:'title',cancelButton: "取消",//取消按钮文本
        otherButtons: BUTTONS,onSuccess: ({ buttonIndex }) => {
          if (isTrue) {
            if (buttonIndex === 0) {
              // xxxxxx

             
            } else if (buttonIndex === 1) {
               // xxxxx
            }
          } else {
            if (buttonIndex === 0) {
             // xxxxx
            }
          }
        }
      });
}

1.2 业务思维层次

比如有这样一个需求。 后台给定一个JSON,结构如下:

{
  history: [{},{}],baseInfo: {name: '张三',dept: '工程部'},bonus: {
    a: '100',b: '210'
  }
}

需要将history以 timeline形式展示

baseInfo按照form表单形式全部展示,bonus按照box形式展示。

这种需求非常常见。

当时我做这个需求的时候想法是这样的:

1. 拆分组件。

我需要写一个timeLine组件。使用方法大概是这样:

<Timeline>
  <Timeline.Item>创建服务现场 2015-09-01</Timeline.Item>
  <Timeline.Item>初步排除网络异常 2015-09-01</Timeline.Item>
  <Timeline.Item>技术测试异常 2015-09-01</Timeline.Item>
  <Timeline.Item>网络异常正在修复 2015-09-01</Timeline.Item>
</Timeline>

我还需要一个form组件,使用方式大概是这样的:

const mapper = {
  name: "姓名",dept: "部门",};

<TextForm data={baseInfo} labelMapper={mapper} />

我还需要一个box组件,用法如下:

<Box cols={4}>
          <BoxItem
            text={d}
            suffix="元"
            linkText="贡献奖"
            ceiling={9999}
          />
          ...
          <BoxItem
            suffix="元"
            text={c}
            linkText="全勤奖"
            ceiling={9999}
          />
        </Box>

2. 在容器组件中拼装。

以timeline为例。

<TimeLine>
              {history.map((item) => {       
                return (
                  <TimeLineItem
                    key={item.key}     
                    date={item.effectTime || ""}
                    color={item.key > 0 ? "gray" : "blue"}
                  >
                    {item.title}
                  </TimeLineItem>
                );
              })}
            </TimeLine>

后期这种抽象方式还是不够,还可以进一步抽象,经过进一步思考,我采用如下方式抽象。

以timeLine实现为例:

const renderItem = item = <Timeline.Item key={item.key}            
   date={item.effectTime || ""}
   color={item.key > 0 ? "gray" : "blue"}
>
{item.title}
</ Timeline.Item>

const renderList = ({children}) = <Timeline>
{children}
</ Timeline>

const renderTimeLine = compose(renderList,map(renderItem))

renderTimeLine(data.history)

本质上渲染一个timeline组件由两部分组成,渲染外层wrapper,渲染里层items。 渲染items又可以看作渲染items执行n遍。 于是问题简化为 1.生成wrapper 2. 生成item。 经过这样抽象,你会发现上面例子的baseInfo和 bonus 其实也可以抽象为上面的“模型”。

二. 系统状态组织混乱

其实将系统抽象为状态,从状态映射视图也是对业务进行的抽象。有一个名词叫有限状态机,其实系统中的状态通常都是有限的,系统总是在有限状态中的一个节点。也就是说系统只是状态在时间节点上的分布。前端有很多状态管理容器,有名的有redux, mobx, vuex,statex 等。拿redux来讲,redux 中最核心的reducer, 其实就是reduce,只不过reduce 遍历的是给定的list。 而redux 遍历的是随时间变化的action list。

系统中状态混乱,彼此之间关联是造成代码难以维护的另一个重要原因。如何组织好系统状态是重要工作,我们不可能依靠某一个工具就可以很好组织我们的app state。状态管理有很多问题需要解决,比如状态如何组织,如何共享,如何保证不污染, 如何优雅订阅状态变更,如何优雅地修改状态等等。

有这么多问题,我们来看下redux(包括react-redux)对上面这些问题做了哪些努力。

1. 状态如何组织

redux是单一数据源,对于业务复杂的,可以写多个reducers,然后combine reducers。

2. 如何共享

redux每次store改变, 都会导致provider 中store变更,从而导致子组件刷新。然后子组件订阅单一数据源的部分key。

3.如何优雅订阅状态变更

如上

4. 如何保证状态不相互污染。

redux通过immutable方式修改state。

5.如何优雅地修改状态

redux 通过diapatch action 修改 state。 dispatch一个action,相当于往actions数组里面push一个action。然后reduce 当前应用状态的state。 通过actions 就可以还原,回退,从而还原“案发现场”

上面这些只是说了redux对状态管理做的努力,再好的工具,如果使用者本身技能不够好,也会把工具用得一团糟。对于使用者来说,一定要明白引入工具解决了什么问题,这样才可以更好的使用工具。对于app state的管理,并不是说引入了redux,就要将应用全部state都放在redux中管理。另外也不是所有应用都适合状态管理框架(应用复杂度要和框架复杂度匹配)。因此这就需要我们对app state有一个完整的理解,恰当地管理app state。一般而言,app state分为两部分,一部分是需要共享的状态,另一部分是组件独享的状态。如何区分比较简单,关键是使用者有去区分的”意识“。 而redux做状态共享非常方便,但是独享的状态使用redux就没必要了。对于完全不需要共享的状态,可以维护在上层container组件中,将这些脏活累活讲给container组件,然后将独享状态分发,保证下层组件的**纯粹性**。

不管是独享还是共享希望都可以从上面提到的几点去思考。

> 在这里,并不其他组件没有使用当前组件状态就不是共享状态,这需要你对应用未来的理解。因为可能出现这个状态本身就应该是共享的,只是目前暂时不共享的情况。

三. 对于非自己开发的功能不熟悉,不明白功能的具体业务场景。

对于这种,从代码 层次上是无法缓解的。那么就必须从文档和注释上下功夫。文档的话我觉得对于一个需求应该有完善的产品文档,UI设计文档和开发文档。 对于一些业务最好可以给一个产品文档和UI设计文档的链接。 因此推荐将业务讨论的过程通过外部文档记录,并将记录过程于文档结合,方便相关人员查看。

// prd: http://www.xxx.com/1233423x/index.html
// sketch: http://www.xxx.com/1233423x/index.html

class PageA {
  xxxxxx
}

适当增加注释,还原当初业务场景:

function renderHistory() {
    // 由于历史遗留,需要处理旧数据,因此这里需要做兼容处理
    // xxxx
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom