React 15.x 升 React 16.x 是一次內部重構,對於使用者來講,原來的使用方式仍然可用,額外加了新的功能;而Antd 3.x 升 Antd 4.x, 在個人認知範圍裏,能夠稱做是飛(po)躍(huai)性的重構, 由於之前不少寫法都不兼容了,組件代碼重構,使用者的代碼也得重構。但此次重構解決了3.x的不少問題,好比:react
說這麼多,仍是直接來張圖吧,我我的項目的打包體積變化:Antd 3.x
VS Antd 4.x
git
升4.x以後,gzip少了150kb,也就是包大小少了500多kb,這不香麼。
我和個人小組,爲了用更爽的方式來開發迭代,針對於antd的Form和Table等組件作了一些簡單的二次封裝,造成了組件庫antd-doddle。雖然Antd 4.x推出快小半年了,受疫情影響,今年業務迭代比較緩慢,沒有新系統,也以爲暫時不必去重構業務代碼,因此一直只關注不動手。最近比較閒,組件庫針對Antd 4.0作了適應性重構,做爲一個膠水層,最大程度的去磨平4.0版本Form這種破壞性變更,減小之後業務代碼升級4.x版本的調整量。github
Antd 4.x到底作了哪些變化,在官方文檔能夠看到。spring
這篇文章主要講針對於4.x Form的變化,我重構組件庫的思路。npm
Antd-doddle 2.x文檔地址, 支持4.x: http://doc.closertb.site, 首次加載較慢,請耐心等候。設計模式
Antd-doddle 1.x文檔地址, 支持3.x: http://static.closertb.site, 首次加載較慢,請耐心等候。api
試用項目Git 地址antd
項目在線試用地址, 請勿亂造koa
4.x 中除了Icon,最大的更改就在於Form,我本身感覺到的變化是:異步
最大的改變
就是增量式更新,3.x版本,任意表單項改變,都會形成Form.create包裹的所有表單項從新render,這是很是大的性能消耗;而4.x以後,任意表單項改變,只有設置了shouldUpdate屬性的表單項有可能執行render,相似於React 16新增的componentShouldUpdate
;根據上面的變化點,由外向內層層剖析,針對性的作重構;
因爲之前FormGroup組件,除了收集form方法和公共配置,也做爲一個標識,接管了組件內部的渲染層;3.x版本其form實例由Form.create,即業務代碼提供;4.x與其類似,只不過是經過hooks生成form實例。
變化點主要在於4.x版本Form要提供initialValues的設置,且這是一個defaultValue的設置,因此咱們須要拓展,讓其支持values爲異步數據時,表單項的值能跟隨其改變, 其原理很簡單,監聽value的變化,並重置表單數據,實現代碼以下:
// 僞代碼,只涉及相關改動 const FormGroup = (props, ref) => { const { formItemLayout = layout, children, datas = {}, ...others } = props; // 兼容了非hooks 組件調用的寫法,內部再聲明一個ref, 以備用; const insideRef = useRef(); const _ref = ref || insideRef; const formProps = { initialValues: {}, // why ...formItemLayout, ...others }; // 若是datas 值變化,重置表單的值 useEffect(() => { const [data, apiStr] = Type.isEmpty(datas) ? [undefined, 'resetFields'] : [datas, 'setFieldsValue']; // 函數式組件採用form操做; if (props.form) { props.form[apiStr](data); return; } // 若是是類組件,才採用ref示例更新組件 if (typeof _ref === 'object') { _ref.current[apiStr](data); } }, [datas]); return ( <Form {...formProps} ref={_ref}> {deepMap(children, extendProps, mapFields)} </Form>); };
上面有句代碼 initialValues: {},
會讓人困惑, 爲何沒有賦值爲datas呢;這個又是antd的一個隱藏知識點,舉個例子:
form.setFieldsValue({ name: 'antd', age: 18 }); // 後面想清空 form.setFieldsValue({}); // 最後發現上面清空根本沒生效,緣由能夠本身想一想
因此當咱們想作一些需求,好比先編輯一個表單,表單中有數據了;但沒作操做關閉了,而後點了新增按鈕,傳了一個空對象,這事就發現bug了,上一次編輯的數據還在,除了這個,還有一些其餘的業務場景會用到,在antd中也有一個相似的issue:
在表單裏有不少元素且擁有initialValue的時候, 如何簡單的清空表單
因此在這個組件設計上,就將initialValues
默認置空數據,而後設置的數據採用setFieldsValue
來重置。若是想清空表單,直接傳入一個空數據,被組件檢測到後,內部調用resetFields
來實現。快誇一下天才的我。
相比於FormGroup的變化,子組件FormRender相比較就變化小一點,主要在適應第二點變更,用代碼的方式更直觀:
// 3.x const render = renderType[type]; content = ( <FormItem label={name} rules={gerateRule(required, pholder, rules)} {...formProps} > {getFieldDecorator(key, { initialValue: data, rules: gerateRule(required, pholder, rules) })( render({ field: common, name, enums: selectEnums, containerName }))} </FormItem>);
重構以後
// 4.x const render = renderType[type]; content = ( <FormItem name={key} label={name} rules={gerateRule(required, pholder, rules)} {...formProps} > {render({ field: common, name, enums: selectEnums, containerName })} </FormItem>);
主要實現三種聯動,根據其餘表單項的變化,來改變關聯表單項
因爲之前每次表單項變化,都會引發其餘表單項render,因此能夠暴力的經過FormGroup增長數據層(useRef)與監(bao)聽(guo)每一個表單項的onChange來實現;
新的Form新增了onFormChange回調來支持增量式數據收集,但第四點變更,讓老的方案GG;表單項的聯動須要依賴shouldUpdate來實現,這也是官方推薦的方案;
其本質是,設置了shouldUpdate屬性的FormItem,其僅僅做爲一個容器,這個容器監聽了相似onFormChange這種事件,而後根據shouldUpdate來判斷是否須要從新渲染容器內的子元素,子元素渲染實現是一個應用React renderPrrops的設計模式;
因此聯動方案彷佛變得更簡單了, 就多一層FormItem包裹,看部分代碼實現:
const render = renderType[type]; content = shouldUpdate ? ( <FormItem shouldUpdate={shouldUpdate} noStyle> {form => { const datas = form.getFieldsValue(); const require = typeof required === 'function' ? required(initData, datas) : required; const disabled = typeof disableTemp === 'function' ? disableTemp(initData, datas) : disableTemp; return finalEnable(initData, datas) ? (<FormItem key={key} name={key} label={name} dependencies={dependencies} rules={gerateRule(require, pholder, rules)} {...formProps} {...otherFormPrrops} > {render({ field: Object.assign(common, { disabled }), name, enums: selectEnums, containerName })} </FormItem>) : selfRender(datas, form)} } </FormItem>) : /* 非聯動實現 */
除了上面這些變化,其實還有不少邊邊角角的變化,好比:
還有,還有一些未考慮好的的新增特性。
實現一個小的編輯框,相似下面這樣:
重構前的代碼
import React from 'react'; import { FormGroup } from 'antd-doddle'; import { editFields } from './fields'; const { FormRender } = FormGroup; function Edit({ id, form, data }) { const { getFieldDecorator } = form; return ( <FormGroup getFieldDecorator={getFieldDecorator} required> {editFields.map(field => <FormRender key={field.key} field={field} data={data} />)} </FormGroup> ); } export default FormGroup.create()(Edit);
重構以後
import React from 'react'; import { FormGroup } from 'antd-doddle'; import { editFields } from './fields'; const { FormRender } = FormGroup; function Edit({ id, data, ...others }) { const [form] = FormGroup.useForm(); return ( <FormGroup required form={form} datas={data}> {editFields.map(field => <FormRender key={field.key} field={field} />)} </FormGroup> ); } export default Edit
不仔細看,是否是不易察覺到變化
由於用的還不像3.x版本那麼熟練,不少特性還沒用上,因此先重構一個簡易版本,來完成平常場景,後面再慢慢迭代。
若是感興趣,能夠fork項目或查看項目文檔