Repository: arco-design/arco-design (ts)

Issue:
级联选中项多的时候会很卡
- [ ] I'm sure this does not appear in [the issue list of the repository](https://github.com/arco-design/arco-design/issues)

## Basic Info 
- **Package Name And Version:** @arco-design/web-react: 2.24.1
- **System:** mac os
- **Browser:** chrome
- **Reproduction Link:** https://codepen.io/pleasure233/pen/WNEBdEy?editors=001

## What is expected?
选中项多的时候不卡

## Steps to reproduce
例子里一级选到第五个的时候，再点勾选框，要等一会才会选中


<!-- generated by arco-issue. DO NOT REMOVE -->

Pull request description:
enhancement: cascader render logic
<!--
  Thanks so much for your PR and contribution.

  Before submitting, please make sure to follow the Pull Request Guidelines: https://github.com/arco-design/arco-design/blob/main/CONTRIBUTING.md
-->

<!-- Put an `x` in "[ ]" to check a box) -->

## Types of changes

<!-- What types of changes does this PR introduce -->
- [ ] New feature
- [ ] Bug fix
- [ ] Documentation change
- [ ] Coding style change
- [ ] Component style change
- [ ] Refactoring
- [x] Performance improvement
- [ ] Test cases
- [ ] Continuous integration
- [ ] Typescript definition change
- [ ] Breaking change
- [ ] Others 

## Background and context

<!-- Explain what problem does the PR solve -->
<!-- Link to related open issues if applicable -->

## Solution

<!-- Describe how the problem is fixed in detail -->

## How is the change tested?

<!-- Unit tests should be added/updated for bug fixes and new features, if applicable -->
<!-- Please describe how you tested the change. E.g. Creating/updating unit tests or attaching a screenshot of how it works with your change -->

## Changelog

| Component | Changelog(CN) | Changelog(EN) | Related issues |
| --------- | ------------- | ------------- | -------------- |
|    Cascader       |    优化 `Cascader` 组件大数据下多选时候选中操作卡顿的问题。           |   Optimized the problem of stuck operation when selecting multiple selections under the big data of the `Cascader` component.            |   Close #208              |

## Checklist:

- [ ] Test suite passes (`npm run test`)
- [ ] Provide changelog for relevant changes (e.g. bug fixes and new features) if applicable.
- [ ] Changes are submitted to the appropriate branch (e.g. features should be submitted to `feature` branch and others should be submitted to `main` branch)

## Other information

<!-- Please describe what other information that should be taken care of. E.g. describe the impact if introduce a breaking change -->


Patch:
diff --git a/components/Cascader/base/node.ts b/components/Cascader/base/node.ts
index 88d4d650..36124602 100644
--- a/components/Cascader/base/node.ts
+++ b/components/Cascader/base/node.ts
@@ -202,7 +202,7 @@ class Node<T> {
 
   // 忽略禁用设置选中状态
   public setCheckedStateIgnoreDisabled = (checked: boolean) => {
-    if (checked === this._checked) {
+    if (checked === Boolean(this._checked)) {
       return;
     }
     this.setCheckedProperty(checked);
diff --git a/components/Cascader/base/store.ts b/components/Cascader/base/store.ts
index 66d1fe05..07f021b9 100644
--- a/components/Cascader/base/store.ts
+++ b/components/Cascader/base/store.ts
@@ -1,4 +1,5 @@
 import isEqualWith from 'lodash/isEqualWith';
+import { valueInSet, transformValuesToSet } from '../util';
 import { isArray, isFunction, isString } from '../../_util/is';
 import { FieldNamesType, CascaderProps } from '../interface';
 import Node, { NodeProps } from './node';
@@ -74,11 +75,11 @@ class Store<T> {
    * @memberof Store
    */
   public setNodeCheckedByValue = (initValues: string[][]) => {
-    const values = initValues || [];
+    const valuesSet = transformValuesToSet(initValues);
 
     // 根据value设置节点初始选中状态
     this.flatNodes.forEach((node) => {
-      if (values.some((item) => isEqualWith(node.pathValue, item))) {
+      if (valueInSet(valuesSet, node.pathValue)) {
         node.setCheckedStateIgnoreDisabled(true);
       } else {
         node.setCheckedStateIgnoreDisabled(false);
diff --git a/components/Cascader/cascader.tsx b/components/Cascader/cascader.tsx
index dd4e4a4b..83be3d1c 100644
--- a/components/Cascader/cascader.tsx
+++ b/components/Cascader/cascader.tsx
@@ -1,4 +1,3 @@
-import isEqualWith from 'lodash/isEqualWith';
 import React, {
   CSSProperties,
   forwardRef,
@@ -23,10 +22,14 @@ import useUpdate from '../_util/hooks/useUpdate';
 import { Enter, Tab } from '../_util/keycode';
 import useCurrentRef from './hook/useRefCurrent';
 import useMergeProps from '../_util/hooks/useMergeProps';
-
-function isEmptyValue(value) {
-  return !value || (isArray(value) && value.length === 0);
-}
+import {
+  valueInSet,
+  transformValuesToSet,
+  isEmptyValue,
+  getConfig,
+  getStore,
+  formatValue,
+} from './util';
 
 export const DefaultFieldNames = {
   label: 'label',
@@ -36,31 +39,6 @@ export const DefaultFieldNames = {
   disabled: 'disabled',
 };
 
-function getConfig(props) {
-  return {
-    showEmptyChildren: props.showEmptyChildren,
-    changeOnSelect: props.changeOnSelect,
-    lazyload: !!props.loadMore,
-    fieldNames: props.fieldNames,
-    filterOption: props.filterOption,
-  };
-}
-
-function getStore(props, value) {
-  const tmp = value ? (Array.isArray(value[0]) ? value : [value]) : [];
-  return new Store(props.options || [], tmp, getConfig(props));
-}
-
-const formatValue = (value, isMultiple): string[][] | undefined => {
-  if (value === undefined) {
-    return [];
-  }
-  if (isMultiple) {
-    return value;
-  }
-  return [value];
-};
-
 const defaultProps: CascaderProps = {
   options: [],
   bordered: true,
@@ -83,12 +61,10 @@ function Cascader<T extends OptionProps>(baseProps: CascaderProps<T>, ref) {
   // 暂存被选中的值对应的节点。仅在onSearch的时候用到
   // 避免出现下拉列表改变，之前选中的option找不到对应的节点，展示上会出问题。
   const stashNodes = useRef<Store<T>['nodes']>([]);
-  const propsValue = 'value' in props ? formatValue(props.value, isMultiple) : undefined;
-  // 为什么不用useMergeValue呢？ 因为在props.value改变时，需要进行一些选中状态的处理，见以下 useUpdate
-  const [stateValue, setValue] = useState<string[][] | undefined>(
-    propsValue || ('defaultValue' in props ? formatValue(props.defaultValue, isMultiple) : [])
-  );
-  const mergeValue = 'value' in props ? propsValue : stateValue;
+  const [mergeValue, setValue, stateValue] = useMergeValue([], {
+    value: 'value' in props ? formatValue(props.value, isMultiple) : undefined,
+    defaultValue: 'defaultValue' in props ? formatValue(props.defaultValue, isMultiple) : undefined,
+  });
 
   const [popupVisible, setPopupVisible] = useMergeValue(false, {
     value: props.popupVisible,
@@ -122,29 +98,40 @@ function Cascader<T extends OptionProps>(baseProps: CascaderProps<T>, ref) {
   useUpdate(() => {
     if ('value' in props) {
       const newValue = formatValue(props.value, isMultiple);
-      if (!isEqualWith(stateValue, newValue)) {
-        store.setNodeCheckedByValue(newValue);
-        setValue(newValue);
-      }
+      store.setNodeCheckedByValue(newValue);
+      // useMergeProps do this
+      // setValue(newValue);
     }
   }, [props.value, stateValue, isMultiple]);
 
   useImperativeHandle(ref, () => selectRef.current, []);
 
   const updateStashNodes = (nodes) => {
-    stashNodes.current = [].concat(nodes, stashNodes.current);
+    stashNodes.current = Array.from(new Set([].concat(nodes, stashNodes.current)));
   };
 
   const getSelectedOptionsByValue = (values: string[][]): OptionProps[][] => {
-    const nodes = store.getCheckedNodes().concat(stashNodes.current);
     const result = [];
+    const valuesSet = transformValuesToSet(values);
+
+    const findValue = (nodes) => {
+      nodes.some((node) => {
+        if (valueInSet(valuesSet, node.pathValue)) {
+          result.push(node.getPathNodes().map((x) => x._data));
+          valuesSet.delete(node.pathValue);
+        }
+        if (!valuesSet.size) {
+          return true;
+        }
+      });
+    };
+
+    findValue(store.getCheckedNodes());
+
+    if (valuesSet.size) {
+      findValue(stashNodes.current);
+    }
 
-    values.map((value) => {
-      const node = nodes.find((item) => isEqualWith(item.pathValue, value));
-      if (node) {
-        result.push(node.getPathNodes().map((x) => x._data));
-      }
-    });
     return result;
   };
 
@@ -195,7 +182,7 @@ function Cascader<T extends OptionProps>(baseProps: CascaderProps<T>, ref) {
     }
     setValue((mergeValue) => {
       const { onChange, changeOnSelect, expandTrigger } = props;
-      const isSame = isEqualWith(mergeValue, newValue);
+      const isSame = mergeValue === newValue;
 
       if (!isSame) {
         if (isTouch || !isMultiple) {
diff --git a/components/Cascader/panel/list.tsx b/components/Cascader/panel/list.tsx
index 52f91cc2..cd583b58 100644
--- a/components/Cascader/panel/list.tsx
+++ b/components/Cascader/panel/list.tsx
@@ -112,15 +112,20 @@ const ListPanel = <T extends OptionProps>(props: CascaderPanelProps<T>) => {
     const value = checkedNodes.map((node) => node.pathValue);
     const newValue = [...inexistenceValue, ...value];
 
+    const indexMap = props.value.reduce((map, next, index) => {
+      map.set(next, index);
+      return map;
+    }, new Map());
+
     // 按照当前props.value的顺序排序
     newValue.sort((a, b) => {
-      const aIndex = props.value.findIndex((item) => isEqualWith(item, a));
-      const bIndex = props.value.findIndex((item) => isEqualWith(item, b));
+      const aIndex = indexMap.get(a);
+      const bIndex = indexMap.get(b);
 
-      if (aIndex === -1) {
+      if (aIndex === undefined) {
         return 1;
       }
-      if (bIndex === -1) {
+      if (bIndex === undefined) {
         return -1;
       }
       return aIndex - bIndex;
diff --git a/components/Cascader/panel/option.tsx b/components/Cascader/panel/option.tsx
index eeab4794..79df9538 100644
--- a/components/Cascader/panel/option.tsx
+++ b/components/Cascader/panel/option.tsx
@@ -24,17 +24,6 @@ const Option = <T extends OptionProps>(props: CascaderOptionProps<T>) => {
 
   const checkboxDisabled = option.disabled || (multiple && option.disableCheckbox);
 
-  const dom = (
-    <div
-      className={`${prefixCls}-list-item-label`}
-      onClick={option.disabled ? undefined : props.onClickOption}
-      onMouseEnter={option.isLeaf || option.disabled ? undefined : props.onMouseEnter}
-      onDoubleClick={checkboxDisabled ? undefined : props.onDoubleClickOption}
-    >
-      {renderOption ? renderOption() : option.label}
-      {option.isLeaf ? selected && <IconCheck /> : option.loading ? <IconLoading /> : <IconRight />}
-    </div>
-  );
   return (
     <>
       {multiple ? (
@@ -48,7 +37,21 @@ const Option = <T extends OptionProps>(props: CascaderOptionProps<T>) => {
       ) : (
         ''
       )}
-      {dom}
+      <div
+        className={`${prefixCls}-list-item-label`}
+        onClick={option.disabled ? undefined : props.onClickOption}
+        onMouseEnter={option.isLeaf || option.disabled ? undefined : props.onMouseEnter}
+        onDoubleClick={checkboxDisabled ? undefined : props.onDoubleClickOption}
+      >
+        {renderOption ? renderOption() : option.label}
+        {option.isLeaf ? (
+          selected && <IconCheck />
+        ) : option.loading ? (
+          <IconLoading />
+        ) : (
+          <IconRight />
+        )}
+      </div>
     </>
   );
 };
diff --git a/components/Cascader/util.tsx b/components/Cascader/util.tsx
new file mode 100644
index 00000000..81bb2a6c
--- /dev/null
+++ b/components/Cascader/util.tsx
@@ -0,0 +1,48 @@
+import { isArray } from '../_util/is';
+import Store from './base/store';
+
+export function isEmptyValue(value) {
+  return !value || (isArray(value) && value.length === 0);
+}
+
+export function getConfig(props) {
+  return {
+    showEmptyChildren: props.showEmptyChildren,
+    changeOnSelect: props.changeOnSelect,
+    lazyload: !!props.loadMore,
+    fieldNames: props.fieldNames,
+    filterOption: props.filterOption,
+  };
+}
+
+export function getStore(props, value) {
+  const tmp = value ? (Array.isArray(value[0]) ? value : [value]) : [];
+  return new Store(props.options || [], tmp, getConfig(props));
+}
+
+export const formatValue = (value, isMultiple): string[][] | undefined => {
+  if (value === undefined) {
+    return [];
+  }
+  if (isMultiple) {
+    return value;
+  }
+  return [value];
+};
+
+const ValueSeparator = '__arco_cascader__';
+
+export const transformValuesToSet = (values: string[][]) => {
+  const _values = values || [];
+  const valuesSet = _values.reduce((set, next) => {
+    set.add(next.join(ValueSeparator));
+    return set;
+  }, new Set());
+
+  return valuesSet;
+};
+
+export const valueInSet = (set, value: string[]) => {
+  const _value = value || [];
+  return set.has(_value.join(ValueSeparator));
+};
diff --git a/components/Popconfirm/README.en-US.md b/components/Popconfirm/README.en-US.md
index 381332ec..dccf4d74 100644
--- a/components/Popconfirm/README.en-US.md
+++ b/components/Popconfirm/README.en-US.md
@@ -25,8 +25,8 @@ A simple confirmation box of an action.
 |okType|The type of the ok button|`ButtonProps['type']`|`primary`|-|
 |okButtonProps|The props of the ok button|`ButtonProps`|`-`|-|
 |cancelButtonProps|The props of the cancel button|`ButtonProps`|`-`|-|
-|onOk|Callback when click the ok button|`() => void`|`-`|-|
-|onCancel|Callback when click the cancel button|`() => void`|`-`|-|
+|onOk|Callback when click the ok button|`(e: React.MouseEvent) => void`|`-`|-|
+|onCancel|Callback when click the cancel button|`(e: React.MouseEvent) => void`|`-`|-|
 |defaultPopupVisible|Whether the confirmation box is visible by default|`boolean`|`-`|-|
 |popupVisible|Whether the confirmation box is visible|`boolean`|`-`|-|
 |onVisibleChange|Callback when the visibility of the confirmation box is changed|`(visible: boolean) => void`|`-`|-|
diff --git a/components/Popconfirm/README.zh-CN.md b/components/Popconfirm/README.zh-CN.md
index dae0a5cb..2e91d042 100644
--- a/components/Popconfirm/README.zh-CN.md
+++ b/components/Popconfirm/README.zh-CN.md
@@ -25,8 +25,8 @@
 |okType|确认按钮的类型|`ButtonProps['type']`|`primary`|-|
 |okButtonProps|确定按钮的参数，可接受 `Button` 组件的所有参数|`ButtonProps`|`-`|-|
 |cancelButtonProps|取消按钮的参数，可接受 `Button` 组件的所有参数|`ButtonProps`|`-`|-|
-|onOk|点击确认按钮的回调函数|`() => void`|`-`|-|
-|onCancel|点击取消按钮的回调函数|`() => void`|`-`|-|
+|onOk|点击确认按钮的回调函数|`(e: React.MouseEvent) => void`|`-`|-|
+|onCancel|点击取消按钮的回调函数|`(e: React.MouseEvent) => void`|`-`|-|
 |defaultPopupVisible|默认弹出框是打开还是关闭|`boolean`|`-`|-|
 |popupVisible|弹出框是打开还是关闭。(受控)|`boolean`|`-`|-|
 |onVisibleChange|弹出打开和关闭触发的回调|`(visible: boolean) => void`|`-`|-|
diff --git a/components/Popconfirm/index.tsx b/components/Popconfirm/index.tsx
index 900cc7ba..0c5b02d9 100644
--- a/components/Popconfirm/index.tsx
+++ b/components/Popconfirm/index.tsx
@@ -70,17 +70,17 @@ function Popconfirm(baseProps: PropsWithChildren<PopconfirmProps>, ref) {
     handleVisibleChange(false);
   };
 
-  const onCancelPopconfirm = () => {
+  const onCancelPopconfirm = (e) => {
     closePopconfirm();
-    props.onCancel && props.onCancel();
+    props.onCancel && props.onCancel(e);
   };
 
-  const onConfirmPopconfirm = () => {
+  const onConfirmPopconfirm = (e) => {
     const _onConfirm = props.onOk || props.onConfirm;
 
     let ret;
     if (_onConfirm) {
-      ret = _onConfirm();
+      ret = _onConfirm(e);
     }
     if (ret && ret.then) {
       setLoading(true);
diff --git a/components/Popconfirm/interface.ts b/components/Popconfirm/interface.ts
index 66e4a5f5..d6c8c5c9 100644
--- a/components/Popconfirm/interface.ts
+++ b/components/Popconfirm/interface.ts
@@ -1,4 +1,4 @@
-import { CSSProperties, ReactNode } from 'react';
+import React, { CSSProperties, ReactNode } from 'react';
 import { TriggerProps } from '../Trigger';
 import { ButtonProps } from '../Button';
 
@@ -69,17 +69,19 @@ export interface PopconfirmProps {
    */
   cancelButtonProps?: ButtonProps;
   /**
-   * @zh 点击确认按钮的回调函数
-   * @en Callback when click the ok button
+   * @zh 点击确认按钮的回调函数。回调函数 `event` 参数在 `2.29.0` 支持
+   * @en Callback when click the ok button. Callback `event` params is supported in `2.29.0`
    */
-  onOk?: () => void;
-  // 统一为 onOk, onConfirm 保留，不建议使用
-  onConfirm?: () => void;
+  onOk?: (e: React.MouseEvent) => void;
   /**
-   * @zh 点击取消按钮的回调函数
-   * @en Callback when click the cancel button
+   * @deprecated 统一为 onOk, onConfirm 保留，不建议使用
    */
-  onCancel?: () => void;
+  onConfirm?: (e: React.MouseEvent) => void;
+  /**
+   * @zh 点击取消按钮的回调函数。 回调函数 `event` 参数在 `2.29.0` 支持
+   * @en Callback when click the cancel button. Callback `event` params is supported in `2.29.0`
+   */
+  onCancel?: (e: React.MouseEvent) => void;
   /**
    * @zh 默认弹出框是打开还是关闭
    * @en Whether the confirmation box is visible by default
diff --git a/components/Spin/README.en-US.md b/components/Spin/README.en-US.md
index e19c33af..1a41f862 100644
--- a/components/Spin/README.en-US.md
+++ b/components/Spin/README.en-US.md
@@ -12,14 +12,15 @@ Used for the loading state of pages. When part of the page is waiting for asynch
 
 ### Spin
 
-|Property|Description|Type|DefaultValue|
-|---|---|---|---|
-|style|Additional style|`CSSProperties`|`-`|
-|className|Additional css class|`string \| string[]`|`-`|
-|loading|Whether is loading status|`boolean`|`-`|
-|size|The size of loading icon|`number`|`-`|
-|icon|Customize icon which will be rotated automatically.|`ReactNode`|`-`|
-|element|Customize element which won't be rotated automatically, such as image/gif.|`ReactNode`|`-`|
-|tip|Customize description content when Spin has children|`string \| ReactNode`|`-`|
-|delay|Specifies a delay(ms) for loading state|`number`|`-`|
-|dot|Whether to use dot type animation|`boolean`|`-`|
+|Property|Description|Type|DefaultValue|Version|
+|---|---|---|---|---|
+|style|Additional style|`CSSProperties`|`-`|-|
+|className|Additional css class|`string \| string[]`|`-`|-|
+|loading|Whether is loading status|`boolean`|`-`|-|
+|size|The size of loading icon|`number`|`-`|-|
+|icon|Customize icon which will be rotated automatically.|`ReactNode`|`-`|-|
+|element|Customize element which won't be rotated automatically, such as image/gif.|`ReactNode`|`-`|-|
+|tip|Customize description content when Spin has children|`string \| ReactNode`|`-`|-|
+|delay|Specifies a delay(ms) for loading state|`number`|`-`|-|
+|dot|Whether to use dot type animation|`boolean`|`-`|-|
+|block|Whether it is a block-level element|`boolean`|`-`|2.29.0|
diff --git a/components/Spin/README.zh-CN.md b/components/Spin/README.zh-CN.md
index eebe147d..55874ecb 100644
--- a/components/Spin/README.zh-CN.md
+++ b/components/Spin/README.zh-CN.md
@@ -11,14 +11,15 @@
 ## API
 ### Spin
 
-|参数名|描述|类型|默认值|
-|---|---|---|---|
-|style|节点样式|`CSSProperties`|`-`|
-|className|节点类名|`string \| string[]`|`-`|
-|loading|是否为加载状态|`boolean`|`-`|
-|size|加载动画的尺寸|`number`|`-`|
-|icon|自定义图标，会自动旋转。|`ReactNode`|`-`|
-|element|自定义元素，非图标，不附带旋转效果。可用于设置为 gif 图片等。|`ReactNode`|`-`|
-|tip|加载的文案|`string \| ReactNode`|`-`|
-|delay|延迟显示加载的时间 (ms)|`number`|`-`|
-|dot|是否使用点类型的动画|`boolean`|`-`|
+|参数名|描述|类型|默认值|版本|
+|---|---|---|---|---|
+|style|节点样式|`CSSProperties`|`-`|-|
+|className|节点类名|`string \| string[]`|`-`|-|
+|loading|是否为加载状态|`boolean`|`-`|-|
+|size|加载动画的尺寸|`number`|`-`|-|
+|icon|自定义图标，会自动旋转。|`ReactNode`|`-`|-|
+|element|自定义元素，非图标，不附带旋转效果。可用于设置为 gif 图片等。|`ReactNode`|`-`|-|
+|tip|加载的文案|`string \| ReactNode`|`-`|-|
+|delay|延迟显示加载的时间 (ms)|`number`|`-`|-|
+|dot|是否使用点类型的动画|`boolean`|`-`|-|
+|block|是否为块级元素|`boolean`|`-`|2.29.0|
diff --git a/components/Spin/index.tsx b/components/Spin/index.tsx
index dae6ace6..73887138 100644
--- a/components/Spin/index.tsx
+++ b/components/Spin/index.tsx
@@ -21,6 +21,7 @@ function Spin(baseProps: SpinProps, ref) {
     tip,
     dot,
     delay,
+    block = false,
   } = props;
 
   const [loading, setLoading] = useState<boolean>(delay ? false : propLoading);
@@ -56,6 +57,7 @@ function Spin(baseProps: SpinProps, ref) {
       className={cs(
         prefixCls,
         {
+          [`${prefixCls}-block`]: block,
           [`${prefixCls}-loading`]: _usedLoading,
           [`${prefixCls}-with-tip`]: tip && !children,
         },
diff --git a/components/Spin/interface.ts b/components/Spin/interface.ts
index a70a3ef8..afe3e5da 100644
--- a/components/Spin/interface.ts
+++ b/components/Spin/interface.ts
@@ -42,4 +42,11 @@ export interface SpinProps {
    * @en Whether to use dot type animation
    */
   dot?: boolean;
+
+  /**
+   * @zh 是否为块级元素
+   * @en Whether it is a block-level element
+   * @version 2.29.0
+   */
+  block?: boolean;
 }
diff --git a/components/Spin/style/index.less b/components/Spin/style/index.less
index 0312a649..cc11454b 100644
--- a/components/Spin/style/index.less
+++ b/components/Spin/style/index.less
@@ -5,6 +5,10 @@
 .@{spin-prefix-cls} {
   display: inline-block;
 
+  &-block {
+    display: block;
+  }
+
   &-with-tip {
     text-align: center;
   }
diff --git a/stories/components/Cascader.jsx b/stories/components/Cascader.jsx
index f849319f..dd5a0a89 100644
--- a/stories/components/Cascader.jsx
+++ b/stories/components/Cascader.jsx
@@ -1,81 +1,40 @@
 import React from 'react';
-import { Cascader, Divider, Space } from '@self';
+import {Cascader} from '@self'
 
-const options = [
-  {
-    value: 'beijing',
-    label: 'Beijing',
-    children: [
-      {
-        value: 'Beijing',
-        label: 'Beijing',
-        children: [
-          {
-            value: 'chaoyang',
-            label: 'Chaoyang',
-            children: [
-              {
-                value: 'datunli',
-                label: 'Datunli',
-              },
-            ],
-          },
-        ],
-      },
-    ],
-  },
-  {
-    value: 'shanghai',
-    label: 'Shanghai',
-    children: [
-      {
-        value: 'shanghaishi',
-        label: 'Shanghai',
-        children: [
-          {
-            value: 'huangpu',
-            label: 'Huangpu',
-          },
-        ],
-      },
-    ],
-  },
-];
+const getOptionList = (labelPrefix: string, valuePrefix: string, len: number) => {
+    return Array(len)
+      .fill(0)
+      .map((_, index) => {
+        return {
+          label: `${labelPrefix}${index}`,
+          value: `${valuePrefix}${index}`,
+        };
+      });
+  };
+  const options = getOptionList('选项', 'value', 10).map(item => {
+    return {
+      ...item,
+      children: getOptionList(`${item.label}-`, `${item.value}-`, 10).map(item2 => {
+        return {
+          ...item2,
+          children: getOptionList(`${item2.label}-`, `${item2.value}-`, 30),
+        };
+      }),
+    };
+  });
 
-class Demo extends React.Component {
-  render() {
-    return (
-      <Space size="large">
-        <Cascader
-          placeholder="Please select ..."
-          style={{ width: 300 }}
-          options={options}
-          dropdownRender={(menu) => {
-            return (
-              <div>
-                {menu}
-                <Divider style={{ margin: 0 }} />
-                <div style={{ margin: 4 }}> The footer content</div>
-              </div>
-            );
-          }}
-        />
-        <Cascader
-          style={{ width: 300 }}
-          dropdownColumnRender={(menu, level) => {
-            return (
-              <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
-                <div style={{ flex: 1 }}>{menu}</div>
-                <Divider style={{ margin: 0 }} />
-                <div style={{ margin: 4 }}> The footer content {level}</div>
-              </div>
-            );
-          }}
-          options={options}
-        />
-      </Space>
-    );
-  }
+export default () => {
+  const [value, setValue] = React.useState([]);
+  return (
+    <Cascader
+      placeholder="Please select ..."
+      style={{ width: 300 }}
+      options={options}
+      mode="multiple"
+      allowClear
+      maxTagCount={20}
+      onChange={setValue}
+      value={value}
+    />
+  );
 }
-
-export default Demo;


Test patch:
diff --git a/components/Spin/__test__/index.test.tsx b/components/Spin/__test__/index.test.tsx
index ba7c6988..1e782c82 100644
--- a/components/Spin/__test__/index.test.tsx
+++ b/components/Spin/__test__/index.test.tsx
@@ -51,4 +51,14 @@ describe('Spin', () => {
     wrapper.update();
     expect(wrapper.find('.arco-spin-loading-layer').length).toBe(1);
   });
+
+  it('spin with display:block', () => {
+    const wrapper = mount(
+      <Spin loading block>
+        <Alert type="info" content="Content" />
+      </Spin>
+    );
+
+    expect(wrapper.find('.arco-spin-block').length).toBe(1);
+  });
 });