我們是袋鼠雲數棧 UED 團隊,致力於打造優秀的一站式資料中臺產品。我們始終保持工匠精神,探索前端道路,為社群積累並傳播經驗價值。
或許是考慮到部分元件升級的毀壞性,antd4.x 中依然保留了對 3.x 版本的相容,廢棄的元件通過 @ant-design/compatible 保持相容,例如 Icon, Form
注:建議 @ant-design/compatible 僅在升級過程中稍作依賴,升級 4.x 請完全剔除對該過渡包的依賴
1、@ant-design/codemod-v4 自帶升級指令碼,會自動替換程式碼
# 通過 npx 直接執行
npx -p @ant-design/codemod-v4 antd4-codemod apps/xxxx
# 或者全域性安裝
# 使用 npm
npm i -g @ant-design/codemod-v4
# 或者使用 yarn
yarn global add @ant-design/codemod-v4
# 執行
antd4-codemod src
注意: 該命令和指令碼只會進行程式碼替換,不會進行AntD的版本升級,需要手動將其升級至4.22.5
該命令完成的工作:
1. 將 Form 與 Mention 元件通過 @ant-design/compatible 包引入
2. 用新的 @ant-design/icons 替換字串型別的 icon 屬性值
3. 將 Icon 元件 + type =「」 通過 @ant-design/icons 引入
4. 將 v3 LocaleProvider 元件轉換成 v4 ConfigProvider 元件
5. 將 Modal.method() 中字串 icon 屬性的呼叫轉換成從 @ant-design/icons 中引入
上圖這類報錯是 Icon 元件自動替換錯誤,有 2 種處理方式:
報錯檔案的 Icon 比較少的情況,可以直接手動替換該檔案中的 Icon 元件。具體替換成 Icon 中的哪個元件可以根據 type 在 Icon檔案 中找。
下圖中是具體報錯的節點,可以看到 JSXSpreadAttribute 節點也就是拓展運運算元中沒有 name 屬性,所以把 Icon 元件的拓展運運算元改一下再執行替換指令碼就可以了
styled-components 依賴需要轉換寫法
不要使用相容包的 icon
在 3.x 版本中,Icon 會全量引入所有 svg 圖示檔案,增加了打包產物
在 4.x 版本中,對 Icon 進行了按需載入,將每個 svg 封裝成一個元件
注:antd 不再內建 Icon 元件,請使用獨立的包 @ant-design/icons
使用
import { Icon } from 'antd';
mport { SmileOutlined } from '@ant-design/icons';
const Demo = () => (
<div>
<Icon type="smile" />
<SmileOutlined />
<Button icon={<SmileOutlined />} />
</div>
);
相容
import { Icon } from '@ant-design/compatible';
const Demo = () => (
<div>
<Icon type="smile" />
<Button icon="smile" />
</div>
);
在 3.x 中,表單中任意一項的修改,都會導致 Form.create() 包裹的表單重新渲染,造成效能消耗
在 4.x 中,Form.create() 不再使用
如果需要使用 form 的 api,例如 setFieldsValue 等,需要通過 Form.useForm()
建立 Form 實體進行操作
// antd v4
const Demo = () => {
const [form] = Form.useForm();
React.useEffect(() => {
form.setFieldsValue({
username: 'Bamboo',
});
}, []);
return (
<Form form={form} {...props}> ... </Form>
)
};
class Demo extends React.Component {
formRef = React.createRef();
componentDidMount() {
this.formRef.current.setFieldsValue({
username: 'Bamboo',
});
}
render() {
return (
<Form ref={this.formRef}>
<Form.Item name="username" rules={[{ required: true }]}>
<Input />
</Form.Item>
</Form>
);
}
}
當我們使用 From.create() 的時候,可能會傳入引數,做資料處理,例如:
export const FilterForm: any = Form.create<Props>({
onValuesChange: (props, changedValues, allValues) => {
const { onChange } = props;
onChange(allValues);
},
})(Filter);
由於 Form.create 的刪除,需要放到 <Form>
中
<Form
ref={this.formRef}
layout="vertical"
className="meta_form"
onValuesChange={(_, allValues) => {
const { onChange } = this.props;
onChange(allValues);
}}
>
在 4.x 中,不在需要 getFieldDecorator 對 Item 進行包裹。
注意以下問題
// antd v4
const Demo = () => (
<Form initialValues={{ username: 'yuwan' }}>
<Form.Item name="username" rules={[{ required: true }]}>
<Input />
</Form.Item>
</Form>
);
initialValue 從字面意來看,就是初始值 defaultValue,但是可能會有部分同學使用他的時候會誤以為 initialValue 等同於 value
造成這樣的誤解是因為在 3.x 的版本中,一直存在一個很神奇的問題,受控元件的值會跟隨 initialValue 改變
看下面的例子,點選 button 修改 username, input 框的 value 也會隨之改變
const Demo = ({ form: { getFieldDecorator } }) => (
const [username, setUsername] = useState('');
const handleValueChange = () => {
setUsername('yuwan');
}
return (
<Fragment>
<Form>
<Form.Item>
{getFieldDecorator('username', {
initialValue: username,
rules: [{ required: true }],
})(<Input />)}
</Form.Item>
</Form>
<Button onClick={handleValueChange}>Change</Button>
</Fragment>
)
);
const WrappedDemo = Form.create()(Demo);
但當 input 框被編輯過,initialValue 和 input 的繫結效果就消失了,正確的做法應該是通過 setFieldsVlaue 方法去 set 值
在 4.x,antd 團隊已經把這個 bug 給解了,並且其一是為了 name 重名問題,二是再次強調其初始值的功能,現在提到 Form 中了,
當然,如果繼續寫在 Form. Item 中也是可以的,但需要注意優先順序
前面有說過,form 表單不再會因為表單內部某個值的改變而重新渲染整個結構,而設有 shouldUpdate 為 true 的 Item,任意變化都會使該 Form. Item 重新渲染
它會接收 render props,從而允許你對此進行控制
這裡稍微注意一下,請勿在設定 shouldUpdate 的外層 Form. Item 上新增 name, 否則,你會得到一個 error
<Form.Item shouldUpdate={(prev, next) => prev.name !== next.name}>
{form => form.getFieldValue('name') === 'antd' && (
<Form.Item name="version">
<Input />
</Form.Item>
)}
</Form.Item>
在使用 shouldUpdate 的時候,需要在第一個 Form.Item 上加上 noStyle,否則就會出現下面的情況,會有留白佔位的情況
onBlur
時不再修改選中值,且返回 React 原生的 event
物件。
如果你在使用相容包的 Form 且設定了 validateTrigger
為 onBlur
,請改至 onChange
以做相容。
在 antd3 時,我們使用 callback 返回報錯。但是 antd4 對此做了修改,自定義校驗,接收 Promise 作為返回值。範例參考
<FormItem label="具體時間" {...formItemLayout}>
{getFieldDecorator('specificTime', {
rules: [
{
required: true,
validator: (_, value, callback) => {
if (!value || !value.hour || !value.min) {
return callback('具體時間不可為空');
}
callback();
},
},
],
})(<SpecificTime />)}
</FormItem>
<FormItem
label="具體時間"
{...formItemLayout}
name="specificTime"
rules={[
{
required: true,
validator: (_, value) => {
if (!value || !value.hour || !value.min) {
return Promise.reject('具體時間不可為空');
}
return Promise.resolve();
},
},
]}
>
<SpecificTime />)
</FormItem>
不在支援 callback,該方法會直接返回一個 Promise,可以通過 then / catch 處理
this.formRef.validateFields()
.then((values) => {
onOk({ ...values, id: appInfo.id || '' });
})
.catch(({ errorFields }) {
this.formRef.scrollToField(errorFields[0].name);
})
或者使用 async/await
try {
const values = await validateFields();
} catch ({ errorFields }) {
scrollToField(errorFields[0].name);
}
該 api 被拆分了,將其拆分為更為獨立的 scrollToField
方法
onFinishFailed = ({ errorFields }) => {
form.scrollToField(errorFields[0].name);
};
在 antd 3.x 版本,繫結欄位時,可以採用.
分割的方式。如:
getFieldDecorator('sideTableParam.primaryKey')
getFieldDecorator('sideTableParam.primaryValue')
getFieldDecorator('sideTableParam.primaryName')
在最終獲取 values 時,antd 3.x 的版本會對欄位進行彙總,得到如下:
const values = {
sideTableParam: {
primaryKey: xxx,
primaryValue: xxx,
primaryName: xxx,
}
}
而在 antd 4.x下,會得到如下的values 結果:
const values = {
'sideTableParam.primaryKey': xxx,
'sideTableParam.primaryValue': xxx,
'sideTableParam.primaryName': xxx,
}
解決方法:
在 antd 4.x 版本傳入陣列
name={['sideTableParam', 'primaryKey']}
name={['sideTableParam', 'primaryValue']}
name={['sideTableParam', 'primaryName']}
使用 setFieldsValue 設定值:
setFieldsValue({
sideTableParam: [
{
primaryKey: 'xxx',
primaryValue: 'xxx',
primaryName: 'xxx',
},
],
});
當我們使用 name={['sideTableParam', 'primaryKey']} 方式繫結值的時候,與其關聯的 dependencies/getFieldValue 都需要設定為['sideTableParam', 'primaryKey']
例如:
<FormItem dependencies={[['alert', 'sendTypeList']]} noStyle>
{({ getFieldValue }) => {
const isShowWebHook = getFieldValue(['alert', 'sendTypeList'])?.includes(
ALARM_TYPE.DING
);
return (
isShowWebHook &&
RenderFormItem({
item: {
label: 'WebHook',
key: ['alert', 'dingWebhook'],
component: <Input placeholder="請輸入WebHook地址" />,
rules: [
{
required: true,
message: 'WebHook地址為必填項',
},
],
initialValue: taskInfo?.alert?.dingWebhook || '',
},
})
);
}}
</FormItem>
當我們希望通過 validateFields 拿到的資料是陣列時,例如這樣:
我們可以設定為這樣
const formItems = keys.map((k: React.Key) => (
<Form.Item key={k} required label="名稱">
<Form.Item
noStyle
name={['names', k]}
rules={[
{ required: true, message: '請輸入標籤名稱' },
{ validator: utils.validateInputText(2, 20) },
]}
>
<Input placeholder="請輸入標籤名稱" style={{ width: '90%', marginRight: 8 }} />
</Form.Item>
<i className="iconfont iconicon_deletecata" onClick={() => this.removeNewTag(k)} />
</Form.Item>
));
<FormItem
label="過濾條件"
extra={
<Tooltip title={customSystemParams}>
系統引數設定
<QuestionCircleOutlined />
</Tooltip>
}
>
<Input.TextArea />
</FormItem>
底層重寫
在新版的 rc-select
中,antd 官方抽取了一個 generator 方法。它主要接收一個 OptionList
的自定義元件用於渲染下拉框部分。這樣我們就可以直接複用選擇框部分的程式碼,而自定義 Select 和 TreeSelect 對應的列表或者樹形結構了。
在 3.x 版本為
在 4.x 版本為
固定列時,文字過長導致錯位的問題,被完美解決了,✿✿ヽ(°▽°)ノ✿
3.x 中對 table fixed 的實現,是寫了兩個 table, 頂層 fixed 的是一個,底層捲動的是一個,這樣,出現這種錯位的問題就很好理解了。
要解決也不是沒有辦法,可以再特定的節點去測算表格列的高度,但是這個行為會導致重排,會影響效能問題
4.x 中,table fixed 不在通過兩個 table 來實現,他使用了一個 position 的新特性:position: sticky;
元素根據正常檔案流進行定位,然後相對它的_最近捲動祖先(nearest scrolling ancestor)_和 containing block (最近塊級祖先 nearest block-level ancestor),包括 table-related 元素,基於
top
,right
,bottom
, 和left
的值進行偏移。偏移值不會影響任何其他元素的位置。
優點
缺點
解決了使用 absolute | fixed 脫離檔案流無法撐開高度的問題,也不在需要對高度進行測量
資產升級後,checkbox 寬度被擠壓了。
通過在 rowSelection 中設定 columnWidth 和 fixed 解決。
const rowSelection = {
fixed: true,
columnWidth: 45,
selectedRowKeys,
onChange: this.onSelectChange,
};
antd4 Table 對渲染條件進行了優化,對 props 進行「淺比較」,如果沒有變化不會觸發 render。
. ant-table-content 更改為 .ant-table-container
.ant-form-explain 更改為 .ant-form-item-explain
在 antd3.0 的時候,我們採用 user.userName 能夠讀到巢狀的屬性
{
title: '賬號',
dataIndex: 'user.userName',
key: 'userName',
width: 200,
}
antd4.0 對此做了修改,同 Form 的 name
{
title: '賬號',
dataIndex: ['user', 'userName'],
key: 'userName',
width: 200,
}
升級 antd4 後,發現一些表格分頁器多了 pageSize 切換的功能,程式碼中 onChange 又未對 size 做處理,會導致 底部分頁器 pageSize 和資料對不上,因此需要各自排查 Table 的 pagination 和 Pagination 元件,和請求列表介面的引數
<Table
rowKey="userId"
pagination={{
total: users.totalCount,
defaultPageSize: 10,
}}
onChange={this.handleTableChange}
style={{ height: tableScrollHeight }}
loading={this.state.loading}
columns={this.initColumns()}
dataSource={users.data}
scroll={{ x: 1100, y: tableScrollHeight }}
/>
handleTableChange = (pagination: any) => {
this.setState(
{
current: pagination.current,
},
this.search
);
};
search = (projectId?: any) => {
const { name, current } = this.state;
const { project } = this.props;
const params: any = {
projectId: projectId || project.id,
pageSize: 10,
currentPage: current || 1,
name: name || undefined,
removeAdmin: true,
};
this.loadUsers(params);
};
antd4.0 對此做了修改,同 Form 的 name
<Table
rowKey="userId"
pagination={{
showTotal: (total) => `共${total}條`,
total: users.totalCount,
current,
pageSize,
}}
onChange={this.handleTableChange}
style={{ height: tableScrollHeight }}
loading={this.state.loading}
columns={this.initColumns()}
dataSource={users.data}
scroll={{ x: 1100, y: tableScrollHeight }}
/>
handleTableChange = (pagination: any) => {
this.setState(
{
current: pagination.current,
pageSize: pagination.pageSize,
},
this.search
);
};
search = (projectId?: any) => {
const { name, current, pageSize } = this.state;
const { project } = this.props;
const params: any = {
projectId: projectId || project.id,
pageSize,
currentPage: current || 1,
name: name || undefined,
removeAdmin: true,
};
this.loadUsers(params);
};
另外,一些同學在 Table 中 既寫了 onChange,也寫了 onShowSizeChange,這個時候要注意,當切換頁碼條數的時候兩個方法都會觸發,onShowSizeChange 先觸發,onChange 後觸發,這個時候如果 onChange 內未對 pageSize 做處理可能導致切頁失敗,看下面程式碼就明白了,寫的時候稍微注意一下即可。
表格中如果要對錶格某一欄位進行排序需要在 columns item 裡設定 sorter 欄位,然後在 onChange 裡拿到 sorter 物件進行引數處理,再請求資料,需要注意的是,很多用到了 sorter.columnKey 來進行判斷,容易出現問題,sorter.columnKey === columns item.key,如果未設定 key,那麼獲取到的 columnKey 就為空,導致搜尋失效,要麼設定 key,再進行獲取,同理, sorter.field === columns item.dataIndex,設定 dataIndex,通過 sorter.field 進行獲取,兩者都可以
columns={
[
{
title: '建立時間',
dataIndex: 'gmtCreate1',
key: 'aa',
sorter: true,
render(n: any, record: any) {
return DateTime.formatDateTime(record.gmtCreate);
}
},
...
]
}
onChange={(pagination: any, filters: any, sorter: any) {
console.log(pagination, '--pagination');
console.log(filters, '--filters');
console.log(sorter, '--sorter');
}}
Tree 元件取消 value 屬性,現在只需要新增 key 屬性即可
特別注意, 此問題會導致功能出問題,需要重點關注!!!
在專案中經常在 TreeItem 中增加引數,如:<TreeItem value={value} data={data} >
。在拖拽等回撥中就可以通過 nodeData.props.data
的方式獲取到 data 的值。
但在 antd4 中,獲取引數的資料結構發生了改變,原先直接通過 props 點出來的不行了。
有兩種方式取值
nodeData.props.data.data
新版資料結構如下:
拖拽節點位置的確定與 3.x 相比進行了變更,官網並沒有說明。具體如下圖
左側為 3.x,右側為 4.x。
在3.x版本,只要把節點拖拽成目標節點的上中下,即代表著目標節點的同級上方,子集,同級下方
在 4.x 版本,是根據當前拖拽節點與目標節點的相對位置進行確定最終的拖拽結果。
當拖拽節點處於目標節點的下方,且相對左側對齊的位置趨近於零,則最終的位置為目標節點的同級下方。
當拖拽節點處於目標節點的下方,且相對左側一個縮近的位置。則最終的位置為目標節點的子集。
當拖拽節點處於目標節點的上方,且相對左側對齊的位置趨近於零,則最終的位置為目標節點的同級上方。
Pagination
自 4.1.0 版本起,會預設將 showSizeChanger
引數設定為 true ,因而在資料條數超過50時,pageSize 切換器會預設顯示。這個變化同樣適用於 Table 元件。可通過 showSizeChanger: false
關閉
如果 size 屬性值為 small,則刪除 size 屬性。
當我們在 Drawer 上 設定了 getContainer={false} 屬性之後,Drawer 會新增上 .ant-drawer-inline 的類名導致我們 position: fixed 失效
在 antd 3.0 中危險按鈕採用 type
使用如下:
設計改動點 type、dangr 屬性
使分頁不被選中
// 3.x
activeKey={undefined}
// 4.x
activeKey={null}
該篇文章詳細講解了如何從 antd3 升級到 antd4 其中的步驟,以及團隊在實踐過程中發現的一些問題和對應的解決方案。