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

Issue:
图片预览受控情况下切换问题
- [x] 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.33.0
- **Browser:** chrome101.0.4951.54

https://codesandbox.io/s/ecstatic-tereshkova-s6wlb4?file=/index.js

`current`受控的情况下不能切换。

![rbzM3mgF0k](https://user-images.githubusercontent.com/33169019/168971575-84b9e73f-bb44-4088-aaad-c8797741f3c8.jpg)

<!-- generated by arco-issue. DO NOT REMOVE -->

Pull request:
fix: preview switching under current control
<!--
  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 -->
<!-- Only support choose one type, if there are multiple types, you can add the `Type` column in the Changelog. -->

- [ ] New feature
- [x] Bug fix
- [ ] Enhancement
- [ ] Documentation change
- [ ] Coding style change
- [ ] Component style change
- [ ] Refactoring
- [ ] 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 -->

多图预览`<Image.PreviewGroup />`使用`current + onChange`受控的情况下不能切换， #877 。

## Solution

<!-- Describe how the problem is fixed in detail -->

由于是使用`useEffect`依赖于`currentIndex`变化触发的，在`current`受控的情况下使用`setCurrentIndex`，在`re-render`之后由于会被`propCurrentIndex`覆盖掉，所以并不会触发`Effect`，所以解决方案是在`setCurrentIndex`之前即触发`onChange`。

## 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 -->

与 https://codesandbox.io/s/ecstatic-tereshkova-s6wlb4?file=/index.js 相同的示例，能够切换图片。

<img width="1680" alt="image" src="https://user-images.githubusercontent.com/33169019/168984297-3ddccc5d-7ce4-47c5-aba9-cebf61e26d37.png">


<img width="1167" alt="image" src="https://user-images.githubusercontent.com/33169019/168984134-10584954-040b-44bc-bc63-2963cf97aa01.png">


## Changelog

| Component | Changelog(CN) | Changelog(EN) | Related issues |
| --------- | ------------- | ------------- | -------------- |
|     Image     |       修复 `<Image.PreviewGroup />`  组件在 `current` 受控时 `onChange` 事件不触发的问题        |      Fixed `<Image.PreviewGroup />` component's `onChange` event not firing when `current` is controlled        |       close #877          |

<!-- If there are multiple types, you can add the `Type` column in the Changelog, the value of the column is the same as `Types of changes` -->
## Checklist:

- [x] Test suite passes (`npm run test`)
- [x] Provide changelog for relevant changes (e.g. bug fixes and new features) if applicable.
- [x] 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/Anchor/link.tsx b/components/Anchor/link.tsx
index 612a5f59..f771df2a 100644
--- a/components/Anchor/link.tsx
+++ b/components/Anchor/link.tsx
@@ -10,7 +10,7 @@ import AnchorContext from './context';
 import { AnchorLinkProps } from './interface';
 import { ConfigContext } from '../ConfigProvider';
 import cs from '../_util/classNames';
-import { isString, isObject } from '../_util/is';
+import { isString, isObject, isUndefined, isNull } from '../_util/is';
 import useMergeProps from '../_util/hooks/useMergeProps';
 
 type AnchorLinkHandle = HTMLDivElement;
@@ -57,17 +57,19 @@ function Link(baseProps: AnchorLinkPropsWithChildren, ref) {
 
   return (
     <div className={classNames} style={style} ref={linkRef}>
-      <a
-        className={`${prefixCls}-title`}
-        title={isString(title) ? title : ''}
-        href={href}
-        data-href={href}
-        onClick={(e) => {
-          onLinkClick && onLinkClick(e, href);
-        }}
-      >
-        {title}
-      </a>
+      {!isUndefined(title) && !isNull(title) && (
+        <a
+          className={`${prefixCls}-title`}
+          title={isString(title) ? title : ''}
+          href={href}
+          data-href={href}
+          onClick={(e) => {
+            onLinkClick && onLinkClick(e, href);
+          }}
+        >
+          {title}
+        </a>
+      )}
       {children &&
         React.Children.map(children, (item) => {
           return (
diff --git a/components/Image/image-preview-group.tsx b/components/Image/image-preview-group.tsx
index b34fa273..4d6380fe 100644
--- a/components/Image/image-preview-group.tsx
+++ b/components/Image/image-preview-group.tsx
@@ -62,11 +62,6 @@ function PreviewGroup(props: PropsWithChildren<ImagePreviewGroupProps>, ref) {
     defaultValue: defaultCurrent,
   });
 
-  useEffect(() => {
-    if (isFirstRender) return;
-    onChange && onChange(currentIndex);
-  }, [currentIndex]);
-
   function registerPreviewUrl(id: number, url: string, preview: boolean) {
     if (!propPreviewUrlMap) {
       setPreviewUrlMap((pre) =>
@@ -100,6 +95,11 @@ function PreviewGroup(props: PropsWithChildren<ImagePreviewGroupProps>, ref) {
     onVisibleChange && onVisibleChange(visible, preVisible);
   };
 
+  const handleSwitch = (index: number) => {
+    onChange && onChange(index);
+    setCurrentIndex(index);
+  };
+
   const loopImageIndex = (children) => {
     let index = 0;
 
@@ -132,7 +132,7 @@ function PreviewGroup(props: PropsWithChildren<ImagePreviewGroupProps>, ref) {
         previewUrlMap: canPreviewUrlMap,
         infinite,
         currentIndex,
-        setCurrentIndex,
+        setCurrentIndex: handleSwitch,
         setPreviewUrlMap,
         registerPreviewUrl,
         visible,
diff --git a/components/Message/index.tsx b/components/Message/index.tsx
index af20c1dd..83d6a6de 100644
--- a/components/Message/index.tsx
+++ b/components/Message/index.tsx
@@ -156,6 +156,7 @@ class Message extends BaseNotification {
               <Notice
                 {...notice}
                 prefixCls={prefixClsMessage}
+                iconPrefix={prefixCls}
                 onClose={this.remove}
                 noticeType="message"
               />
diff --git a/components/Notification/index.tsx b/components/Notification/index.tsx
index 64140102..d1115bc4 100644
--- a/components/Notification/index.tsx
+++ b/components/Notification/index.tsx
@@ -153,6 +153,7 @@ class Notification extends BaseNotification {
                 {...notice}
                 onClose={this.remove}
                 prefixCls={prefixClsNotification}
+                iconPrefix={prefixCls}
                 noticeType="notification"
               />
             </CSSTransition>
diff --git a/components/Table/style/index.less b/components/Table/style/index.less
index 23cc14a4..dd97d595 100644
--- a/components/Table/style/index.less
+++ b/components/Table/style/index.less
@@ -184,6 +184,16 @@
     margin: 0;
     border-spacing: 0;
     border-collapse: separate;
+
+    thead tr:first-child {
+      th:first-child {
+        border-radius: @table-border-radius 0 0 0;
+      }
+
+      th:last-child {
+        border-radius: 0 @table-border-radius 0 0;
+      }
+    }
   }
 
   .@{table-cls-th} {
@@ -204,14 +214,6 @@
     &[colspan] {
       text-align: center;
     }
-
-    &:first-child {
-      border-radius: @table-border-radius 0 0 0;
-    }
-
-    &:last-child {
-      border-radius: 0 @table-border-radius 0 0;
-    }
   }
 
   .@{table-cls-td} {
diff --git a/components/_class/notice.tsx b/components/_class/notice.tsx
index 3fa76086..80fc05ca 100644
--- a/components/_class/notice.tsx
+++ b/components/_class/notice.tsx
@@ -7,6 +7,7 @@ import IconExclamationCircleFill from '../../icon/react-icon/IconExclamationCirc
 import IconLoading from '../../icon/react-icon/IconLoading';
 import cs from '../_util/classNames';
 import IconHover from '../_class/icon-hover';
+import { IconContext } from '../../icon/react-icon/context';
 
 export interface NoticeProps {
   style?: CSSProperties;
@@ -22,6 +23,7 @@ export interface NoticeProps {
   type?: string;
   btn?: ReactNode;
   prefixCls?: string;
+  iconPrefix?: string;
   noticeType?: 'message' | 'notification';
   update?: boolean;
   closable?: boolean;
@@ -77,7 +79,7 @@ class Notice extends Component<NoticeProps, {}> {
   };
 
   renderIcon = () => {
-    const { showIcon, icon, type, prefixCls } = this.props;
+    const { showIcon, icon, type, prefixCls, iconPrefix } = this.props;
     let content: ReactNode;
     if (icon) {
       content = icon;
@@ -101,6 +103,11 @@ class Notice extends Component<NoticeProps, {}> {
         default:
           break;
       }
+      content = (
+        <IconContext.Provider value={iconPrefix ? { prefixCls: iconPrefix } : {}}>
+          {content}
+        </IconContext.Provider>
+      );
     }
     return <span className={`${prefixCls}-icon`}>{content}</span>;
   };
@@ -126,6 +133,7 @@ class Notice extends Component<NoticeProps, {}> {
       prefixCls,
       closable,
       noticeType,
+      iconPrefix,
     } = this.props;
     const classNames = cs(
       prefixCls,
@@ -184,7 +192,9 @@ class Notice extends Component<NoticeProps, {}> {
                 className={`${prefixCls}-close-btn`}
                 onClick={this.onClose}
               >
-                <IconClose />
+                <IconContext.Provider value={iconPrefix ? { prefixCls: iconPrefix } : {}}>
+                  <IconClose />
+                </IconContext.Provider>
               </IconHover>
             )}
           </div>
diff --git a/components/_util/is.ts b/components/_util/is.ts
index 5301c8fc..cf5c6adc 100644
--- a/components/_util/is.ts
+++ b/components/_util/is.ts
@@ -48,6 +48,10 @@ export function isUndefined(obj: any): obj is undefined {
   return obj === undefined;
 }
 
+export function isNull(obj: any): obj is null {
+  return obj === null;
+}
+
 export function isFunction(obj: any): obj is (...args: any[]) => any {
   return typeof obj === 'function';
 }
diff --git a/icon/build/icon.template.nunjucks b/icon/build/icon.template.nunjucks
index 865937bc..ce3c2713 100644
--- a/icon/build/icon.template.nunjucks
+++ b/icon/build/icon.template.nunjucks
@@ -2,7 +2,7 @@ import React, { useContext } from 'react';
 import { IconContext } from '../context';
 
 function {{ iconName }}Component (iconProps, ref) {
-  const { prefixCls } = useContext(IconContext);
+  const { prefixCls = 'arco' } = useContext(IconContext);
   const { spin, className } = iconProps;
   const props = {
     "aria-hidden": true,


Test patch:
diff --git a/components/Message/__test__/index.test.tsx b/components/Message/__test__/index.test.tsx
index 62ba88c7..2385a84c 100644
--- a/components/Message/__test__/index.test.tsx
+++ b/components/Message/__test__/index.test.tsx
@@ -64,4 +64,21 @@ describe('open message', () => {
       expect($(`.arco-message-wrapper-${position} .arco-message`).length).toBe(1);
     });
   });
+
+  it('notice icon prefix', () => {
+    Message.config({
+      prefixCls: 'aaa',
+    });
+    Message.success({
+      content: 'New Content',
+    });
+
+    expect($('.aaa-message')).toHaveLength(1);
+
+    expect($('.aaa-icon-check-circle-fill')).toHaveLength(1);
+
+    Message.config({
+      prefixCls: 'arco',
+    });
+  });
 });
diff --git a/components/Notification/__test__/index.test.tsx b/components/Notification/__test__/index.test.tsx
index 857dc086..9acfd8c9 100644
--- a/components/Notification/__test__/index.test.tsx
+++ b/components/Notification/__test__/index.test.tsx
@@ -97,4 +97,21 @@ describe('open message', () => {
   //   jest.runAllTimers();
   //   expect($('.arco-notification').length).toBe(0);
   // });
+
+  it('notice icon prefix', () => {
+    Notification.config({
+      prefixCls: 'aaa',
+    });
+    Notification.success({
+      content: 'New Content',
+    });
+
+    expect($('.aaa-notification')).toHaveLength(1);
+
+    expect($('.aaa-icon-check-circle-fill')).toHaveLength(1);
+
+    Notification.config({
+      prefixCls: 'arco',
+    });
+  });
 });