React/Vue, ¿qué brecha de calidad de código es mayor en el mismo nivel?

prefacio

Vi un artículo hace unos días, ¿ sabes por qué You Yuxi renunció a la propuesta de azúcar sintáctica de $? - Nuggets (juejin.cn) , el área de comentarios analiza la calidad del código escrito por los desarrolladores de Vue de diferentes niveles, y la calidad del código escrito es muy diferente. Los desarrolladores de React piensan que el código de React es aún más desigual. De hecho, yo también lo creo, ya que me hice cargo del código fuente de casi 100 millones...

imagen.png

Entonces, cómo escribir el código de reacción, sin mencionar lo elegante que es la escritura, al menos debería ser lo suficientemente práctico, no dejes que la gente quiera refactorizar después de verlo, la mayoría del uso doméstico y como biblioteca de componentes, así que hazlo el análisis en react+antd

forma

En los proyectos en segundo plano, generalmente no son más que formularios, formularios, cuadros emergentes y otras operaciones. La mayor diferencia es la forma dinámica del formulario.

<Form
  className="addPersonnelBox"
  form={form}
  onFinish={values => {
    saveAdd(values)
  }}
  onFinishFailed={values => {
    console.log('values', values)
  }}
>
  <div className="addList">
    <div className="addListTitle">
      <span>*</span>照片
    </div>
    <div className="addListContent">
      <Form.Item className="file" name="photo" rules={[{ required: true, message: '请上传照片' }]}>
        <div className="file-content">
          <div className="fileImg">
            {imgList.map((item, i) => {
              return (
                <div key={i} className="img-box">
                  <img
                    onClick={() => {
                      setImgIsVisible(true)
                    }}
                    key={i}
                    src={imgUrlHandle(item.fileUrl)}
                    alt="success"
                  />

                  <div
                    className="clear-img"
                    onClick={() => {
                      const index = imgList.findIndex(k => {
                        return k.uid === item?.uid || k.id === item.id
                      })
                      if (index > -1) {
                        const newImgList = imgList
                        newImgList.splice(index, 1)
                        setImgList([...newImgList])
                      }
                    }}
                  >
                    <CloseCircleOutlined />
                  </div>
                </div>
              )
            })}
          </div>
          <div
            className="file-tip"
            style={{
              marginLeft: imgList.length ? '24px' : ''
            }}
          >
            <Upload {...props}>
              {imgList.length < 1 && (
                <Button className="file-button">
                  上传文件
                  <UploadOutlined />
                </Button>
              )}
            </Upload>
            <p className="file-text">最多可以上传1张,单张大小不超过2M</p>
          </div>
        </div>
      </Form.Item>
    </div>
  </div>
  <div className="addList">
    <div className="addListTitle">
      <span>*</span>姓名
    </div>
    <div className="addListContent">
      <Form.Item name="userName" rules={[{ required: true, message: '请输入姓名' }]}>
        <Select
          onChange={ChangePeople}
          style={{ width: '400px' }}
          placeholder={!userArr.length ? '暂无业主信息' : '请选择业主'}
        >
          {userArr.length && userArr.map(item => <Option key={item.userName}>{item.userName}</Option>)}
        </Select>
      </Form.Item>
    </div>
  </div>
  <div className="addList">
    <div className="addListTitle">
      <span>*</span>职务
    </div>
    <div className="addListContent">
      <Form.Item name="postTypeId" rules={[{ required: true, message: '请输入职位' }]}>
        <Select style={{ width: '400px' }} placeholder={!postSource.length ? '暂无职务信息' : '请选择职务'}>
          {postSource.length && postSource.map(item => <Option key={item.id}>{item.name}</Option>)}
        </Select>
      </Form.Item>
    </div>
  </div>
// 省略大段代码
  <div className="fnBox">
    <Button className="closeBtn" onClick={() => setVisibleVote(false)}>
      取消
    </Button>
    <Button className="confirmBtn" htmlType="submit">
      确定
    </Button>
  </div>
</Form>

imagen.png

Este es el código real en el proyecto, no hablemos del código en la lógica, solo mirar el dom y el diseño es un dolor de cabeza, analicémoslo juntos

estilo de diseño

el diseño tiene horizontal |  vertical |  inlinesu mayor diferencia es que la pantalla más externa es flexible cuando está en línea, y las otras dos son bloques. La diferencia entre horizontal y vertical es que la dirección flexible del cuadro flexible de FormItem es diferente

  1. Elimine todos los classNames y divs, y el diseño se unifica con horizontal, labelCol y wrapperCol para configurar. labelCol/wrapperCol se puede escribir de manera receptiva: span: number, o se puede diseñar manualmente {style:{}}. Si desea dos columnas, agregue Row y Col a la capa exterior para configurar los atributos correspondientes y listo.
  2. Use la etiqueta directamente para el nombre del formulario. Si desea que la etiqueta esté alineada a la derecha, establezca el ancho y configure labelAlign: right (predeterminado)
  3. Se requiere verificación, escriba requerido o reglas en el elemento de formulario, vea más documentación api. (La estrella roja escrita a mano está realmente sin palabras)
  4. Hay ejemplos oficiales de carga de avatares, ¿por qué no usarlos?

imagen.png

forma dinámica

Conocimiento previo: FormItem transmitirá valor de forma transparente, onChange o usar valuePropName/ triggerconfigurar otros nombres de campo a los subcomponentes

imagen.png

在某些情况下在FormItem下的组件上写onChange是没有问题的,熟悉antd或看过源码的应该知道在Input上写了onChange和透传的onChange都会运行,并不会被覆盖。但是一般情况下是不需要的,见过很多代码喜欢在FormItem下的Input等组件加onChange,他们写onChange是为了动态表单,先onChange改变useState的值 然后用state去动态render,这种方式是最拉的如下图。除了用Form.useWatch,还有属性dependencies、shouldUpdate实现一般简易动态表单。Form.List和 @formily/react也可以不同的实现复杂动态表单

imagen.png

imagen.png

组件封装

很多组件封装都是为了在表单中使用,但不要将FormItem封到组件中,会限制灵活性和维护性,上面示例的房号组件如下:

imagen.png

// 上面的逻辑先不看,从dom分析下
return (
    <Form.Item
      label="房号"
      name="houseId"
      {...props}
      rules={[
        {
          required,
          message: '请输入'
        }
      ]}
    >
      <div
        className="flex"
        style={{
          display: 'flex'
        }}
      >
        <Form.Item noStyle name="number">
          <Select disabled={disabled} placeholder='选择楼栋' onSelect={e => handleSelect(e)}>
            {HouseUser.map(item => (
              <Option key={item.number}>{item.number}</Option>
            ))}
          </Select>
        </Form.Item>
        <Form.Item name="buildingId" noStyle>
          <Select disabled={disabled} placeholder='选择单元' onSelect={e => handleUnit(e)}>
            {unitArr.map(item => (
              <Option key={item.buildingId}>{item.unit}</Option>
            ))}
          </Select>
        </Form.Item>
        <Form.Item disabled={disabled} name="floor" noStyle>
          <Select placeholder='选择楼层' onSelect={e => onSelect(e)}>
            {floorArr && floorArr.map(item => <Option key={item}>{item}</Option>)}
          </Select>
        </Form.Item>
        <Form.Item disabled={disabled} name="houseId" noStyle>
          <Select placeholder='选择房号' allowClear>
            {houseIdArr && houseIdArr.map(item => <Option key={item.houseId}>{item.houseNo}</Option>)}
          </Select>
        </Form.Item>
      </div>
    </Form.Item>
  )

它的逻辑是挂载完成请求一个楼栋的接口(HouseUser),选择楼栋后请求单元(unitArr),之后依次请求到最后并且要传四个字段,不仅是维护困难,交互体验也差。说说是大概应该怎么改

1.跟后端沟通,很多时候不要他说怎么返数据结构就怎么做,先根据数据量确定能否全量返树形结构(直接用级联,只需要一个字段传数组过去),如果数据量较大也可以用级联的动态加载选项。

2.再根据项目中使用的频次及其他分析,考虑是将房号组件单独封装(将api也封装,使用时不必再写请求数据),还是将请求api手动传

另外FormItem传下来的value onChange一定要用到组件上才能保证自动回显和自动form.setFieldValue()能成功,其他的属性可以通过props透传下去

imagen.png

提交及关闭

一般情况下都有提交、重置等按钮,提交和重置在表单域button上写htmlType:submit||reset 在form上传onFinish方法(自动校验),表单域中的button不要用form.validateFields().then,校验逻辑通用的尽量都封装,然后再引入(如身份证,电话号码,银行卡等) imagen.png

在Modal或者Drawer内使用Form组件管理状态时,关闭并不会清除表单的数据(编辑或新增时打开修改后关闭再打开),即使配置了destroyOnClose属性也不生效。常用解决方式:

前一种在于手动清空,后两种在于根据state(visible/open)变化清空

  1. 手动调用:在关闭时调form.resetFields(),借助Form组件的API清空状态(可能会冗余代码,如确认取消等操作成功后都要调用)
  2. 弹框afterClose/抽屉afterOpenChange 关闭后调用form.resetFields()
  3. 在Form中设置preserve属性为false,在Modal或Drawer中设置destroyOnClose属性为true自动清空

新增编辑详情

新增编辑详情能用一个页面绝不写两个

回显:保持提交数据与回显数据的一致性,如antd DatePicker 选择后是moment,传到后端时需要转为字符串,回显时再从字符串转回moment,这时就可以将DatePicker二次封装,使选择后表单本身值是string(透传下来的onChange(moment.format(...)),回显时将透传的value自动转为moment也就能自动回显。

详情:回显数据+disabled(UI不满足时再考虑重写) / 用@formily/antd中PreviewText

表格

 //× bad  到处都是这种代码 各种冗余
<Form.Item label="支付方式" name="payType">
  <Select allowClear placeholder="请选择" size="large" style={{ width: '320px' }}>
    <Option value="WEIXIN">微信</Option>
    <Option value="ALIPAY">支付宝</Option>
    <Option value="BANK">银行卡</Option>
    <Option value="CASH">现金</Option>
    <Option value="OTHER">其他</Option>
  </Select>
</Form.Item>

{
  title: '支付方式',
  dataIndex: 'wayOfInOutType',
  render: (text, record) => (
    <a style={{ color: 'black' }}>
      {text.wayOfInOutType == 'WEIXIN'
        ? '微信'
        : text.wayOfInOutType == 'ALIPAY'
        ? '支付宝'
        : text.wayOfInOutType == 'BANK'
        ? '银行卡'
        : text.wayOfInOutType == 'CASH'
        ? '现金'
        : '其他'}
    </a>
  )
}


 //√ good
 export const payTypeOption=[{label:'xx',vallue:'xx'}]  //某文件导出
 
 import {payTypeOption}  .....
 const  getText =(val)=> payTypeOption.find(item=>item.value==val)?.label||'-' //util导入
 
<Form.Item label="支付方式" name="payType">
  <Select option={payTypeOption} allowClear placeholder="请选择" size="large" />
</Form.Item>

{
  title: '支付方式',
  dataIndex: 'wayOfInOutType',
  render: (text, record) => getText(text)
}

优化:某个业务模块多处使用枚举在模块目录下创建js文件并导出枚举对象,在全局多处使用便在全局目录下创建js文件并导出枚举数组对象。不理解这里支付方式不能点击颜色也不是亮色为啥要用a标签

表单提交时不要过度解构赋值

// ×  bad
const onFinish = values => {
    if (values) {
      delete values?.villageName
      const {
        verifyUserName,
        cardType,
        cardNo,
        phone,
        villageName,
        cert,
        propertyRecord,
        rangeTimer,
        unifyCode, 
        uploadFileType,
        groupPhoto,
        businessCard,
        organizationCard,
        taxRegistrationCard,
        idCardFront,
        idCardBehind,
        propertyServiceContractCard,
        serviceContentCard
      } = form.getFieldsValue(true)

      const par = {
        verifyType,
        verifyUserName,
        cardType,
        cardNo,
        phone,
        villageName
      }
      let params = {}


      if (verifyType === 'ASSEMBLY') {
        params = {
          ...par,
          cert
        }
      }
      if (verifyType === 'COMPANY_AGENT') {
        params = {
          ...par,
          serviceStartTime: rangeTimer.serviceStartTime,
          serviceEndTime: rangeTimer.serviceEndTime,
          unifyCode,
          uploadFileType,
          groupPhoto,
          businessCard,
          organizationCard,
          taxRegistrationCard,
          idCardFront,
          idCardBehind,
          propertyServiceContractCard,
          serviceContentCard
        }
      }
      if (verifyType === 'REALTY_MANAGEMENT_COMMITTEE') {
        params = {
          ...par,
          propertyRecord
        }
      }
    }

values不需要判断, 如果是校验没通过不会到这个方法, 通过即使没有值也是空对象 动态表单时页面上的表单应该也会动态删减, 不需要再手动判断该传什么值, 直接全部传, 即便是多传了也无所谓,后端只会取需要的

// √ good
const onFinish = values => {
// 若是如上面回显所说二次封装,保持提交数据与回显数据的一致性就都不用再处理了,直接传values
  axios({ method: 'xx', url: 'xx', data: { ...values, startTime:handle(value.Time)} })
}

Tabs

再给大家伙看个牛逼的,手写tabs,写就算了,还是每个用到tabs的地方都手动写

const PropertyArchives = () => {
  const [arr, setArr] = useState([
    { id: 1, name: 'xxx1', click: true },
    { id: 2, name: 'xxx2', click: false }
  ])
  const [count, setCount] = useState(1)
  const handleClick = id => {
    const newArr = arr.map(v => {
      v.click = false
      if (v.id == id) {
        setCount(v.id)
        v.click = true
      }
      return v
    })
    setArr(newArr)
  }
  return (
    <Wrap>
      <div className="tabber">
        {arr.map(v => (
          <div onClick={() => handleClick(v.id)} className={v.click ? 'tabberClickList' : 'tabberList'} key={v.id}>
            {v.name}
          </div>
        ))}
      </div>
      {count == 1 ? <WYArchives /> : <CategorySettings />}
    </Wrap>
  )
}

export default PropertyArchives

imagen.png

这好用吗 用antd的Tabs不香吗

还有写重复转换的代码的,咱就是说直接key为枚举里的值不就完事了

// bad ×
import React, { useState } from 'react'
import {  Tabs } from 'antd'

export default function Index() {

const getData = (val)=>{
  let status
  switch(val){
    case 1 :
      status='success'
      break;
    case 2 :
      status='fail'
      break;
  }
  api(xxx,{status})....
}

return <>
    <Tabs
      onChange={(val) => getData(val)}
      items={[
        {
          label: '成功',
          key: 1,
        },
        {
          label: '失败',
          key: 2,
        },
      ]}
    />
    ...
</>
}


CSS

再来两段css,这是后台管理啊,各种改组件样式,各种百分比和rem,大多数组件是可以传style的,要改也不应该是这种方式

.form {
    margin-top: 32px;
    width: 49%;
    input,
    .ant-select-selector {
      height: 48px;
      color: #1b1b1d;
      font-size: 16px;
      font-weight: 500;
    }
    .ant-select-selection-placeholder {
      line-height: 48px;
    }
    .ant-form-item-label {
      width: 22.5%;
    }
    .ant-form-item-label > label {
      height: 48px;
      justify-content: flex-end;
      font-size: 16px;
      font-weight: 500;
    }
    .ant-select-selection-item {
      line-height: 48px;
    }
    .star {
      position: relative;
      &::after {
        position: absolute;
        left: 13%;
        top: 20px;
        display: inline-block;
        color: #ff4d4f;
        font-size: 14px;
        font-family: SimSun, sans-serif;
        line-height: 1;
        content: '*';
      }
    }
    .ant-picker {
      width: 100%;
      height: 48px;
    }
  }
.form {
    width: 90%;
    margin: -0.5rem auto 4rem;
    .radio {
      height: 4.8rem;
      line-height: 5.8rem;
    }
    input,
    .ant-select-selector {
      height: 4.8rem;
      color: #1b1b1d;
      font-size: 1.6rem;
      font-weight: 500;
    }
    .ant-form-item-label {
      width: 30%;
    }
    .ant-form-item-control-input {
      width: 74%;
    }
    .ant-form-item-control-input-content {
      text-align: left;
    }
    .ant-form-item-with-help .ant-form-item-explain {
      text-align: left;
    }
    .ant-select-selector {
      height: 4.8rem;
      color: #1b1b1d;
    }
    .ant-select-single .ant-select-selector .ant-select-selection-item {
      line-height: 4.8rem;
      font-size: 1.4rem;
    }
    .flex {
      display: flex;
      align-items: center;
    }
    .ant-select-selection-placeholder {
      line-height: 4.8rem;
      font-size: 1.4rem;
    }
    .ant-form-item-label > label {
      height: 4.8rem;
      justify-content: flex-end;
      font-size: 1.6rem;
      font-weight: 500;
    }

弹性布局用Row,有间隔用Space,分割线用Divider,描述用Descriptions,排版用Typography,表单布局用Row+Col 或者labelCol+wapperCol

后台管理能少写样式就少写,能用组件库的就不要覆盖,特别是对于刚开始工作的人,不要UI图上怎么画就怎么写,要沟通(之前还遇到过一个新手,UI图上的字体浏览器没有,去百度了很久,还专门引了个字体包到项目,但其实根本就不需要改字体,想太多了),这个项目现在之所以丑且难用,很大一部分是因为最开始没有定好规范,确定好组件库之后就是页面适应组件而不是组件适应页面

其他

架构

这个项目还有个坑就是它的创建不是cra、不是ice也不是umi,而是一个架构师自己写的脚手架,他当时用这个的时候一定觉得他很NB吧,却不知道现在坑那么多。公司也不是个大公司,业务也不是啥非要自己架构的业务,就是简单的后台管理,有必要自己去架构?用umi不是啥都有

代码

  1. 状态值不要用props到处传,层级多就用redux等状态管理
  2. 经常会跳转到的页面里的状态最好是内部控制不要有props,需要的值用history.push({url,xxx})带过来
  3. 不写过多useState,相同类型的写一个useState({})
  4. 不写过多useEffect,业务逻辑清晰分离
  5. 恰当的用useRef代替useState,useRef除了用于获取dom和父调子通信,还会返回{current:xx}的对象
  6. 不用 arr.length && <>..</>, 0会显示在页面中
  7. 不要疯狂.then链式调用,async await用起来
  8. 还有些基础的数组map都用不明白的,非要用for循环
for (let i = 0; i < data.length; i++) {
    data[i].name = data[i].fileName
    data[i].prefix = data[i].suffix
    data[i].path = imgUrlHandle(
      data[i].fileUrl
    )
    data[i].url = imgUrlHandle(
      data[i].fileUrl
    )
  }

自定义Hook

之前小程序上拉分页加载逻辑代码

// × bad

import React, { useState, useEffect } from "react";
import Taro, {
  getCurrentInstance,
  stopPullDownRefresh,
  usePullDownRefresh
} from "@tarojs/taro";
import { Input, View, Image, Text } from "@tarojs/components";
let one = true;
let first = true;
const Index = () => {
  let isLoad = false;
  const [arr, setArr] = useState([]);
  const [name, setName] = useState("");
  const [search, setSearch] = useState({
    keyWords: "",
    page: 0,
    pageSize: 10
  });
  useEffect(() => {
    if (isLoad) return;
    api.xx({ form: search }).then(res => {
      // 判断小于10条就没有了
      if (res.content) {
        if (res.content.length < 10) {
          isLoad = true;
        }
        if (first) {
          setArr(res.content);
        } else {
          setArr([...arr, ...res.content]);
          first = false;
        }
      }
    });
  }, [search]);
  const handleSearch = () => {
    isLoad = false;
    one = false;
    first = true;
    setSearch({ ...search, keyWords: name, page: 0 });
  };
  const onScrollToLower = () => {
    if (isLoad) return;
    one = false;
    let page: number = search.page || 0;
    let data = { ...search };
    data.page = page + 1;
    first = false;
    setSearch(data);
  };
  usePullDownRefresh(() => {
    one = false;
    first = true;
    isLoad = false;
    setSearch({ ...search, page: 0 });
  });
  return (...)
 }

用自定义hook后

√ good
import { usePageData } from "@/utils/hooks";

const Index = () => {
//content:列表数据 isLoad: 是否加载完 getNextPageData: 获取下一页并合并后的数据
   const { content, getNextPageData,isLoad ,loading} = usePageData({
    api: xxx,
    initParams: xxx
  });

  // 上拉加载更多
  const onScrollToLower = () => {
    getNextPageData();
  };
  return <>...</>
}

最后

Creo que los jys que a menudo van a la comunidad pueden no tomarlo en serio y no entenderlo, pero todavía hay tantos códigos de este tipo y todavía hay muchos problemas lógicos que no se han escrito. También espero que todos puedan mejorar su códigos propios Nadie quiere que su código sea regañado por aquellos que toman el control, y usted puede compartir más "casos clásicos", o la misma oración,不想着写的多么优雅NB,但要为了实用维护和自我提升努努力吧

Hablando de otras cosas, también tengo algo de limpieza en el código, y también quiero escribir un código elegante y ser adoptado. Este proyecto a menudo se refactoriza cuando se cambia a un lugar determinado y puede que no sea aceptable. Mirando hacia atrás, escribí mucho código basura antes, y algunas personas dijeron que mi código era un problema. Pero definitivamente cambiará. Cada vez que escriba un nuevo requisito más tarde, también pensaré en una mejor forma de escribir. Incluso si puedo lograrlo yo mismo, seguiré mirando los métodos de escritura de otras personas en Baidu. En el pasado, mi definición de front-end junior, intermedio y avanzado era bastante vaga y sentía que había muchas consideraciones además del código. Pero como he experimentado los códigos de reclutamiento de miembros del equipo y miembros del equipo CR, el pensamiento de programación y las ideas de desarrollo se reflejarán en el código, por lo que muchas veces el código es suficiente para probar todo.

Me gusta

Supongo que te gusta

Origin juejin.im/post/7248431478241329213
Recomendado
Clasificación