国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【react框架】結(jié)合antd做表單組件的一些心得記錄

這篇具有很好參考價(jià)值的文章主要介紹了【react框架】結(jié)合antd做表單組件的一些心得記錄。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

前言

作為一個(gè)前端最常遇見(jiàn)的需求場(chǎng)景就是寫表單、寫表格。寫多了會(huì)逐漸的積累一些開發(fā)心得,此文章根據(jù)我使用vue和react的經(jīng)驗(yàn)記錄了一些東西,拋磚引玉的給大家看看。


功能的實(shí)現(xiàn)盡量先看第三方組件庫(kù)上是否已經(jīng)提供

舉例react項(xiàng)目,在做表單的很多時(shí)候,我都是從antd上把其中一個(gè)form組件例子復(fù)制下來(lái),然后再看看提供了哪些api就開干了。

這樣做其實(shí)會(huì)忽略很多細(xì)節(jié)方面的東西。

比如我想在rules校驗(yàn)的時(shí)候拿到其他item的值,可能會(huì)通過(guò)form的實(shí)例調(diào)用getFieldValue,但其實(shí)在rules的使用中已經(jīng)默認(rèn)傳入了該方法:

rules={[
  ({ getFieldValue }) => ({
    validator(_, value) {
      // 可以通過(guò)getFieldValue('password')獲取對(duì)應(yīng)其他item的值
    },
  }),
]}

類似的例子還有很多,所以有些需要的功能其實(shí)你細(xì)看整個(gè)form組件的頁(yè)面說(shuō)明后,說(shuō)不定就給你找到了你想要的。

這里提供一個(gè)注冊(cè)表單的例子,里面很多小細(xì)節(jié)可以注意下,沒(méi)準(zhǔn)你還有新發(fā)現(xiàn):

<Form labelCol={{ span: 6 }} wrapperCol={{ span: 16 }} onFinish={onFinish}>
  <Form.Item
    label="用戶名"
    name="username"
    rules={[
      { required: true, message: '請(qǐng)輸入用戶名' },
      { type: 'string', min: 5, max: 20, message: '字符長(zhǎng)度在 5-20 之間' },
      { pattern: /^\w+$/, message: '只能是字母數(shù)字下劃線' },
    ]}
  >
    <Input />
  </Form.Item>
  <Form.Item
    label="密碼"
    name="password"
    rules={[{ required: true, message: '請(qǐng)輸入密碼' }]}
  >
    <Input.Password />
  </Form.Item>
  <Form.Item
    label="確認(rèn)密碼"
    name="confirm"
    dependencies={['password']} // 依賴于 password ,password 變化,會(huì)重新觸發(fā) validator
    rules={[
      { required: true, message: '請(qǐng)輸入密碼' },
      ({ getFieldValue }) => ({
        validator(_, value) {
          if (!value || getFieldValue('password') === value) {
            return Promise.resolve()
          } else {
            return Promise.reject(new Error('兩次密碼不一致'))
          }
        },
      }),
    ]}
  >
    <Input.Password />
  </Form.Item>
  <Form.Item label="昵稱" name="nickname">
    <Input />
  </Form.Item>
  <Form.Item wrapperCol={{ offset: 6, span: 16 }}>
    <Space>
      <Button type="primary" htmlType="submit">
        注冊(cè)
      </Button>
      <Link to={LOGIN_PATHNAME}>已有賬戶,登錄</Link>
    </Space>
  </Form.Item>
</Form>

其實(shí)不僅僅是表單組件,其他組件也是一樣的道理,所以建議沒(méi)事可以多去組件庫(kù)官網(wǎng)上翻翻,留些整體印象


當(dāng)一個(gè)頁(yè)面有多個(gè)表單組件時(shí),就要優(yōu)先考慮把值存在狀態(tài)管理中

這種場(chǎng)景太常見(jiàn)了,一個(gè)頁(yè)面有多個(gè)表單組件。我看很多時(shí)候大家都是把每個(gè)組件的數(shù)據(jù)狀態(tài)維護(hù)在各自的組件中,其實(shí)不太好。

  • 指不定你下個(gè)頁(yè)面還可以返回上一步的表單填寫頁(yè),如果都把數(shù)據(jù)統(tǒng)一放在狀態(tài)管理維護(hù),回顯就很容易了。
  • 各個(gè)表單組件可能是會(huì)聯(lián)動(dòng)的,例如a組件的某個(gè)item的值會(huì)改變b組件的一些item的顯隱,有狀態(tài)管理就比較容易去做。

當(dāng)有一個(gè)復(fù)雜的多表單頁(yè)面時(shí),建議做頁(yè)面緩存

場(chǎng)景舉例:

  • 復(fù)雜的多表單頁(yè)面A —> 復(fù)雜的多表單頁(yè)面B,A頁(yè)面下一步到B頁(yè)面,然后再返回A頁(yè)面,A頁(yè)面回顯
  • 移動(dòng)端中復(fù)雜的多表單頁(yè)面A ----> 某項(xiàng)輸入項(xiàng)具體選擇的頁(yè)面C,A頁(yè)面點(diǎn)擊某項(xiàng)輸入項(xiàng)到C頁(yè)面,然后再返回A頁(yè)面,A頁(yè)面回顯并且定位在剛剛的位置。

這些情況還用redux去做回顯就很笨了,增加代碼復(fù)雜度。

vue有官方的keepalive,react有網(wǎng)友給我推薦了keepalive-react-component,我個(gè)人還沒(méi)使用過(guò)。


如果一些表單比較簡(jiǎn)單且能確保后續(xù)不會(huì)有復(fù)雜功能的拓展,可以使用業(yè)務(wù)組件

例如后臺(tái)管理的一些列表搜索頁(yè)的搜索表單區(qū)域,這種一般就不會(huì)做的很復(fù)雜,很適合用業(yè)務(wù)組件。

【react框架】結(jié)合antd做表單組件的一些心得記錄

但當(dāng)你覺(jué)得某個(gè)表單現(xiàn)在看來(lái)簡(jiǎn)單,但以后被改復(fù)雜的可能性很大時(shí),就不要考慮使用業(yè)務(wù)組件了。

react表單業(yè)務(wù)類組件例子:

/* eslint-disable react-hooks/exhaustive-deps */
/* 用于列表頁(yè)的搜索過(guò)濾區(qū) 類組件
necessaryField 必須返回字段值
initiative 主動(dòng)第一次觸發(fā)搜索

formatter: int

btnChildren 按鈕插槽

config :{
    formItems: [],
    colSpan: [], // 
    formLayout
}
*/
import React, { Component } from 'react'
import {
    Button,
    Form,
    Input,
    Select,
    DatePicker,
    Row,
    Col,
    InputNumber,
} from 'antd'
import { ReloadOutlined } from '@ant-design/icons'

import moment from 'moment'

import './TableFiltersSearch.less'
import { isEffectVar, deepClone, validateNumber } from './utils'

export default class TableFiltersSearchClass extends Component {
    constructor(props) {
        super(props)
        this.state = {
            formItems: [],
            colSpan: [21, 3], // 表單和按鈕區(qū)的寬度設(shè)置
            formLayout: 'inline',
            formItemLayout: {
                labelCol: {
                    flex: '1',
                },
                wrapperCol: {
                    flex: '1',
                },
            },
        }
    }
    formRef = React.createRef()
    submitRef = React.createRef()
    // 布局

    componentDidMount() {
        /* 處理搜索區(qū)配置 */
        this.init()
    }

    componentDidUpdate() {
        // this.init()
    }

    init = () => {
        if (this.props.config) {
            // 先對(duì)子項(xiàng)做處理
            this.setState({
                formItems: this.itemConfigDeal(this.props.config.itemConfigs),
            })
            // 默認(rèn)值處理
            this.defaultFormDeal(this.props.config.itemConfigs)
            // 是否需要主動(dòng)初始化觸發(fā)
            if (this.props.initiative) {
                this.queryHandle()
            }
            // 表單和按鈕區(qū)的寬度設(shè)置
            if (this.props?.config?.colSpan) {
                this.setState({
                    colSpan: this.props.config.colSpan,
                })
            }
            if (this.props?.config?.formLayout) {
                this.setState({
                    formLayout: this.props.config.formLayout,
                })
            }
            if (this.props?.config?.formItemLayout) {
                this.setState({
                    formItemLayout: this.props.config.formItemLayout,
                })
            }
        }
    }

    /* 子項(xiàng)的處理 */
    itemConfigDeal = (configs) => {
        return configs.map((item, index) => {
            if (item.display === false) {
                return null
            }
            if (!item.type || item.type === 'input') {
                // 普通輸入框
                return (
                    <Form.Item
                        label={item.label}
                        name={item.name}
                        key={index}
                        rules={item.rules ? item.rules : []}
                    >
                        <Input
                            style={{
                                width: item.width ? item.width : '220px',
                            }}
                            placeholder={
                                item.placeholder ? item.placeholder : '請(qǐng)輸入'
                            }
                            allowClear
                            onChange={(e) => {
                                this.inputOnChange(e, item)
                            }}
                            disabled={
                                item.disabled !== undefined
                                    ? item.disabled
                                    : false
                            }
                            // {...item.attr}
                        />
                    </Form.Item>
                )
            }
            // 數(shù)字輸入框
            if (!item.type || item.type === 'inputNumber') {
                return (
                    <Form.Item
                        label={item.label}
                        name={item.name}
                        key={index}
                        rules={item.rules ? item.rules : []}
                    >
                        <InputNumber
                            style={{ width: item.width ? item.width : '220px' }}
                            placeholder={
                                item.placeholder ? item.placeholder : '請(qǐng)輸入'
                            }
                            allowClear
                            disabled={
                                item.disabled !== undefined
                                    ? item.disabled
                                    : false
                            }
                            // onChange={e => e.target.value = validateNumber(e.target.value)}
                        />
                    </Form.Item>
                )
            }

            // 時(shí)間返回選擇器
            if (item.type === 'dateRange') {
                return (
                    <Form.Item
                        label={item.label}
                        name={item.name}
                        key={index}
                        rules={item.rules ? item.rules : []}
                    >
                        <DatePicker.RangePicker
                            style={{ width: item.width ? item.width : '220px' }}
                            separator="至"
                            format="YYYY-MM-DD"
                            disabled={
                                item.disabled !== undefined
                                    ? item.disabled
                                    : false
                            }
                        />
                    </Form.Item>
                )
            }
            // 單選下拉框
            if (item.type === 'select') {
                return (
                    <Form.Item
                        label={item.label}
                        name={item.name}
                        key={index}
                        rules={item.rules ? item.rules : []}
                    >
                        <Select
                            style={{ width: item.width ? item.width : '220px' }}
                            allowClear
                            placeholder={
                                item.placeholder ? item.placeholder : '請(qǐng)選擇'
                            }
                            disabled={
                                item.disabled !== undefined
                                    ? item.disabled
                                    : false
                            }
                        >
                            {item.option?.length > 0 &&
                                item.option.map((o, i) => {
                                    return (
                                        <Select.Option key={i} value={o.value}>
                                            {o.label}
                                        </Select.Option>
                                    )
                                })}
                        </Select>
                    </Form.Item>
                )
            }

            // 多選下拉框
            if (item.type === 'selectMulti') {
                return (
                    <Form.Item
                        label={item.label}
                        name={item.name}
                        key={index}
                        rules={item.rules ? item.rules : []}
                    >
                        <Select
                            style={{ width: item.width ? item.width : '220px' }}
                            mode="multiple"
                            allowClear
                            placeholder={
                                item.placeholder ? item.placeholder : '請(qǐng)選擇'
                            }
                            disabled={
                                item.disabled !== undefined
                                    ? item.disabled
                                    : false
                            }
                            onChange={(value) => {
                                item.onChange && item.onChange(value)
                            }}
                        >
                            {item.option?.length > 0 &&
                                item.option.map((o, i) => {
                                    return (
                                        <Select.Option
                                            key={i}
                                            value={o.value}
                                            maxTagCount="responsive"
                                        >
                                            {o.label}
                                        </Select.Option>
                                    )
                                })}
                        </Select>
                    </Form.Item>
                )
            }
            return ''
        })
    }

    defaultFormDeal = (configs) => {
        configs.forEach((item, index) => {
            if (isEffectVar(item.default)) {
                this.formRef.current.setFieldsValue({
                    [item.name]: item.default,
                })
            }
        })
    }

    /* 重置 */
    resetHandle = () => {
        this.formRef.current.resetFields()
        this.props.resetFn && this.props.resetFn() // 重置的回調(diào)
    }

    /* 查詢 */
    queryHandle = (extraData) => {
        let data = this.formRef.current.getFieldsValue(true) // 返回的居然是淺拷貝 T^T
        data = deepClone(data)
        this.formRef.current
            .validateFields()
            .then((values) => {
                this.queryDoing(data, extraData)
            })
            .catch((errorInfo) => {
                return false // 返回不出去,默認(rèn)有undefined判斷
            })
    }

    // 查詢做的事
    queryDoing = (data, extraData) => {
        // 記錄日期格式的子項(xiàng)的key
        let dateTypeKeys = this.props.config.itemConfigs.map((item) => {
            if (item.type === 'dateRange') {
                return item.name
            }
        })
        Object.keys(data).forEach((key) => {
            // 把時(shí)間處理成后端需要的入?yún)?/span>
            if (!isEffectVar(data[key])) {
                data[key] = ''
            }
            if (dateTypeKeys.includes(key)) {
                let dateArr = [...data[key]]
                if (dateArr.length) {
                    // 原始數(shù)據(jù)只有null或者[x,x]
                    data[key][0] = moment(dateArr[0]).format('YYYYMMDD')
                    data[key][1] = moment(dateArr[1]).format('YYYYMMDD')
                }
            }
        })
        // 如果必須要返回所有字段,空的就返回undefined
        if (this.props.necessaryField) {
            this.props.config.itemConfigs.forEach((item) => {
                if (!isEffectVar(data[item.name])) {
                    data[item.name] = undefined
                }
            })
        }
        console.log('經(jīng)過(guò)二次加工提交的原生數(shù)值', data)
        this.props.queryFn && this.props.queryFn(data, extraData) // 重置的回調(diào)
    }

    getData = () => {
        let data = this.formRef.current.getFieldsValue(true) // 返回的居然是淺拷貝 T^T
        data = deepClone(data)
        return data
    }

    validateFields = async () => {
        const res = await this.formRef.current
            .validateFields()
            .catch((err) => null)

        if (!res) {
            return null
        } else {
            return this.getData()
        }
    }

    /* input格式化 */
    inputOnChange = (e, item) => {
        let value = e.target.value
        if (item.formatter === 'int') {
            value = value.replace(/[^\d]/g, '')
        }
        this.formRef.current.setFieldsValue({
            [item.name]: value,
        })
    }

    onFinish = (values) => {
        console.log('Success:', values)
        this.queryHandle()
    }

    onFinishFailed = (errorInfo) => {
        console.log('Failed:', errorInfo)
    }

    setFieldsValue = (data) => {
        console.log('賦值', data)
        this.formRef.current.setFieldsValue({
            ...data,
        })
    }

    render() {
        let { formItems, colSpan, formLayout, formItemLayout } = this.state
        let { btnChildren } = this.props
        return (
            <div className="tfs-container">
                {/* {...formItemLayout} */}
                <Row>
                    <Col span={colSpan[0]}>
                        <Form
                            {...formItemLayout}
                            layout={formLayout}
                            ref={this.formRef}
                            onFinish={this.onFinish}
                            onFinishFailed={this.onFinishFailed}
                            className={
                                'tfs-table' + formLayout === 'horizontal'
                                    ? 'tfs-horizontal-table'
                                    : ''
                            }
                        >
                            {/* 循環(huán)渲染子項(xiàng) */}
                            {formItems.length > 0 &&
                                formItems.map((item) => {
                                    return item
                                })}
                            <Button
                                ref={this.submitRef}
                                style={{ display: 'none' }}
                                htmlType="submit"
                            ></Button>
                        </Form>
                    </Col>
                    {colSpan[1] !== 0 && (
                        <Col span={colSpan[1]}>
                            {/* 按鈕區(qū) */}
                            <div className="tfs-btn-container">
                                <Button
                                    type={'link'}
                                    onClick={this.resetHandle}
                                >
                                    重置 <ReloadOutlined />
                                </Button>

                                <Button
                                    type={'primary'}
                                    className="it2-primary-button"
                                    htmlType="submit"
                                    onClick={() => {
                                        this.submitRef.current.click()
                                    }}
                                    loading={this.props.isSearching}
                                >
                                    查詢
                                </Button>

                                {/* 按鈕插槽 */}
                                {btnChildren ? btnChildren : null}
                            </div>
                        </Col>
                    )}
                </Row>

                {/* 縱向表單的自定義按鈕 */}
                {colSpan[1] === 0 && formLayout === 'horizontal' && (
                    <Row>{btnChildren ? btnChildren : null}</Row>
                )}
            </div>
        )
    }
}

所需工具函數(shù)

import moment, { isMoment } from "moment";

/* 是否是有效值 */
export function isEffectVar(val) {
    return ![null, "", undefined].includes(val);
}

export function deepClone(target) {
    let result;
    if (typeof target === "object") {
        if (Array.isArray(target)) {
            result = [];
            for (let i in target) {
                result.push(deepClone(target[i]));
            }
        } else if (target === null) {
            result = null;
        } else if (isMoment(target)) {
            result = target.clone();
        } else if (target.constructor === RegExp) {
            result = target;
        } else {
            result = {};
            for (let i in target) {
                result[i] = deepClone(target[i]);
            }
        }
    } else {
        result = target;
    }
    return result;
}

export function validateNumber(value) {
    // 使用正則表達(dá)式驗(yàn)證輸入值是否為數(shù)字
    const reg = /^\d*$/;
    if (!reg.test(value)) {
        // 如果輸入值不是數(shù)字,則清空輸入框
        return '';
    }
    return value;
}

使用配置例子:

formConfig: {
    colSpan: [24, 0],
    formLayout: 'horizontal',
    formItemLayout: {
        labelCol: {
            span: 7,
        },
        wrapperCol: {
            span: 17,
        },
    },
    itemConfigs: [
        {
            label: '場(chǎng)景',
            name: 'signScenType', 
            type: 'selectMulti',
            option: [
                { label: '采購(gòu)', value: 'P' },
                { label: '銷售', value: 'S' },
                { label: '其他', value: 'O' },
            ],
            width: '500px',
            rules: [{ required: true, message: '請(qǐng)輸入內(nèi)容' }],
            onChange: (value) => {
            },
        },
        {
            label: 'aaaaa',
            name: 'signOtherScen', // signOtherScen?
            width: '500px',
            rules: [{ required: true, message: '請(qǐng)輸入內(nèi)容' }],
            display: false,
        },
        {
            label: '簽署量(份/年)',
            name: 'signVolume',
            formatter: 'int',
            width: '500px',
            rules: [
                { required: true, message: '請(qǐng)輸入內(nèi)容' },
                ({ getFieldValue }) => ({
                    validator(_, value) {
                        if (value) {
                            if (Number(value) > 1000000000) {
                                return Promise.reject(
                                    new Error('超出最大數(shù)值')
                                )
                            }
                        }
                        return Promise.resolve()
                    },
                }),
            ],
        },{
            label: '聯(lián)系人手機(jī)號(hào)',
            name: 'contactNumber',
            formatter: 'int',
            width: '500px',
            rules: [{ required: true, message: '請(qǐng)輸入內(nèi)容' }],
        },
        {
            label: 'bbbbbb',
            name: 'FDD',
            width: '500px',
            disabled: true,
            default: 'aaa',
            rules: [{ required: true, message: '請(qǐng)輸入內(nèi)容' }],
        },
    ],
},

配置寫的比較倡促,以后來(lái)寫個(gè)完整的

vue的我之前也寫過(guò)一個(gè)vue2版本的組件庫(kù)例子:【業(yè)務(wù)組件二次封裝】


表單做搜索區(qū)與表格怎么做聯(lián)動(dòng)

后臺(tái)中有種頁(yè)面場(chǎng)景很常見(jiàn),就是一個(gè)表單搜索區(qū)+分頁(yè)表格區(qū)+分頁(yè)區(qū)(就如上面例子的圖片)。這種組件怎么設(shè)計(jì)才好,并且盡量做到三者之間解耦。

還是用react舉例(需要配合ahooks第三方庫(kù)):

我們?nèi)ピO(shè)計(jì)一個(gè)這樣的頁(yè)面時(shí),會(huì)用一個(gè)容器組件,里面引入表單組件,表格組件和分頁(yè)組件。表單組件的值驅(qū)動(dòng)著表格數(shù)據(jù)的獲?。ǚ猪?yè)組件待會(huì)再說(shuō)),我們首先看看表單組件怎么驅(qū)動(dòng)最好。

咱們就舉例一個(gè)最簡(jiǎn)單的表單,就只有一個(gè)搜索輸入項(xiàng),我們想做到的是:

  • 刷新頁(yè)面,表單原來(lái)填寫的值能夠回顯
  • 容器組件和表格組件都能拿到表單的值,且盡量解耦

如果我們把每次搜索時(shí)表單的值都更新在url上,其他組件通過(guò)router的hook就可以拿到表單值,頁(yè)面刷新后表單組件也能獲取到表單值進(jìn)行回填。

import React, { FC, useEffect, useState } from 'react'
import type { ChangeEvent } from 'react'
import { useNavigate, useLocation, useSearchParams } from 'react-router-dom'
import { Input } from 'antd'
import { LIST_SEARCH_PARAM_KEY } from '../constant'

const { Search } = Input

const ListSearch: FC = () => {
  const nav = useNavigate()
  const { pathname } = useLocation()

  // 輸入值
  const [value, setValue] = useState('')
  function handleChange(event: ChangeEvent<HTMLInputElement>) {
    setValue(event.target.value)
  }

  // 獲取 url 參數(shù),回填表單數(shù)據(jù)
  const [searchParams] = useSearchParams()
  useEffect(() => {
    const curVal = searchParams.get(LIST_SEARCH_PARAM_KEY) || ''
    setValue(curVal)
  }, [searchParams])

  // 點(diǎn)擊搜索
  function handleSearch(value: string) {
    // 跳轉(zhuǎn)頁(yè)面,增加 url 參數(shù)
    nav({
      pathname,
      search: `${LIST_SEARCH_PARAM_KEY}=${value}`, // 去掉了 page pageSize
    })
  }

  return (
    <Search
      allowClear
      placeholder="輸入關(guān)鍵字"
      value={value}
      onChange={handleChange}
      onSearch={handleSearch}
    />
  )
}

export default ListSearch

除了表單組件驅(qū)動(dòng)表格數(shù)據(jù),還有分頁(yè)組件的分頁(yè)數(shù)據(jù)也驅(qū)動(dòng)著表格數(shù)據(jù)的獲取,驅(qū)動(dòng)方式也和表單組件一樣即可:

import React, { FC, useEffect, useState } from 'react'
import { Pagination } from 'antd'
import { useSearchParams, useNavigate, useLocation } from 'react-router-dom'
import { LIST_PAGE_SIZE, LIST_PAGE_PARAM_KEY, LIST_PAGE_SIZE_PARAM_KEY } from '../constant/index'

type PropsType = {
  total: number
}

const ListPage: FC<PropsType> = (props: PropsType) => {
  const { total } = props
  const [current, setCurrent] = useState(1)
  const [pageSize, setPageSize] = useState(LIST_PAGE_SIZE)

  // 從 url 參數(shù)中找到 page pageSize ,并且同步到 Pagination 組件中
  const [searchParams] = useSearchParams()
  useEffect(() => {
    const page = parseInt(searchParams.get(LIST_PAGE_PARAM_KEY) || '') || 1
    setCurrent(page)
    const pageSize = parseInt(searchParams.get(LIST_PAGE_SIZE_PARAM_KEY) || '') || LIST_PAGE_SIZE
    setPageSize(pageSize)
  }, [searchParams])

  // 當(dāng) page pageSize 改變時(shí),改變 url 對(duì)應(yīng)參數(shù)
  const nav = useNavigate()
  const { pathname } = useLocation()
  function handlePageChange(page: number, pageSize: number) {
    searchParams.set(LIST_PAGE_PARAM_KEY, page.toString())
    searchParams.set(LIST_PAGE_SIZE_PARAM_KEY, pageSize.toString())

    nav({
      pathname,
      search: searchParams.toString(), // 除了改變 page pageSize 之外,其他的 url 參數(shù)要帶著
    })
  }

  return (
    <Pagination current={current} pageSize={pageSize} total={total} onChange={handlePageChange} />
  )
}

export default ListPage

我們可以把表格數(shù)據(jù)的獲取放在容器組件中,只需要監(jiān)聽路由的變化即可,還可以專門抽離成一個(gè)hook:

import { useSearchParams } from 'react-router-dom'
import { useRequest } from 'ahooks'
import { getQuestionListService } from '../services/question'
import {
  LIST_SEARCH_PARAM_KEY,
  LIST_PAGE_PARAM_KEY,
  LIST_PAGE_SIZE_PARAM_KEY,
  LIST_PAGE_SIZE,
} from '../constant/index'

// 可能多個(gè)表單表格頁(yè)面功能是一樣的,可以復(fù)用,用傳入的類型來(lái)區(qū)分,但是為了業(yè)務(wù)拓展性,建議還是分開寫hook
type OptionType = {
  isStar: boolean
  isDeleted: boolean
}

function useLoadQuestionListData(opt: Partial<OptionType> = {}) {
  const { isStar, isDeleted } = opt
  const [searchParams] = useSearchParams()

  const { data, loading, error, refresh } = useRequest(
    async () => {
      // 從url中解構(gòu)出入?yún)?/span>
      const keyword = searchParams.get(LIST_SEARCH_PARAM_KEY) || ''
      const page = parseInt(searchParams.get(LIST_PAGE_PARAM_KEY) || '') || 1
      const pageSize = parseInt(searchParams.get(LIST_PAGE_SIZE_PARAM_KEY) || '') || LIST_PAGE_SIZE

      const data = await getQuestionListService({ keyword, isStar, isDeleted, page, pageSize })
      return data
    },
    {
      refreshDeps: [searchParams], // 刷新的依賴項(xiàng)
    }
  )

  return { data, loading, error, refresh } // 返回接口數(shù)據(jù)、是否正在請(qǐng)求中的狀態(tài)、錯(cuò)誤情況、手動(dòng)更新函數(shù)(參數(shù)不變)
}

export default useLoadQuestionListData

代碼設(shè)計(jì)來(lái)自雙越老師的視頻


推薦的表單庫(kù)

react中推薦react-hook-form,formik,這倆個(gè)都很強(qiáng)大,但是其實(shí)antd提供的表單已經(jīng)能滿足大部分的需求了。

vue目前我只知道餓了么。


關(guān)于低代碼表單

這玩意個(gè)人認(rèn)為只能用于非常固定的業(yè)務(wù)場(chǎng)景,例如一般的問(wèn)卷調(diào)查,他就是非常固定的一些表單輸入項(xiàng),后期也不會(huì)加復(fù)雜的東西,那就非常適合用低代碼去搭一個(gè)后臺(tái)。

我還遇到過(guò)一個(gè)用法,就是之前待過(guò)的一個(gè)公司里后臺(tái)管理很多表單用的也是低代碼生成的。機(jī)制是這樣的這些表單配置都是在后臺(tái)配出來(lái)的,然后前端每次渲染頁(yè)面的時(shí)候會(huì)通過(guò)接口拉取表單的json配置數(shù)據(jù),前端開發(fā)人員把配置數(shù)據(jù)傳入對(duì)應(yīng)的低代碼組件,然后這個(gè)低代碼組件有一些拓展功能要開發(fā)人員自己調(diào)試。這樣做出來(lái)的表單的一個(gè)好處是,當(dāng)項(xiàng)目上線了,突然要改表單的某些選項(xiàng),直接后臺(tái)通過(guò)低代碼把對(duì)應(yīng)的輸入項(xiàng)做調(diào)整就可以了,完全不用再去改項(xiàng)目代碼,反應(yīng)迅速。

掘金上有篇討論可以看看【低代碼:現(xiàn)在我怎么樣了】

關(guān)于低代碼的開源項(xiàng)目參考:

  • 這個(gè)比較成熟商用級(jí)別的:https://lowcode-engine.cn/index
  • 這個(gè)比較適合用來(lái)學(xué)習(xí)簡(jiǎn)單低代碼的搭建:https://buqiyuan.gitee.io/vite-vue3-lowcode/#/

關(guān)于動(dòng)態(tài)表單

動(dòng)態(tài)表單我個(gè)人的方案是【業(yè)務(wù)組件二次封裝】里,把所有要顯示的表單項(xiàng)id維護(hù)在一個(gè)數(shù)組中,公共組件根據(jù)這個(gè)數(shù)組去顯示對(duì)應(yīng)的表單。

具體可以看PzFormGeneral.vue里的displayList部分

這種方式的好處就是很靈活,基本滿足所有場(chǎng)景,不好的地方就是每個(gè)能觸發(fā)動(dòng)態(tài)表單切換的表單項(xiàng)都要維護(hù)一個(gè)數(shù)組,稍顯麻煩。

另外一種方式是從渡一教育哪里看過(guò)來(lái)的,每個(gè)組件的配置都有一個(gè)屬性,這個(gè)屬性是一個(gè)函數(shù),他內(nèi)部處理對(duì)應(yīng)邏輯返回下一個(gè)要顯示或者隱藏的表單項(xiàng)配置。

就類似鏈表的設(shè)計(jì)了,好處就是簡(jiǎn)單,都維護(hù)在配置項(xiàng)中,不好的地方在于鏈表關(guān)系,無(wú)法做到第2項(xiàng)影響第4項(xiàng)之后的表單項(xiàng)。


一些數(shù)據(jù)上的處理技巧

數(shù)組轉(zhuǎn)字符串

有時(shí)候我們表單中的某個(gè)輸入項(xiàng)拿到的值是一個(gè)數(shù)組,但是后端需要的是用逗號(hào)分開區(qū)分的字符串。咱們可以直接:

let arr = [1,2,3]
console.log(arr.toString()) // '1,2,3'
console.log(arr.join(',')) // '1,2,3'

日期的轉(zhuǎn)換

咱們用組件庫(kù)表單的數(shù)據(jù)獲取api拿到的日期輸入項(xiàng)的數(shù)據(jù)一般都是帶有組件定義的格式的,例如ant-design拿到的一般是個(gè)Moment對(duì)象,后端可能需要的是yyyy-dd-mm的格式,這些我們都可以提前準(zhǔn)備好公共函數(shù)統(tǒng)一處理的。

類似的不止日期啦,學(xué)會(huì)舉一反三文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-470791.html

到了這里,關(guān)于【react框架】結(jié)合antd做表單組件的一些心得記錄的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 在react antd中動(dòng)態(tài)生成多個(gè) form表單組,包括一個(gè)動(dòng)態(tài)添加/刪除表單項(xiàng)的功能和一個(gè)提交表單的功能

    在這個(gè)示例中,我們首先使用 Form.useForm() 創(chuàng)建一個(gè)表單實(shí)例。接著,我們使用 Form.List 組件來(lái)動(dòng)態(tài)生成多個(gè)表單項(xiàng)。在 Form.List 組件中,我們使用 fields.map 方法循環(huán)渲染每個(gè)表單項(xiàng),并使用 Form.Item 組件包裹每個(gè)表單項(xiàng)。在 Form.Item 組件中,我們使用 label 屬性指定標(biāo)簽,使用

    2024年02月15日
    瀏覽(23)
  • React修改Antd組件的樣式

    React修改Antd組件的樣式

    修改默認(rèn)的antd組件,需要使用 global 修改后Steps樣式 為什么需要這樣寫呢? 因?yàn)槲覀儐?dòng)了 CSS Modules ,它是一種技術(shù)流的組織css代碼的策略,它將為css提供默認(rèn)的局部作用域。因?yàn)闃?gòu)建工具會(huì)在編譯的時(shí)候自動(dòng)把我們的類名加上一個(gè)哈希字符串,例如上面我們寫的類名為t

    2024年02月11日
    瀏覽(25)
  • React antd upload組件上傳視頻并實(shí)現(xiàn)視頻預(yù)覽

    記錄問(wèn)題:antd的upload組件文檔中對(duì)于視頻的上傳預(yù)覽沒(méi)有明確的文檔demo,在這里記錄一下 項(xiàng)目需求:支持圖片及視頻的上傳并實(shí)現(xiàn)預(yù)覽,點(diǎn)擊上傳后不會(huì)立即請(qǐng)求接口上傳資源,后續(xù)點(diǎn)擊確定再上傳 上代碼

    2024年02月04日
    瀏覽(30)
  • React + Typescript + Antd:封裝通用的字典組件DXSelect

    在開發(fā)中,我們經(jīng)常遇到這樣的場(chǎng)景,在表單中,有個(gè)下拉框,選擇對(duì)應(yīng)的數(shù)據(jù)。 那么這個(gè)下拉框的選項(xiàng),就是字典。一搬的做法是,通過(guò)antd的Select來(lái)實(shí)現(xiàn),代碼如下:

    2024年02月15日
    瀏覽(42)
  • 【react + antd】antd如何自定義請(qǐng)求使用antd的upload組件實(shí)現(xiàn)圖片上傳且可預(yù)覽可刪除

    【react + antd】antd如何自定義請(qǐng)求使用antd的upload組件實(shí)現(xiàn)圖片上傳且可預(yù)覽可刪除

    官網(wǎng)給出的案例無(wú)法使用封裝好的請(qǐng)求方式上傳圖片,以及 無(wú)法滿足上傳圖片后獲取接口url、名稱等信息的的業(yè)務(wù)需求 。這個(gè)時(shí)候需要用到customRequest這個(gè)api。 但是很遺憾,官網(wǎng)沒(méi)有給出具體案例。 不過(guò)——博主自己試出來(lái)了( ̄︶ ̄) 要使用upload,特別重要的屬性就是file

    2024年02月17日
    瀏覽(29)
  • react Hook+antd封裝一個(gè)優(yōu)雅的彈窗組件

    前言 在之前學(xué)vue2的時(shí)候封裝過(guò)一個(gè)全局的彈窗組件,可以全局任意地方通過(guò)this調(diào)用,這次大創(chuàng)項(xiàng)目是用react技術(shù)棧,看了一下項(xiàng)目需求,突然發(fā)現(xiàn)彈窗還是比較多的,主要分為基礎(chǔ)的彈窗以及form表單式的彈窗,如果只是無(wú)腦的去寫代碼,那些項(xiàng)目也沒(méi)啥必要了。正好react和

    2024年02月13日
    瀏覽(23)
  • React使用antd的圖片預(yù)覽組件,點(diǎn)擊哪個(gè)圖片就預(yù)覽哪個(gè)的設(shè)置

    React使用antd的圖片預(yù)覽組件,點(diǎn)擊哪個(gè)圖片就預(yù)覽哪個(gè)的設(shè)置

    使用了官方推薦的相冊(cè)模式的預(yù)覽,但是點(diǎn)擊預(yù)覽之后,每次都是從圖片列表的第一張開始預(yù)覽,而不是點(diǎn)擊哪張就從哪張開始預(yù)覽: 所以這里我就封裝了一下,對(duì)初始化預(yù)覽的列表進(jìn)行了邏輯處理: 當(dāng)點(diǎn)擊開始預(yù)覽的時(shí)候,要找到當(dāng)前圖片在預(yù)覽圖列表中的索引,然后設(shè)

    2024年02月13日
    瀏覽(30)
  • 【React】如何簡(jiǎn)單快速地修改antd組件UI內(nèi)部樣式如字體顏色

    【React】如何簡(jiǎn)單快速地修改antd組件UI內(nèi)部樣式如字體顏色

    最近剛開始學(xué)習(xí)react 在寫一個(gè)登錄的頁(yè)面 發(fā)現(xiàn)組件的顏色不太合適,默認(rèn)是黑色字體 那我想修改成白色字體以適應(yīng)我的頁(yè)面 運(yùn)用多種css文件打包策略太過(guò)復(fù)雜 對(duì)我這種小白不友好 兩行代碼搞定 實(shí)現(xiàn)需求 通過(guò):global加上!important 在Umi項(xiàng)目中,在global.less文件夾下面,通過(guò)roo

    2024年02月13日
    瀏覽(40)
  • React antd如何實(shí)現(xiàn)<Upload>組件上傳附件再次上傳已清除附件緩存問(wèn)題

    React antd如何實(shí)現(xiàn)<Upload>組件上傳附件再次上傳已清除附件緩存問(wèn)題

    最近遇到一個(gè)React上傳組件的問(wèn)題,即上傳附件成功后,文件展示處仍然還有之前上傳附件的緩存信息,需要解決的問(wèn)題是,要把上一次上傳的附件緩存在上傳成功或者取消后,可以進(jìn)行清除 經(jīng)過(guò)一頓試錯(cuò),終于解決了這個(gè)問(wèn)題。 showUploadList,是可選參數(shù),即是否展示upload

    2024年02月04日
    瀏覽(60)
  • react18+antd5.x(1):Notification組件的二次封裝

    react18+antd5.x(1):Notification組件的二次封裝

    antdesign已經(jīng)給我們提供了很好的組件使用體驗(yàn),但是我們還需要根據(jù)自己的項(xiàng)目業(yè)務(wù)進(jìn)行更好的封裝,減少我們的代碼量,提升開發(fā)體驗(yàn) 開起來(lái)和官網(wǎng)的使用沒(méi)什么區(qū)別,但是我們?cè)谑褂玫臅r(shí)候,進(jìn)行了二次封裝,更利于我們進(jìn)行開發(fā) MyNotification.jsx,是我們的業(yè)務(wù)頁(yè)面 Notif

    2024年02月11日
    瀏覽(26)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包