封装表单元素的架构设计

如果你点开了这篇文章,相信你是一个有经验的前端开发了,造轮子可能已经不能满足我们的成就感了。想当初我吭哧吭哧做了一套Angular的组件库别提多开心了,但是毕竟是自己一个人短期内开发出来的,也只有input,select之类的几个基础组件,前端小伙伴又凭什么宁愿放弃现有的成熟的组件库,转而用我的呢?既然AntDesign这样的组件库已经如此成熟,我又何必班门弄斧?但我发现一个夹缝求生的地方,像AntDesign,SemiDesign,MuiDesign这类组件库,本质是定义的样式,很多组件拿到项目中,还是需要封装一遍,尤其是做简化代码甚至低代码的工作中,将现有的组件库封装成一个可配置化的组件库就尤为重要了。

我们在这篇文章中介绍了封装表单组件需要准备哪些属性:封装表单元素,应该定义哪些状态或属性?。我们可以知道有一些表单元素有共同的属性,有一些有特异性,比如单行文本输入框text input,数字输入框number input,和多行文本输入框textarea都需要监听输入的变化,而select和autocomplete都是以选项的变化来改变值的。

本着"高内聚,低耦合"的宗旨,我们将封装分为不同的层级,这里我用不同的目录层级来表示,可以参考右边的大纲。不常用的组件后期我会持续更新,感兴趣可以先收藏。

FreeForm

在form层面,也就是field的外层需要对单个field处理如下属性:

  1. 【值属性】根据field的getDefaultValue()计算好初始值。传递defaultValue。默认值属性相互之间是有冲突的,后面我会详细设计这其中的优先级。
  2. 【值属性】如果组件是初始无值的状态,将defaultValue赋值给value。如果组件已经有值,则作为value。传递value
  3. 【值属性】根据field的onChange()改变value
  4. 【值属性】根据field的onInputChange()改变inputValue
  5. 【值属性】传递rootFormValue
  6. 【值属性】传递哪个form里哪个field的值变化了。
  7. 【控制类属性】根据field的enabledOn计算enabled状态。传递enabled
  8. 【控制类属性】根据field的visibleOn计算visible状态。传递visible
  9. 【控制类属性】根据field的availableOn计算available状态。传递available
  10. 【控制类属性】根据field的requiredOn计算required状态。传递required
  11. 【用户行为类属性】根据field的onBlur()改变touched状态。传递touched
  12. 【用户行为类属性】根据field的onChange()改变dirty状态。传递dirty
  13. 【用户行为类属性】根据field的onChange()value是否等同于defaultValue来计算different状态。传递different
  14. 【用户行为类属性】传递clearable,当clearabletrue时,根据field的onClear()value清空。
  15. 【用户行为类属性】传递resettable,当resettabletrue时,根据field的onReset()valueinputValuetoucheddirty重置。
  16. 【用户行为类属性】传递deletable,当deletabletrue时,根据field的onDelete()available改为false,意味着这个field不可用,对应的字段将被移除。
  17. 【辅助类属性】传递labelkeytitledisabledReason(disabled时,title选用展示disableReason), helperTextplaceholder
  18. 【辅助类属性】传递path,当有嵌套form时,path将帮助我们快速定位到field,比如:univerity.department.class.student.name
  19. 【样式类属性】传递classNamewidthsizetabIndex
  20. 【校验类属性】传递validations
  21. 【校验类属性】传递errors
  22. 【多组件组合】需要支持多field+button组合,如下图:
  23. 【辅助类属性】传递label
  24. 【辅助类属性】传递key/name
  25. 【辅助类属性】传递title
  26. 【辅助类属性】传递helperText
  27. 【辅助类属性】传递placeholder
jsx 复制代码
const { type, getDefaultValue } = props;
const defaultValue = getDefaultValue(...);
const [value, setValue] = useState(defaultValue);
const [inputValue, setInputValue] = useState(defaultValue);

const enabled = useMemo(() => enabledOn(), [...]);
const visible = useMemo(() => visibleOn(), [...]);
const available = useMemo(() => availableOn(), [...]);
const required = useMemo(() => requiredOn(), [...]);

const [touched, setTouched] = useState(false);
const [dirty, setDirty] = useState(false);
const [different, setDifferent] = useState(false);

const handleChange = (value) => {
    setValue(value);
    setTouched(true);
    setDifferent(!isEqual(value, defaultValue));
}

return <FormControl>
    <BaseField
        type={type}
        value={value}
        defaultValue={defaultValue}
        label={label}
        key={key}
        title={title}
        disableReason={disableReason}
        helperText={helperText}
        placeholder={placeholder}
        path={path}
        className={classname}
        width={width} // '200px', '80%'
        size={size}  // 'sm', 'md', 'lg'
        validations={validations}
        tabIndex={tabIndex}
        onChange={handleChange}
        onInputChange={setInputValue}
        {...(clearable && {onClear: handleClear})} // 将value清空
        {...(resettable && {onClear: handleReset})}  // 将value重置为最开始的值(不一定是defaultValue)
        {...(deletable && {onClear: handleDelete})}  // availble改为false
        {...(mentionable && onMention: handleMention)}  // @某人
        onValidated={handleValidated}
    />
</FormControl>

BaseField

多个BaseField组成FreeForm。面向的是form,如果是面向开发者/使用者,参考下面的FreeFormField。

jsx 复制代码
<BaseField .../>
  1. 【值属性】传递value。
  2. 【值属性】传递defaultValue。
  3. 【校验类属性】接受validations。将validations的结果通过onValidated()返回给form。validations和对应的errors的属性定义见这一篇:正在写。。。
  4. 【校验类属性】传递errors。对单独的field显示错误信息有两种方式,一个是传给原生组件,一个是封装自己的错误样式,这取决于我们用什么样的组件库。validations和对应的errors的属性定义见这一篇:正在写。。。
  5. 【校验类属性】传递下面各类field所需的校验类属性。

1. BaseTypeable

打字型输入。这是最基本的组件,后面很多组件都需要这个组件。

jsx 复制代码
<BaseField type="text" .../> ==> <BaseTypeable type="text" .../>
<BaseField type="number" .../> ==> <BaseTypeable type="number" .../>
<BaseField type="textarea" .../> ==> <BaseTypeable type="textarea" .../>
<BaseField type="password" .../> ==> <BaseTypeable type="password" .../>

继承以上BaseField的所有属性以外,还可以:

  1. 【值属性】【性能】支持debounce。value change可选择防抖,比如用户type一个字符约1s后改变field的值。
  2. 需要支持前缀后缀,如下图:
  3. 【用户行为类】如果deletable为true,提供一个按钮点击返回onDelete()。(clear,reset,delete行为仅返回event,field内部不做值的处理,值的处理在form内,field外处理。)

1-1. BaseText

单行文本输入框。

js 复制代码
<BaseTypeable type="text" .../> ==> <BaseText .../>

继承以上BaseTypeable的所有属性以外,还可以:

  1. 【校验类属性】字数限制,接受minLengthmaxLength,传递给adaptor。这两个属性是原生属性,我们尽量使用原生功能,更高效。
  2. 【校验类属性】字数显示/倒计数,如下图:
  3. 【校验类属性】接受pattern,传递给adaptor。
  4. 【用户行为类属性】如果clearabletrue,提供一个按钮点击返回onClear()
  5. 【用户行为类属性】如果resettabletrue,提供一个按钮点击返回onReset()
  6. 【用户行为类属性】语音输入。(如果你想做得狂拽炫酷吊炸天可以试试)
  7. 【用户行为类属性】接受mode,适用于移动端的原生属性。
  8. 【值属性】接受format

1-2. BaseNumber

数字输入框。有加减按钮。

即:

js 复制代码
<BaseTypeable type="number" .../> ===> <BaseNumber .../>

继承以上BaseTypeable的所有属性以外,还可以:

  1. 【控制类属性】可通过键盘控制增加减少,大多数组件库本来就支持。
  2. 【校验类属性】接受maxmin,和step,原生功能。
  3. 【校验类属性】接受numberType: integer, decimal,
  4. 【值属性】接受format格式化数字,比如货币,科学计数法,电话号码,银行卡号,如下图:
  5. 【值属性】支持高精度,如下图,封装的时候需要装BigInt polyfill。

1-3. BasePassword

密码输入框,可以选择显示明文密码。如下图:

js 复制代码
<BaseTypeable type="password" .../> ===> <BasePassword .../>

继承以上BaseTypeable的所有属性以外,还可以:

  1. 【用户行为类属性】是否可以显示明文密码。 密码的报错信息可能不同于其他field,需要显示校验列表,并标出哪些满足了哪些不满足。这部分参见Password组件。

1-4. BaseTextarea

多行文本输入框。

js 复制代码
<BaseTypeable type="textarea" .../> ===> <BaseTextarea .../>

继承以上BaseTypeable的所有属性以外,还可以:

  1. 【校验类属性】字数限制,接受minLengthmaxLength,传递给adaptor。这两个属性是原生属性,我们尽量使用原生功能,更高效。
  2. 【校验类属性】字数显示/倒计数,类似于BaseText。
  3. 【样式类属性】接受rows,默认显示多少行。
  4. 【样式类属性】接受resizable,可以自定义输入框大小。
  5. 【用户行为类属性】可以mention@某人,mention的属性参考下面BaseMention组件。

1-5. BaseTags

tag输入框。如下图:

2. BaseBinary

Boolean类的输入。也是基础组件,后面很多复合组件也会用到这里的组件。

js 复制代码
<BaseField type="radio" .../> ==> <BaseBinary type="radio" .../>
<BaseField type="checkbox" .../> ==> <BaseBinary type="checkbox" .../>
<BaseField type="switch" .../> ==> <BaseBinary type="switch" .../>

继承以上BaseField的所有属性以外,还可以:

  1. 【值属性】处理change event里的checked,改为value
  2. 【值属性】常见多个binary field组合成一个selectable 组件,这里值的处理需要符合selectable field的用法。
  3. 【辅助类属性】label是指这个field的label,而binary的单个field也有它们自己的label,具体见下面各个field。传递binary label。

2-1. BaseRadio

单个Radio。一旦勾选无法取消。

js 复制代码
<BaseBinary type="radio" .../> ===> <BaseRadio .../>

继承以上BaseBinary的所有属性以外,还可以:

  1. 【值属性】返回值为boolean
  2. 【辅助类属性】接受optionLabel,作为这一个选项的label。

2-2. BaseCheckbox

单个Checkbox。可勾选可取消,还有一个中间态(依然放在binary里面,因为中间态是给treeSelect这种复合组件用的)。 单个Radio。一旦勾选无法取消。

js 复制代码
<BaseBinary type="checkbox" .../> ===> <BaseCheckbox .../>

继承以上BaseBinary的所有属性以外,还可以:

  1. 【值属性】接受ternary,当ternarytrue时,值有三种状态,用数字0,1,2表示。否则返回值为boolean
  2. 【辅助类属性】接受optionLabel,作为这一个选项的label。

2-3. BaseSwitch

Switch。如下图:

js 复制代码
<BaseBinary type="switch" .../> ===> <BaseSwitch .../>

继承以上BaseBinary的所有属性以外,还可以:

  1. 【值属性】返回值为boolean
  2. 【辅助类属性】接受labelLeft,作为显示在左边的label。
  3. 【辅助类属性】接受labelRight,作为显示在右边的label。大多数情况,只需要labelRight就可以了。

3. BaseSelectable

选择类的输入。属于复合组件了,有些会用到上面的基础组件。为了方便TypeScript定义类型,我将带有下拉框的组件分为单选和多选。

js 复制代码
<BaseField type="select" .../> ==> <BaseSelectable type="select" .../>
<BaseField type="autocomplete" .../> ==> <BaseSelectable type="autocomplete" .../>
// <BaseField type="cascader" .../> ==> <BaseSelectable type="cascader" .../> 待定
<BaseField type="treeSelect" .../> ==> <BaseSelectable type="treeSelect" .../>
<BaseField type="radios" .../> ==> <BaseSelectable type="radios" .../>
<BaseField type="checkboxes" .../> ==> <BaseSelectable type="checkboxes" .../>

继承以上BaseField的所有属性以外,还可以:

  1. 【值属性】单选的值为单个值,多选的值为一个数组。
  2. 【值属性】接受value,并计算出选项中与之同等的值,即selectedOption/selectedOptions
  3. 【值属性】接受allowValueOutOfValue,当这个属性为true时,value改变后必须是选项中的一个,否则会被还原为上一次的值。考虑到后台可能会有脏数据,value在没做任何改变时还是可以照本来的value显示。
  4. 【值属性】接受getDefaultValue(),这个方法决定了当某些依赖项(如某些其他field的值,选项)改变时,这个field默认改变value。
  5. 【值属性】接受isOptionEqualToValue(),用这个方法通过value选择对应的option。
  6. 【选项类属性】选项类属性较为复杂,请参考这一篇文章:正在写。。。

3-1. BaseDropdown

带下拉框的选择组件。

js 复制代码
<BaseField type="dropdown" multiple={true} .../> ==> <BaseSingleDropdown .../>
<BaseField type="dropdown" multiple={false} .../> ==> <BaseMultiDropdown .../>

继承以上BaseSelectable的所有属性以外,还可以:

  1. 【性能】虚拟列表
  2. 【选项类属性】异步load options时,会有输入状态告知用户。

3-1-1. BaseSingleDropdown

带下拉框的单选组件。

js 复制代码
<BaseSingleDropdown .../>

继承以上BaseDropdown的所有属性以外,还可以:

  1. 【值属性】当clearabletrue时,选项中第一位会为一个带placeholder的空选项:
js 复制代码
// options[0]
{
    label: '-- Select --',
    value: ''
}

这样用户选择了某些值之后,还可以通过选择这个值来清空。

  1. 【值属性】接受defaultSelectFirst,当这个属性为true时,表示初始状态或选项更新后,默认选择第一个选项。

3-1-2. BaseMultiDropdown

带下拉框的多选组件。

js 复制代码
<BaseMultiDropdown .../>

继承以上BaseDropdown的所有属性以外,还可以:

  1. 【样式类属性】接受limitTags参数,定义在输入框里显示多少个选项。
  2. 【值属性】接受defaultSelectAll,意味着默认全选。

3-2. BaseAutocomplete

带下拉框和自动填充的选择组件。

js 复制代码
<BaseField type="autocomplete" multiple={true} .../> ==> <BaseSingleAutocomplete .../>
<BaseField type="autocomplete" multiple={false} .../> ==> <BaseMultiAutocomplete .../>

继承以上BaseSelectable的所有属性以外,还可以:

  1. 【值属性】当allowValueOutOfValuetrue时,用户可以自由输入。
  2. 【值属性】接受format,当用户可以自由输入时,如果这个组件值的类型较复杂,可以通过这个方法得到符合格式要求的值。
  3. 【选项类属性】接受optionAddable,当用户自由输入了一个新值后,可以把这个新值加入到option list中(异步操作会发送请求,同步操作会存到状态里)。
  4. 【选项类属性】异步load options时,会有输入状态告知用户。既可以初始时一次性异步获取options,也可以随着输入异步获取options。

3-2-1. BaseSingleAutocomplete

带下拉框和自动填充的单选组件。

js 复制代码
<BaseSingleAutocomplete .../>

继承以上BaseAutocomplete的所有属性以外,还可以:

  1. 【值属性】接受clearable,当这个属性为true时,选项中第一位会为一个带placeholder的空选项:
js 复制代码
// options[0]
{
    label: '-- Select --',
    value: ''
}

这样用户选择了某些值之后,还可以通过选择这个值来清空。

  1. 【值属性】接受defaultSelectFirst,当这个属性为true时,表示初始状态或选项更新后,默认选择第一个选项。

3-2-2. BaseMention

@功能,是由BaseSingleAutocomplete变换而成,type"@"后,随着输入可以异步查询人名。一般常用在Textarea里。如下图:

js 复制代码
<BaseMention .../>

继承以上BaseSingleAutocomplete的大部分属性以外,还可以:

  1. 【选项类属性】异步获取人员信息,像Jira一样。
  2. 【值属性】选择好后,@后面其实只是可编辑可删除的文本。

3-2-3. BaseMultiAutocomplete

带下拉框和自动填充的多选组件。

js 复制代码
<BaseMultiAutocomplete .../>

继承以上BaseAutocomplete的所有属性以外,还可以:

  1. 【样式类属性】接受limitTags参数,定义在输入框里显示多少个选项。
  2. 【值属性】接受defaultSelectAll,意味着默认全选。

3-3. BaseRadios

Radio组合的单选组件。

js 复制代码
<BaseField type="radios" .../> ==> <BaseRadios .../>

继承以上BaseSelectable的所有属性以外,还可以:

  1. 【选项类属性】接受'optionsDirection',定义options的排列方向,是横向还是垂直,row还是column

3-4. BaseCheckboxes

Checkbox组合的多选组件。

js 复制代码
<BaseField type="checkboxes" .../> ==> <BaseCheckboxes .../>

继承以上BaseSelectable的所有属性以外,还可以:

  1. 【选项类属性】接受'optionsDirection',定义options的排列方向,是横向还是垂直,row还是column

3-5. BaseTreeDropdown

下拉框是树组件的选择组件。

js 复制代码
<BaseField type="tree" multiple={false} .../> ==> <BaseSingleTreeDropdown .../>
<BaseField type="tree" multiple={true} .../> ==> <BaseMultiTreeDropdown .../>

继承以上BaseSelectable的所有属性以外,还可以:

  1. 【选项类属性】接受searchable,类似autocomplete组件,可以将带有关键字的节点筛选出来。
  2. 【选项类属性】父节点展开时可以定义lazyload,异步获取子节点。

3-5-1. BaseTree

基础的树组件,独立存在,不在下拉框里。如果BaseTreeDropdown的searchablefalse,则选用这个组件。

js 复制代码
<BaseSingleTreeDropdown searchable={false}.../> 封装 <BaseSearchTree .../>
<BaseMultiTreeDropdown searchable={false}.../> 封装 <BaseSearchTree .../>

树组件需要带有select功能,具体设计参考这篇文章:正在写。。。

3-5-2. BaseSearchTree

带搜索功能的树组件,独立存在,不在下拉框里。将BaseTree与搜索框封装在了一起。如果BaseTreeDropdown的searchabletrue,则选用这个组件。

js 复制代码
<BaseSingleTreeDropdown searchable={true}.../> 封装 <BaseSearchTree .../>
<BaseMultiTreeDropdown searchable={true}.../> 封装 <BaseSearchTree .../>

3-5-3. BaseSingleTreeDropdown

下拉框是树组件的单选组件。

js 复制代码
<BaseSingleTreeDropdown .../>

3-5-4. BaseMultiTreeDropdown

下拉框是树组件的多选组件。

js 复制代码
<BaseMultiTreeDropdown .../>

4. BaseFile

文件上传组件。可以通过打开文件系统选择,也可以拖拽上传,也可以组合起来。

js 复制代码
<BaseField type="fileSelect" .../> ==> <BaseFile type="fileSelect" .../>
<BaseField type="fileArea" .../> ==> <BaseFile type="fileArea" .../>
<BaseField type="fileReview" .../> ==> <BaseFile type="fileReview" .../>

继承以上BaseField的所有属性以外,还可以:

  1. 【值属性】如果是已有值,需要用到BaseFileReview。
  2. 【值属性】定义file的值,需要区分文件本身和文件名。
  3. 【值属性】接受multiple,可以选择单文件还是多文件上传。
  4. 【校验类属性】接受accept,限制上传的文件类型。
  5. 【校验类属性】接受maxSize,限制上传的文件大小。
  6. 【用户行为类属性】上传的方式,文件切片,是否可以中止或继续。
  7. 【样式类属性】上传过程中,是进度条还是圆环。

4-1. BaseFileSelect

输入框式的文件选择器。

js 复制代码
<BaseFile type="fileSelect" .../> ==> <BaseFileSelect .../>

继承以上BaseFile的所有属性

4-2. BaseFileArea

Drag & Drop式的文件上传。

js 复制代码
<BaseFile type="fileArea" .../> ==> <BaseFileArea .../>

继承以上BaseFile的所有属性以外,还可以:

  1. 【样式类属性】接受size,拖拽区域的大小。

4-3. BaseFileReview

上传后的文件预览。这个组件不属于formField组件,仅用来展示。

js 复制代码
<BaseFile type="fileReview" .../> ==> <BaseFileReview .../>

5. BaseMoment

日期时间选择组件。

js 复制代码
<BaseField type="date" .../> ==> <BaseMoment type="date" .../>
<BaseField type="week" .../> ==> <BaseMoment type="week" .../>
<BaseField type="month" .../> ==> <BaseMoment type="month" .../>
<BaseField type="time" .../> ==> <BaseMoment type="time" .../>
<BaseField type="datetime" .../> ==> <BaseMoment type="datetime" .../>

继承以上BaseField的所有属性以外,还可以:

  1. 【值属性】接受format,比如:'DD MMM YYYY'

5-1. BaseDate

选择日期。

js 复制代码
<BaseMoment type="date" .../> ==> <BaseDate .../>

继承以上BaseMoment的所有属性

5-2. BaseWeek

选择周。

js 复制代码
<BaseMoment type="week" .../> ==> <BaseWeek .../>

继承以上BaseMoment的所有属性

5-3. BaseMonth

选择月。

js 复制代码
<BaseMoment type="month" .../> ==> <BaseMonth .../>

继承以上BaseMoment的所有属性

5-4. BaseTime

选择时间。

js 复制代码
<BaseMoment type="time" .../> ==> <BaseTime .../>

继承以上BaseMoment的所有属性

5-5. BaseDateTime

选择日期和时间。

js 复制代码
<BaseMoment type="datetime" .../> ==> <BaseDateTime .../>

继承以上BaseMoment的所有属性

6. BaseRange

范围类组件,field值会是包含两个值的数组。

js 复制代码
<BaseField type="dateRange" .../> ==> <BaseRange type="dateRange" .../>
<BaseField type="numberRange" .../> ==> <BaseRange type="numberRange" .../>

继承以上BaseField的所有属性

6-1. BaseDateRange

日期范围。

js 复制代码
<BaseRange type="dateRange" .../> ==> <BaseDateRange />

继承以上BaseRange的所有属性

6-2. BaseNumberRange

日期范围。

js 复制代码
<BaseRange type="numberRange" .../> ==> <BaseNumberRange />

继承以上BaseRange的所有属性

7. BaseSlider

滑块组件,field值会是单个数字,或是数字数组。

js 复制代码
<BaseField type="slider" .../> ==> <BaseSlider .../>

继承以上BaseField的所有属性

8. BaseColor

颜色选择器。

js 复制代码
<BaseField type="color" .../> ==> <BaseColor .../>

继承以上BaseField的所有属性

9. BaseRating

五星好评选择器。

js 复制代码
<BaseField type="rating" .../> ==> <BaseRating .../>

继承以上BaseField的所有属性

FormField

以上BaseField是放在FreeForm里运行的,form中会计算值,做校验等操作,并不适合单独拿出来使用,为了能让开发者更方便地使用form field,需要将每个BaseField封装成FormField,比如BaseText,开发者可以单独使用Text组件,BaseSingleDropdown对应SingleDropdown,这样做的好处如下:

  1. 这样的组件已经将值计算和校验等操作封装在其中了,开发者使用起来会非常轻松。
  2. 有利于做集成测试。
  3. 有利于写开发文档。
  4. 给未来如果需要将组件做成动态组件做准备。

总结

我们开发的重点并不是在组件本身,事实上,我打算直接用现有的组件库,由于需要通用不同的组件库,每个组件库对应的接口不同,因此需要特制Adaptor,比如在我们的框架中格式化方法叫"format",而某个组件库的同样功能的方法名叫"formatter",另一个组件库这个方法名叫"formateValue",这样Adaptor的重要性就在于此,仅仅只是通过变换名字,将不同的组件库接入进来,这种方式也是coding浪费最少的方式了。

详细Adaptor的设计这在写。。。 接下来,是FreeForm和FormArray的封装:正在写。。。

相关推荐
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
多客软件佳佳13 小时前
校园交友系统的设计与实现(开源版+三端交付+搭建+售后)
小程序·前端框架·uni-app·开源·php·交友
豆华14 小时前
React 中 为什么多个 JSX 标签需要被一个父元素包裹?
前端·react.js·前端框架
练习两年半的工程师14 小时前
使用React和Vite构建一个AirBnb Experiences克隆网站
前端·react.js·前端框架
林太白14 小时前
❤React-JSX语法认识和使用
前端·react.js·前端框架
女生也可以敲代码14 小时前
react中如何在一张图片上加一个灰色蒙层,并添加事件?
前端·react.js·前端框架
布兰妮甜15 小时前
前端框架大比拼:React.js, Vue.js 及 Angular 的优势与适用场景探讨
前端·vue.js·react.js·前端框架·angular.js
真的很上进16 小时前
⚡️如何在 React 和 Next.js 项目里优雅的使用 Zustand
java·前端·javascript·react.js·前端框架·vue·es6
@大迁世界16 小时前
释放 PWA 的力量:2024 年的现代Web应用|React + TypeScript 示例
前端·javascript·react.js·前端框架·ecmascript