最近常用的是golang、python、最新项目虽然用了react,但直接上了低代码,这么一回想,已经1年多没有正经用react了。发现自己连form表单都倒腾了好久,好记性不如烂笔头。
关于ts,很长一段时间,我会要求自己完全禁用any,不过自从golang写习惯后意识到ts真的只是个半成品,当然可以花费时间去刻意追求,这很好,但哪怕追求到极致也用着不顺(对我来说),所以下面案例我对ts可能非常随心所欲,希望读者根据自己的情况,减少any

upload

有这么几个需求:

  1. 上传文件,这个官方文档里最普通案例就有,可以直接使用
  2. 异步初始化数据(初始化列表是通过接口异步获取的):这个需要使用使用fileList,不能使用defaultFileList
  3. 下载功能,如果是静态资源,可以有路径了直接下载,但如果是使用参数请求后端接口,需要额外处理,目前一期只用静态资源,这种方式不是很安全,没有做权限管控,对于敏感信息不能这样做
  4. 校验,官方文档有案例,对后缀名的校验需要重新修改
<Col span={24}>
  <Form.Item label="附件" name="filelist">
    <Upload action="/api/file/upload/requirement/create/" onChange={uploadOnChange}
      listType="picture" beforeUpload={(file: any) => beforeUpload(file)}
      fileList={defaultFileList}>
      <button style={{ border: 0, background: 'none' }} type="button">
        <PlusOutlined />
        <div style={{ marginTop: 8 }}>上传</div>
      </button>
    </Upload>
  </Form.Item>
</Col>

富文本编辑器

安装

npm install braft-editor --save
yarn add braft-editor

使用

引入

import BraftEditor from 'braft-editor'; // 引入编辑器组件
import 'braft-editor/dist/index.css'; // 引入编辑器样式

react组件中使用如下代码,设置必要的值

const [editorState, setEditorState] = useState(BraftEditor.createEditorState('')); // 设置编辑器的初始内容
const [isEdit, setIsEdit] = useState(false)

组件的渲染,我渲染在了form表单里

<Form disabled={!isEdit} form={form}>
  <Col span={24}>
          <Form.Item label="详情" name="remark" rules={rules.remark}>
            <BraftEditor onChange={handleEditorChange}             
              className="draft-editor-content-wrapper" readOnly={!isEdit}/>
          </Form.Item>
        </Col>
</Form>

异步数据初始化,使用能查到的最常见方法无法初始化,可以参考下方issues
https://github.com/margox/braft-editor/issues/341

  const [form] = Form.useForm()
  form.setFieldsValue({
      remark:  BraftEditor.createEditorState(baseInfo?.remark || ''),
  })

修改数据

  const handleEditorChange = (e: any) => {
    // 更新编辑器的状态, 不论是否为空状态,都把内容填入,等最后上传的时候确认内容(因为用户也可能把内容改成空的)
    // 是否为空使用自定义校验规则
    form.setFieldValue('remark', e.toHTML())
  };

规则校验

  const rules = {    
    remark: [
      { required: true, message: '请输入详情' },
      {
        validator: (_: any, value: string) => {
          if (value === '<p></p>') {
            return Promise.reject('详情不能为空');
          }
          return Promise.resolve();
        }
      }
    ]
  }

如何知道是否手机端

import { Steps,Button,Grid } from 'antd';
const { useBreakpoint } = Grid;
const screens = useBreakpoint();

通用操作:自定义组件、数据缓存、防抖

自定义组件

form表单中我们可能会想要使用一些自定义组件,特别是公共组件,此时直接丢进form表单将无法正常使用form的校验,无法取得正常值;
父组件引入子组件:正常引入,正常使用

import UserPart from '../../components/base/user/index';
 <Form form={form} labelCol={{
    xs: { span: 6 },
    sm: { span: 3 }, 
    // style: {textAlign: screens.xs? 'left':'right' }
  }}>
  <Form.Item label="负责人" name="manager" rules={rules.manager}>
    <UserPart />
  </Form.Item>
</Form>

不管是form表单的校验还是值操作,保持正常
let checkRes = await form.validateFields()
form.getFieldsValue()

子组件关键是onChange的绑定或者自定义操作

import React, { useEffect } from "react";
import { Select } from "antd";
import {getUser} from "../service";

const UserPart: React.FC<{value?:any; onChange?:any}> = (props) => {
  const {value, onChange} = props;
  const [userList, setUserList] = React.useState([]);
  useEffect(() => {
    getUser().then((res) => {
      setUserList(res.data||[]);
    console.log(value)
    }).catch (err => {
      console.log(err)
    })
  }, []);

  return <Select showSearch value={value}
    filterOption={(input, option) =>
    ((option?.label + '__' + option?.pinyin) ?? '').toLowerCase().includes(input.toLowerCase())
  }
  placeholder="请选择"
  options={userList.map((item: any) => {
    return { 
      label: `${item.fullName}(${item.username}) - ${item.department||""}`, 
      value: item.username, pinyin: item.pinyin }
  })}
  onChange={onChange}
  allowClear></Select>
  
}
export default UserPart;

数据缓存

公共组件,比如下面案例里的用户列表,并不需要每次都获取最新数据,直接缓存可以减少数据库压力,但是同时也没必要用各种三方工具或者storage来存储,直接使用语言本身的特性,内存存储即可

import {axios} from '../../plugin';

// 缓存用户数据
let cacheUserInfo: any = null;

// getUser获取用户
export async function getUser() {
  if (cacheUserInfo) {
    return Promise.resolve(cacheUserInfo);
  }
  try {
    let res = await axios.get(`/api/base/user/`);
    cacheUserInfo = res;
    return Promise.resolve(cacheUserInfo);
  } catch (err) {
    return Promise.reject(err);
  }
}

防抖

用拍照类比,当你拍照手抖时,仍然只会拍1清晰的照片,因为你会控制自己等手抖结束,聚焦清晰后按下快门。函数防抖就是,当你执行,但触发了很多次(特别是用户很急的时候,会疯狂快速多次点击按钮),程序控制当多次点击触发时,只提交最后一次。
防抖函数不是很复杂的原理,只要内部有个计时器,看上次点击和这次点击的时间差,不过有现成的工具,就用了现成的工具

import { debounce } from 'lodash'; 
  // 防抖,防止多次提交
  const handleOk = debounce( async () => {
    console.log("我快速提交")
    console.log(form.getFieldsValue())
    setLoading(true)
    // 数据的校验
    try {
      let checkRes = await form.validateFields()
    } catch (errorInfo) {
      setLoading(false)
    }
    // 提交
    setLoading(false)
  }, 1000);

其他

当然还会有很多其他的细节,诸如对于规则的书写,加载中状态的展现,这个案例是一个弹框
所以状态的展现操作的是Modal的属性
<Modal title="新增/编辑" open={isOpen} onOk={handleOk} onCancel={handleCancel} width="1000px"

  confirmLoading={loading} okText="确认" cancelText="取消">

校验规则的书写

const rules = {
  nameValue: [
    { required: true, message: '请输入标题' },
    { max: 50, message: '标题不能超过50个字符' },
    { min: 5, message: '标题不能少于5个字符' }
  ], 
  manager: [
    { required: true, message: '请选择负责人' }
  ],
}

vincent
48 声望2 粉丝