Antd Form 實現機制解析

背景

在中後臺業務中,表單頁面基礎的場景包括組件值的收集、校驗和更新。在 to B 業務中,表單頁面的需求每每更復雜和定製化,除了上述的基本操做,還要處理包括自定義表單組件、表單聯動、表單的異步檢驗等複雜場景,在一些大型表單頁面中還要考慮性能的問題,表單頁面的需求每每是新同窗摔得第一個跤。

本文分爲兩個部分,第一部分會經過對 Antd Form 源碼的分析來幫助你們對 Form 的總體設計和流程有一個清晰的概念,第二部分會分享一些複雜場景的解決方案。但願能夠幫助你們更容易的處理表單需求和快速定位表單場景中的問題。javascript

本文並不涉及過於具體的源碼實現分析,你們能夠放鬆心情,一塊兒來對 Form 有一個感性的認知吧~html

Form 組件解決了什麼問題

首先咱們先看一個簡單的表單,收集並校驗兩個組件的值。只須要經過監聽兩個表單組件的 onChange 事件,獲取表單項的 value,根據定義的校驗規則對 value 進行檢驗,生成檢驗狀態和檢驗信息,再經過 setState 驅動視圖更新,展現組件的值以及校驗信息便可。前端

代碼實現多是這樣的:java

export default class 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>);
  }
}

用流程圖來表示是這樣的:react

簡單流程

上面的實現,咱們設定了一個表單數據狀態的模型,來維護組件的 value 和校驗的錯誤信息。git

this.state = {
  [field1]:{
    value: '',
    error: '',
  },
  [field2]:{
    value: '',
    error: '',
  },
  ...
}

還有一個字段配置相關的模型,維護組件對應的校驗規則。github

fieldMeta = {
  username:{
    rules:[]
  },
  password:{
    rules:[],
  }
}

對於這種簡單的業務場景,上述方式徹底能夠知足需求。數組

具體到真實的業務場景,每每更復雜,其中包含多種表單組件,如 Input、Checkbox、Radio、Upload,還有一些自定義表單組件。數據結構

20191216100127

這個時候若是繼續採用這種方式,不只須要維護多個 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');

export default class 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 架構

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)。

20191213010654

初始化階段

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;

  // 高階組件
  return function 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';
        } else if (wrappedComponentRef) {
          formProps.ref = wrappedComponentRef;
        }
        const props = mapProps.call(this, {
          ...formProps,
          ...restProps,
        });
        return <WrappedComponent {...props} />;
      },
    });
        
    return argumentContainer(unsafeLifecyclesPolyfill(Form), WrappedComponent);
  };
}

export default createBaseForm;

20191210132203

組件建立完成以後,FormHocCustomizedForm 就會經歷 React 組件的生命週期。

getInitailState 階段

Form 並無經過內部的 state 來管理內部組件的值, 並且建立了 FieldsStore 實例,也就是上面提到的組件數據管理中心。

rc-form 架構

經過上面的類圖咱們能夠看到 FieldsStore 包含兩個屬性,fields 和 fieldsMeta,fields主要用來記錄每一個表單項的實時數據,包含如下屬性:

dirty 數據是否已經改變,但未校驗

errors 校驗文案

name 字段名稱

touched 數據是否更新過

value 字段的值

validating 校驗狀態

20191210141658

fieldsMeta 用來記錄元數據,即每一個字段對應組件的配置信息:

name 字段的名稱

originalProps 被 getFieldDecorator() 裝飾的組件的原始 props

rules 校驗的規則

trigger 觸發數據收集的時機 默認 onChange

validate 校驗規則和觸發事件

valuePropName 子節點的值的屬性,例如 checkbox 應該設爲 checked

getValueFromEvent 如何從 event 中獲取組件的值

hidden 爲 true 時,校驗或者收集數據時會忽略這個字段

20191210141722

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);
  return fieldElem => {
    // 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);
  } else if (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
 */

// 默認的 getValueFromEvent
export function 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 支持(示例

表單聯動

組件的數據由 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,主頁上的介紹是:

React Performance First Form Component.

你們也能夠期待一下官方新版本的 Form 組件。

招賢納士

政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 50 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。

若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com

相關文章
相關標籤/搜索