写点什么

Antd Form 实现机制解析

  • 2021-03-13
  • 本文字数:10565 字

    阅读完需:约 35 分钟

Antd Form 实现机制解析

背景


在中后台业务中,表单页面基础的场景包括组件值的收集、校验和更新。在 to B 业务中,表单页面的需求往往更复杂和定制化,除了上述的基本操作,还要处理包括自定义表单组件、表单联动、表单的异步检验等复杂场景,在一些大型表单页面中还要考虑性能的问题,表单页面的需求往往是新同学摔得第一个跤。


本文分为两个部分,第一部分会通过对 Antd Form 源码的分析来帮助大家对 Form 的整体设计和流程有一个清晰的概念,第二部分会分享一些复杂场景的解决方案。希望可以帮助大家更容易的处理表单需求和快速定位表单场景中的问题。


本文并不涉及过于具体的源码实现分析,大家可以放松心情,一起来对 Form 有一个感性的认知吧~


Form 组件解决了什么问题


首先我们先看一个简单的表单,收集并校验两个组件的值。只需要通过监听两个表单组件的 onChange 事件,获取表单项的 value,根据定义的校验规则对 value 进行检验,生成检验状态和检验信息,再通过 setState 驱动视图更新,展示组件的值以及校验信息即可。


代码实现可能是这样的:


exportdefaultclass LoginForm extends React.Component {  state = {    username:{      value: '',      error: '',    },    password:{      value: '',      error: '',    },  }    fieldMeta = {    username:{    rules:[],  },    password:{    rules:[],   }, }    onInputChange = (e) => {   const { value,name } = e.target; 	// 获取校验结果   const error = this.doValidate(value, name);      this.setState({     [name]:{       value,       error,     }   })  }    validator = (value, rules) => {  	...  }    doValidate = (value, name) => {    // 读取校验规则    const { rules } = this.fieldMeta[name];    return validator(value,rules);  }
render() { const { username, password } = this.state; return (<div> <div> <Input onChange={this.onInputChange} name='username' value={username.value} /> </div> <div style={errorStyle}> {username.error} </div> <div> <Input onChange={this.onInputChange} name='password' value={password.value}/> </div> <div style={errorStyle}> {password.error} </div> <Button onClick={this.onSubmit}>登录</Button> </div>); }}
复制代码

用流程图来表示是这样的:


上面的实现,我们设定了一个表单数据状态的模型,来维护组件的 value 和校验的错误信息。


this.state = {  [field1]:{    value: '',    error: '',  },  [field2]:{    value: '',    error: '',  },  ...}
复制代码

还有一个字段配置相关的模型,维护组件对应的校验规则。

fieldMeta = {  username:{    rules:[]  },  password:{    rules:[],  }}
复制代码

对于这种简单的业务场景,上述方式完全可以满足需求。


具体到真实的业务场景,往往更复杂,其中包含多种表单组件,如 Input、Checkbox、Radio、Upload,还有一些自定义表单组件。



这个时候如果继续采用这种方式,不仅需要维护多个 onChange 事件,还要对不同组件 value 的取值做差异化处理,以及对各个组件的校验以及触发时机规则进行维护,就很容易出现“祖传代码”。


对表单场景进行归纳,可以发现每个组件的数据收集、校验、数据更新的流程其实是一致的。对这个流程进行抽象,并且通过一些配置屏蔽组件间的差异性,再对组件的值以及组件的配置规则统一管理,就是我们常见的 Form 表单的解决方案。


Antd Form 是怎么实现的


要实现上面的方案需要解决三个问题:


  • 如何实时收集内部组件的数据?

  • 如何对组件的数据进行校验?

  • 如何更新组件的数据?


下面我们就带着这三个问题,一起看看 Antd Form 是如何来做的吧~


先看一下 Form class 的结构,Form 组件有两个静态属性 Item、createFormField 和一个静态方法 create,Item 是对 FormItem 组件的引用,createFormField 指向 rc-form 提供的同名方法,create 方法则是对  rc-form createDOMForm 的调用,为了方便理解,这边隐藏了部分代码,Form class 整体的结构如下:


import * as React from'react';import * as PropTypes from'prop-types';import classNames from'classnames';import createDOMForm from'rc-form/lib/createDOMForm';import createFormField from'rc-form/lib/createFormField';import omit from'omit.js';import { ConfigConsumer, ConfigConsumerProps } from'../config-provider';import { tuple } from'../_util/type';import warning from'../_util/warning';import FormItem from'./FormItem';import { FIELD_META_PROP, FIELD_DATA_PROP } from'./constants';import FormContext from'./context';
const FormLayouts = tuple('horizontal', 'inline', 'vertical');
exportdefaultclass Form extends React.Component{ static defaultProps = { colon: true, layout: 'horizontal', hideRequiredMark: false, onSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); }, };
static propTypes = { prefixCls: PropTypes.string, layout: PropTypes.oneOf(FormLayouts), children: PropTypes.any, onSubmit: PropTypes.func, hideRequiredMark: PropTypes.bool, colon: PropTypes.bool, }; // Item 是对 FormItem 组件的引用 static Item = FormItem; // createFormField 指向 rc-form 提供的同名方法 static createFormField = createFormField;
// create 方法则是对 rc-form createDOMForm 的调用 static create = function create( options ){ return createDOMForm({ fieldNameProp: 'id', ...options, fieldMetaProp: FIELD_META_PROP, fieldDataProp: FIELD_DATA_PROP, }); };
constructor(props) { super(props); }
renderForm = ({ getPrefixCls }: ConfigConsumerProps) => { const { prefixCls: customizePrefixCls, hideRequiredMark, className = '', layout } = this.props; const prefixCls = getPrefixCls('form', customizePrefixCls); const formClassName = classNames( prefixCls, { [`${prefixCls}-horizontal`]: layout === 'horizontal', [`${prefixCls}-vertical`]: layout === 'vertical', [`${prefixCls}-inline`]: layout === 'inline', [`${prefixCls}-hide-required-mark`]: hideRequiredMark, }, className, );
const formProps = omit(this.props, [ 'prefixCls', 'className', 'layout', 'form', 'hideRequiredMark', 'wrapperCol', 'labelAlign', 'labelCol', 'colon', ]);
return<form {...formProps} className={formClassName} />; };
render() { const { wrapperCol, labelAlign, labelCol, layout, colon } = this.props; return ( <FormContext.Provider value={{ wrapperCol, labelAlign, labelCol, vertical: layout === 'vertical', colon }} > <ConfigConsumer>{this.renderForm}</ConfigConsumer> </FormContext.Provider> ); }}
复制代码



从 Form 的源码上看,组件本身并不涉及表单数据流程相关的逻辑,Form 组件以及 FormItem 主要处理布局方式、表单样式、属性必填样式、检验文案等视图层面的逻辑。


对数据的收集、校验、更新的流程的抽象以及组件数据管理主要由 rc-form 实现。下面我们继续来看一下核心的 rc-form 模块是怎样的结构。


rc-form 的 核心文件以及核心类图如下:



rc-form 核心逻辑可以从两个文件来看,createBaseForm.js、createFieldsStore.js。


createBaseForm.js 中暴露出的 createBaseForm 函数,创建了一个高阶组件 decorate,decorate 会为我们的目标组件包裹上一个容器组件,也就是上图中的核心类 BaseForm。


createFieldsStore.js 中暴露的 createFieldsStore 函数用来创建 FieldsStore 类的实例。FieldsStore 类可以理解为组件数据的管理中心,负责数据模型的初始化,并提供 Api 对组件数据进行更新和读取,以及获取组件数据的校验结果和数据更改状态。


Form 组件流程分析


我们通过 Antd Pro 中登录页面的实现来一起看一下,Form 内部的调用流程。


首先来看一下 Form 表单的用法:


class CustomizedForm extends React.Component {}
CustomizedForm = Form.create({})(CustomizedForm);

复制代码


我们有一个自定义组件 CustomizedForm,在使用 Form 表单的时候,我们会先调用 Form.create({})(CustomizedForm)。



初始化阶段


Form.create 函数指向 rc-form 提供的 createBaseForm 方法,createBaseForm 则创建了高阶组件 decorate。


decorate 的参数就是我们的 CustomizedForm 自定义组件。decorate 会创建一个被 BaseForm 组件包裹的自定义表单组件,经过包裹的组件将会自带 this.props.form 属性。为了方便记忆,我们把这个组件称为 FormHocCustomizedForm。


/***  rc-form/createBaseForm.js*/
// 默认的数据收集触发事件const DEFAULT_TRIGGER = 'onChange';
function createBaseForm(option = {}, mixins = []) { const { validateMessages, onFieldsChange, onValuesChange, mapProps = identity, mapPropsToFields, fieldNameProp, fieldMetaProp, fieldDataProp, formPropName = 'form', name: formName, // @deprecated withRef, } = option;
// 高阶组件 returnfunction decorate(WrappedComponent) { const Form = createReactClass({ mixins, ... render() { const { wrappedComponentRef, ...restProps } = this.props; // eslint-disable-line // 为目标组件注入 form props const formProps = { [formPropName]: this.getForm(), }; if (withRef) { if ( process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test' ) { warning( false, '`withRef` is deprecated, please use `wrappedComponentRef` instead. ' + 'See: https://github.com/react-component/form#note-use-wrappedcomponentref-instead-of-withref-after-rc-form140', ); } formProps.ref = 'wrappedComponent'; } elseif (wrappedComponentRef) { formProps.ref = wrappedComponentRef; } const props = mapProps.call(this, { ...formProps, ...restProps, }); return<WrappedComponent {...props} />; }, }); return argumentContainer(unsafeLifecyclesPolyfill(Form), WrappedComponent); };}
export default createBaseForm;
复制代码



组件创建完成之后,FormHocCustomizedForm 就会经历 React 组件的生命周期。


getInitailState 阶段


Form 并没有通过内部的 state 来管理内部组件的值, 而且创建了 FieldsStore 实例,也就是上面提到的组件数据管理中心。



通过上面的类图我们可以看到 FieldsStore 包含两个属性,fields 和 fieldsMeta,fields 主要用来记录每个表单项的实时数据,包含以下属性:


  • dirty 数据是否已经改变,但未校验

  • errors 校验文案

  • name 字段名称

  • touched 数据是否更新过

  • value 字段的值

  • validating 校验状态



fieldsMeta 用来记录元数据,即每个字段对应组件的配置信息:


  • name 字段的名称

  • originalProps 被 getFieldDecorator() 装饰的组件的原始 props

  • rules 校验的规则

  • trigger 触发数据收集的时机 默认 onChange

  • validate 校验规则和触发事件

  • valuePropName 子节点的值的属性,例如 checkbox 应该设为 checked

  • getValueFromEvent 如何从 event 中获取组件的值

  • hidden 为 true 时,校验或者收集数据时会忽略这个字段



Render 阶段


被 Form 管理的组件,需要使用 props.form.getFieldDecorator 来包装,在 Render 阶段需要调用 getFieldDecorator 传入我们的组件配置,包括字段名 name 以及组件元数据 otherOptions,再将字段对应的组件传入 getFieldDecorator 返回的高阶组件。


{getFieldDecorator('name', otherOptions)(<Input />)}
复制代码


/** * rc-form/createBaseForm.js */			// getFieldDecorator 实际上创建了一个高阶组件,参数是字段对应的组件getFieldDecorator(name, fieldOption) {  // 装饰组件的 props  const props = this.getFieldProps(name, fieldOption);  returnfieldElem => {    // We should put field in record if it is rendered    this.renderFields[name] = true;
const fieldMeta = this.fieldsStore.getFieldMeta(name); const originalProps = fieldElem.props; // 校验细节略过 ... fieldMeta.originalProps = originalProps; fieldMeta.ref = fieldElem.ref; return React.cloneElement(fieldElem, { ...props, ...this.fieldsStore.getFieldValuePropValue(fieldMeta), }); };},
复制代码


经过 getFieldDecorator 包装的组件,表单组件会自动添加  value(或 valuePropName 指定的其他属性) onChange(或 trigger 指定的其他属性)属性,接下来的数据同步将被 Form 接管。


getFieldDecorator 主要用于装饰组件,其中调用的  getFieldProps 用于装饰 props,getFieldProps 会将组件的 (DEFAULT_TRIGGER = 'onChange')触发事件指向 FormHocCustomizedForm 的 onCollect 方法,并将配置的 validateTriggers 指向 onCollectValidate。在这个阶段还会收集组件的元数据,也就是我们调用  getFieldDecorator 中传入的 option 配置,这些配置会存入 fieldStore 的 fieldsMeta 对象中,作为组件的元数据。


这里我们就可以回答第一个问题,如何实时收集内部组件的数据?


Form 通过 getFieldDecorator 对组件进行包装,接管组件的 value 和 onChange 属性,当用户输入改变时,触发 onCollect 或 onCollectValidate 来收集组件最新的值。


用户输入


当用户输入触发组件的 onChange 或者其他的 trigger 事件时,执行 onCollect 或者  onCollectValidate,onCollect 执行组件数据收集,onCollectValidate 除了执行组件数据收集,还会根据配置的校验规则来校验组件,其中校验的流程并不由 rc-form 实现,而且通过引入第三方校验库  async-validator 执行。


onCollect 和 onCollectValidate 方法中收集数据的动作主要由 onCollectCommon 来处理。


/** * rc-form/createBaseForm.js */
onCollect(name_, action, ...args) { const { name, field, fieldMeta } = this.onCollectCommon( name_, action, args, ); const { validate } = fieldMeta;
this.fieldsStore.setFieldsAsDirty();
const newField = { ...field, dirty: hasRules(validate), }; this.setFields({ [name]: newField, });},
复制代码


onCollectCommon 负责组件数据的收集,在事件的回调中,通过默认的 getValueFromEvent 方法或者组件配置的 getValueFromEvent 方法,可以从参数 event 中正确的拿到组件的值。


/** * rc-form/createBaseForm.js */
onCollectCommon(name, action, args) { const fieldMeta = this.fieldsStore.getFieldMeta(name);
if (fieldMeta[action]) { fieldMeta[action](...args); } elseif (fieldMeta.originalProps && fieldMeta.originalProps[action]) { fieldMeta.originalProps[action](...args); } const value = fieldMeta.getValueFromEvent ? fieldMeta.getValueFromEvent(...args) : getValueFromEvent(...args); if (onValuesChange && value !== this.fieldsStore.getFieldValue(name)) { const valuesAll = this.fieldsStore.getAllValues(); const valuesAllSet = {}; valuesAll[name] = value; Object.keys(valuesAll).forEach(key => set(valuesAllSet, key, valuesAll[key]), ); onValuesChange( { [formPropName]: this.getForm(), ...this.props, }, set({}, name, value), valuesAllSet, ); } const field = this.fieldsStore.getField(name); return { name, field: { ...field, value, touched: true }, fieldMeta };},
复制代码


针对不同的组件取值差异,由 getValueFromEvent 方法来屏蔽。


/** * rc-form/utils.js */
// 默认的 getValueFromEventexportfunction getValueFromEvent(e) { // To support custom element if (!e || !e.target) { return e; } const { target } = e; return target.type === 'checkbox' ? target.checked : target.value;}
复制代码


收集并校验组件的值。


/** * rc-form/createBaseForm.js */
onCollectValidate(name_, action, ...args) { const { field, fieldMeta } = this.onCollectCommon(name_, action, args); // 获取组件最新的值 const newField = { ...field, dirty: true, };
this.fieldsStore.setFieldsAsDirty(); // 对组件最新的值 进行校验 this.validateFieldsInternal([newField], { action, options: { firstFields: !!fieldMeta.validateFirst, }, });},
复制代码


执行校验。


validateFieldsInternal(  fields,  { fieldNames, action, options = {} },  callback,) {  const allRules = {};  const allValues = {};  const allFields = {};  const alreadyErrors = {};  fields.forEach(field => {    const name = field.name;    if (options.force !== true && field.dirty === false) {      if (field.errors) {        set(alreadyErrors, name, { errors: field.errors });      }      return;    }    const fieldMeta = this.fieldsStore.getFieldMeta(name);    const newField = {      ...field,    };    newField.errors = undefined;    newField.validating = true;    newField.dirty = true;    // 从 fieldMeta 中读取校验规则    allRules[name] = this.getRules(fieldMeta, action);    allValues[name] = newField.value;    allFields[name] = newField;  });  // 校验前更新字段状态  this.setFields(allFields);  // in case normalize  Object.keys(allValues).forEach(f => {    allValues[f] = this.fieldsStore.getFieldValue(f);  });
// AsyncValidator 三方校验库 async-validator; const validator = new AsyncValidator(allRules); if (validateMessages) { validator.messages(validateMessages); } validator.validate(allValues, options, errors => { const errorsGroup = { ...alreadyErrors, }; if (errors && errors.length) { errors.forEach(e => { // 省略... const fieldErrors = get(errorsGroup, fieldName.concat('.errors')); fieldErrors.push(e); }); } const expired = []; const nowAllFields = {}; Object.keys(allRules).forEach(name => { const fieldErrors = get(errorsGroup, name); const nowField = this.fieldsStore.getField(name); // avoid concurrency problems if (!eq(nowField.value, allValues[name])) { expired.push({ name, }); } else { nowField.errors = fieldErrors && fieldErrors.errors; nowField.value = allValues[name]; nowField.validating = false; nowField.dirty = false; nowAllFields[name] = nowField; } }); // 检验完成 更新字段实时数据 this.setFields(nowAllFields); // ... });},
复制代码


到这里我们可以回答上面的第二个问题,如何对组件的数据进行校验?


当通过执行 onCollectCommon 完成了表单数据的收集,onCollectValidate 会调用 validateFieldsInternal 方法创建 AsyncValidator 的实例,由 AsyncValidator 根据组件的配置规则进行校验,并将最终的校验结果和表单数据更新到 fieldStore。


到这里就完成了表单数据的收集和校验的环节,已经拿到了表单最新的数据以及校验结果。


下一步,就是数据的更新,也就是将表单最新的值和校验相关的信息更新到视图上。


在 onCollect 和 validateFieldsInternal 方法中,我们看到最后一步调用了 setFields 来更新实时数据。


/** * rc-form/createBaseForm.js */
setFields(maybeNestedFields, callback) { const fields = this.fieldsStore.flattenRegisteredFields( maybeNestedFields, ); // 更新 fieldsStore this.fieldsStore.setFields(fields);
if (onFieldsChange) { const changedFields = Object.keys(fields).reduce( (acc, name) =>set(acc, name, this.fieldsStore.getField(name)), {}, ); onFieldsChange( { [formPropName]: this.getForm(), ...this.props, }, changedFields, this.fieldsStore.getNestedAllFields(), ); } // 更新 this.forceUpdate(callback);},
复制代码


setFields 方法将字段组件最新的数据更新到 fieldStore。此时 fieldStore 已经收集存储了组件最新的值,下面我们就需要更新组件,将数据正确的在界面上渲染出来。


可以看到,setFields 中最后调用了 React 组件提供的 forceUpdate 函数。这里可以回答第三个问题,如何更新组件的数据?


因为我们在最顶层的 FormHocCustomizedForm 组件中调用 forceUpdate,forceUpdate 会跳过 shouldComponentUpdate 触发组件的 Render 方法,进而触发所有子组件的更新流程。在子组件 Render 的执行过程中, getFieldDecorator 方法从 fieldStore 中读取实时的表单数据以及校验信息,并通过注入 value 或者  valuePropName 的值设定的属性来更新表单。


到这里,一个完整的 Form 数据收集、校验、更新流程就完成了,整个过程的流程图如下所示:



复杂表单场景的最佳实践


看完了上面的 Form 内部的运行流程,下面我们一起来看看 Form 还提供了哪些机制方便我们解决一些复杂场景问题。


嵌套数据结构收集


FieldStore 内部集成了  lodash/set,可以设置对象路径(eg: a.b.c 或者 a.b[0])为字段值,通过使用对象路径字段,我们可以很方便的实现嵌套数据结构值的收集。


<FormItem>  <Col span={16}>    {getFieldDecorator('nested.fieldObj.name')(<Input/>)}  </Col></FormItem><FormItem>  <Col span={16}>    {getFieldDecorator('nested.fieldArray[0].name')(<Input/>)}  </Col></FormItem>
复制代码


上面的代码中,我们通过对象路径的方式来设置 field,在获取表单值的时候已经被转换成了对应路径结构的对象或数组,如下面所示:


{  nested:{   fieldObj:{     name:'嵌套对象的值'   },   fieldArray:['嵌套数组的值']  }}
复制代码


自定义表单接入


上面的分析里提到,Form 通过接管组件的 value 和 onChange 事件来管理组件,想实现一个可以接入 Form 管理的组件,只需要满足下面三个条件


  • 提供受控属性 value 或其它与 valuePropName 的值同名的属性

  • 提供 onChange 事件或 trigger 的值同名的事件

  • 支持 ref:

  • React@16.3.0 之前只有 Class 组件支持

  • React@16.3.0 及之后可以通过 forwardRef 添加 ref 支持(https://codesandbox.io/s/7wj199900x


表单联动


组件的数据由 FieldStore 来统一管理,组件值变化时也会实时更新,所以结合 ES6 的 get 方法可以很简单的实现组件之间的联动。


class Linkage extends Component {  get showInput(){   return this.props.form.getFieldValue('checkbox');  }  render() {    const { form } = this.props;    const { getFieldDecorator } = form;    return (      <div>      {        getFieldDecorator('checkbox', {valuePropName: 'checked',})(          <Checkbox>show Input</Checkbox>        )      }      {       this.showInput && <Input/>	      }      </div>    );  }}
export default Form.create()(Linkage);
复制代码


总结


本文在流程上对 Form 组件的实现机制进行了解析,省略了里面的实现细节,大家对流程有一个整体认知之后,也可以自己翻阅 Form 的源码来了解实现细节。


Antd Form 具有很好的灵活性,可以帮我们快速的实现表单需求,但是也存在一些问题,比如当表单中的任何一个组件值发生改变,触发 onCollect 数据收集、执行更新流程,都会调用 forceUpdate 触发所有组件的更新。


在复杂表单业务,用户频繁的输入场景就会产生性能瓶颈。对于复杂的表单组件,我们可以通过拆分组件的粒度,通过 shouldComponentUpdate 来避免不必要的更新,或者修改组件的数据收集时机来减少数据的收集频率。当然这并不是很优雅的解决方案,在未来要发布的 Antd V4 版本中,Form 的底层实现已经替换为 rc-field-form(https://github.com/react-component/field-form),主页上的介绍是:


React Performance First Form Component.


大家也可以期待一下官方新版本的 Form 组件。



头图:Unsplash

作者:子洋

原文:https://mp.weixin.qq.com/s/LwBCJJzhT5uKB-ix4XkqbA

原文:Antd Form 实现机制解析

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


2021-03-13 13:164282

评论

发布
暂无评论
发现更多内容

我是一名数学专业的应届博士,我该如何选择offer?

IC男奋斗史

职业规划

芯荒荒,汽车芯片路在何方

IC男奋斗史

芯片行业思考 芯片技术

通过简书网学习 ActionChains,selenium webdriver 学习第3篇

梦想橡皮擦

Python 3月月更

PostmangRPC功能使用介绍

蜜糖的代码注释

gRPC 调试 Postman 3月月更

IC应届生40万白菜价!从业多年的资深专家手把手指导你如何选择offer!

IC男奋斗史

职业规划

芯片工程师太贵?贵你妹啊

IC男奋斗史

芯片行业思考

润还是不润?这是个问题

IC男奋斗史

职业规划 芯片行业思考

博文推荐|使用 Apache Pulsar 构建边缘应用程序

Apache Pulsar

开源 架构 分布式 云原生 Apache Pulsar

Redis 主从复制的原理及演化

百度开发者中心

这是我们的黄金时代

IC男奋斗史

职业规划 芯片行业思考 芯片技术

第三次“世界大战”——芯片保卫战,无烟的战场

IC男奋斗史

芯片行业思考

微博评论架构设计

刘洋

#架构实战营 「架构实战营」

Ember 速度最快、性能最高的渲染技术框架之一

devpoint

前端框架 ember.js

Hoo虎符研究院|2022年三月值得关注的赛道

区块链前沿News

Web NFT 元宇宙 虎符交易所

云原生网络利器--Cilium 总览

Daocloud 道客

ebpf cilium 云原生网络 容器网络方案

我的奋斗:我在外企那些年(二)

IC男奋斗史

职业规划 芯片行业思考

我的奋斗:我在外企那些年(一)

IC男奋斗史

职业规划 芯片行业思考

iOS防截屏|担心App内容被截屏泄露吗?这个开源库就是你要的

LabLawliet

ios

聊聊redo log是什么

程序猿阿星

Redo Log MySQL InnoDB

李凌:6 年,我如何从开源小白成为 Apache 顶级项目 PMC

腾源会

开源 腾源会

Redis现网那些坑:用个缓存,还要为磁盘故障买单?

华为云开发者联盟

redis 缓存 SSD 磁盘故障 缓存Redis

检测图片中是否有二维码

逆锋起笔

android 二维码 Android端 3月月更

对信用卡欺诈 Say No!百行代码实现简化版实时欺诈检测

沃趣科技

数据库表

凤姐如何变冰冰?

IC男奋斗史

芯片技术

裸奔?哒咩!

IC男奋斗史

芯片技术

国内外最知名的9大工作任务管理软件盘点

爱吃小舅的鱼

为什么需要线程池?什么是池化技术?

王磊

面试

为什么需要线程池?什么是池化技术?

CRMEB

云原生多云应用利器 -- Karmada 调度器

Daocloud 道客

Kubernetes 云原生 开源软件 Karmada

看到字节跳动28岁员工猝死,我都想润了......

IC男奋斗史

职业规划 芯片行业思考

VuePress 博客优化之中文锚点跳转问题

冴羽

typescript Vue 博客 vuepress 博客搭建

Antd Form 实现机制解析_语言 & 开发_政采云前端团队_InfoQ精选文章