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

Issue:
在循环里调用多次Notification.info，渲染出的通知个数异常
- [ ] 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.32.0
- **Browser:** chrome100.0.4896.88
- **Reproduction Link:** https://codesandbox.io/s/lively-dust-9orz62?file=/src/App.js

## Extra info
本地调试之后，分析原因是同步调用Notification.info ，最终调用到  notificationInstance[position].add  ，React 对该函数里的setState 进行了批处理的优化，导致 notificationInstance[position].state.notices 得不到及时的更新，最终只渲染出部分通知。临时的解决方法也有，就是破坏批处理：在Notification.info外层 包一层setTimeout(()=>{},0) .

## What is expected?
渲染产出和循环次数相同的通知

## Steps to reproduce
在循环中同步的调用 Notification.info；for (let i = 0; i < 6; i  ) {
      // debugger
      const instance = Notification.info({ content: ` 666 Notification${i}` })
      console.log('666 instance', instance, i)
    }


<!-- generated by arco-issue. DO NOT REMOVE -->

Pull request:
fix: notification count error
<!--
  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 -->

## 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 |
| --------- | ------------- | ------------- | -------------- |
|   Notification        |   修复 `Notification` 组件在并发弹出多个提醒框时，只渲染出部分通知的 bug。  |   Fixed a bug where the `Notification` component only rendered some notifications when multiple reminder boxes popped up concurrently.            |                Close #789  |

<!-- 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:

- [ ] 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/Form/store.tsx b/components/Form/store.tsx
index 7cfb94c3..6d6649b2 100644
--- a/components/Form/store.tsx
+++ b/components/Form/store.tsx
@@ -8,23 +8,11 @@ import Control from './control';
 import { FieldError, FormProps, ValidateFieldsErrors, KeyType, FormValidateFn } from './interface';
 import promisify from './promisify';
 
-type DeepPartial<T> = T extends
-  | string
-  | number
-  | bigint
-  | boolean
-  | null
-  | undefined
-  | symbol
-  | Date
-  ? T | undefined
-  : T extends Array<infer U>
-  ? Array<DeepPartial<U>>
-  : T extends ReadonlyArray<infer U>
-  ? ReadonlyArray<DeepPartial<U>>
-  : {
-      [K in keyof T]?: DeepPartial<T[K]>;
-    };
+export type DeepPartial<T> = T extends object
+  ? {
+      [P in keyof T]?: DeepPartial<T[P]>;
+    }
+  : T;
 
 /**
  * setFieldValue: setFieldsValue, setFieldValue, setFields
@@ -204,8 +192,8 @@ class Store<
     if (!field) return;
     const prev = cloneDeep(this.store);
     set(this.store, field, value);
-    this.triggerValuesChange({ [field]: value } as unknown as Partial<FormData>);
-    this.triggerTouchChange({ [field]: value } as unknown as Partial<FormData>);
+    this.triggerValuesChange(({ [field]: value } as unknown) as Partial<FormData>);
+    this.triggerTouchChange(({ [field]: value } as unknown) as Partial<FormData>);
 
     this.notify('innerSetValue', { prev, field, ...options, changeValues: { [field]: value } });
   };
@@ -252,14 +240,16 @@ class Store<
   };
 
   // 外部调用，设置多个表单控件的值，以及 error，touch 信息。
-  public setFields = (obj: {
-    [field in FieldKey]?: {
-      value?: FieldValue;
-      error?: FieldError<FieldValue>;
-      touched?: boolean;
-      warning?: React.ReactNode;
-    };
-  }) => {
+  public setFields = (
+    obj: {
+      [field in FieldKey]?: {
+        value?: FieldValue;
+        error?: FieldError<FieldValue>;
+        touched?: boolean;
+        warning?: React.ReactNode;
+      };
+    }
+  ) => {
     const fields = Object.keys(obj) as FieldKey[];
     const changeValues = {} as any;
     fields.forEach((field) => {
@@ -350,7 +340,7 @@ class Store<
     if (fields && isArray(fields)) {
       const changeValues = {} as any;
       fields.forEach((field) => {
-        set(this.store, field, this.initialValues[field as unknown as keyof FormData]);
+        set(this.store, field, this.initialValues[(field as unknown) as keyof FormData]);
         changeValues[field] = get(this.store, field);
       });
 
diff --git a/components/InputNumber/index.tsx b/components/InputNumber/index.tsx
index 78aa302f..1293196e 100644
--- a/components/InputNumber/index.tsx
+++ b/components/InputNumber/index.tsx
@@ -20,6 +20,7 @@ import { RefInputType } from '../Input/interface';
 import { InputNumberProps } from './interface';
 import useMergeProps from '../_util/hooks/useMergeProps';
 import omit from '../_util/omit';
+import { toFixed } from './utils';
 
 NP.enableBoundaryChecking(false);
 
@@ -140,7 +141,7 @@ function InputNumber(baseProps: InputNumberProps, ref) {
 
       return isNumber(finalValue)
         ? isNumber(mergedPrecision)
-          ? Number(finalValue.toFixed(mergedPrecision))
+          ? Number(toFixed(finalValue, mergedPrecision))
           : finalValue
         : undefined;
     },
@@ -200,7 +201,7 @@ function InputNumber(baseProps: InputNumberProps, ref) {
     if (isUserInputting) {
       _value = inputValue;
     } else if (isNumber(value) && isNumber(mergedPrecision)) {
-      _value = value.toFixed(mergedPrecision);
+      _value = toFixed(value, mergedPrecision);
     } else if (value == null) {
       _value = '';
     } else {
diff --git a/components/InputNumber/utils.ts b/components/InputNumber/utils.ts
new file mode 100644
index 00000000..9ddd91ec
--- /dev/null
+++ b/components/InputNumber/utils.ts
@@ -0,0 +1,5 @@
+// Replace number.toFixed with Math.round
+export function toFixed(number: number, precision: number): string {
+  const pow = 10 ** precision;
+  return (Math.round(number * pow) / pow).toFixed(precision);
+}
diff --git a/components/Mentions/index.tsx b/components/Mentions/index.tsx
index 47a3a6f9..902add5b 100644
--- a/components/Mentions/index.tsx
+++ b/components/Mentions/index.tsx
@@ -4,7 +4,7 @@ import { ConfigContext } from '../ConfigProvider';
 import Input from '../Input';
 import Select from '../Select';
 import { MentionsProps } from './interface';
-import { Esc, Enter, ArrowUp, ArrowDown } from '../_util/keycode';
+import { ArrowDown, ArrowUp, Enter, Esc } from '../_util/keycode';
 import { getBeforeSelectionText, getLastMeasureIndex, isValidSearch } from './utils';
 import useMergeValue from '../_util/hooks/useMergeValue';
 import useMergeProps from '../_util/hooks/useMergeProps';
@@ -37,6 +37,7 @@ function Mentions(baseProps: MentionsProps, ref) {
     getPopupContainer,
     onChange,
     onSearch,
+    notFoundContent,
     ...rest
   } = props;
 
@@ -161,7 +162,7 @@ function Mentions(baseProps: MentionsProps, ref) {
             ref={refSelect}
             options={options}
             inputValue={measureInfo.text}
-            notFoundContent={null}
+            notFoundContent={notFoundContent}
             triggerElement={
               <span className={`${prefixCls}-measure-trigger`}>{measureInfo.prefix}</span>
             }
diff --git a/components/Pagination/page-item.tsx b/components/Pagination/page-item.tsx
index 997fe444..21df9a2e 100644
--- a/components/Pagination/page-item.tsx
+++ b/components/Pagination/page-item.tsx
@@ -97,7 +97,7 @@ function Pager(props: PagerProps) {
       style={style}
       className={classnames}
       onClick={onClick}
-      aria-label={locale.Pagination.currentPage.replace('{0}', pageNum)}
+      aria-label={locale.Pagination.currentPage?.replace('{0}', pageNum)}
       {...ariaCurrentProps}
     >
       {itemRender ? itemRender(pageNum, 'page', pageNum) : pageNum}
@@ -141,8 +141,8 @@ export const JumpPager = (props: JumpPagerProps) => {
 
   const ariaLabel =
     jumpPage > 0
-      ? locale.Pagination.nextSomePages.replace('{0}', jumpPage)
-      : locale.Pagination.prevSomePages.replace('{0}', -jumpPage);
+      ? locale.Pagination.nextSomePages?.replace('{0}', jumpPage)
+      : locale.Pagination.prevSomePages?.replace('{0}', -jumpPage);
 
   return (
     <li style={pageItemStyle} className={cls} onClick={onClick} aria-label={ariaLabel}>
diff --git a/components/Pagination/pagination.tsx b/components/Pagination/pagination.tsx
index 0cea8c58..601b11f1 100644
--- a/components/Pagination/pagination.tsx
+++ b/components/Pagination/pagination.tsx
@@ -290,7 +290,7 @@ function Pagination(baseProps: PaginationProps, ref) {
   if (typeof showTotal === 'boolean' && showTotal) {
     totalElement = (
       <div className={`${prefixCls}-total-text`}>
-        {locale.Pagination.total.replace('{0}', total)}
+        {locale.Pagination.total?.replace('{0}', total)}
       </div>
     );
   }
diff --git a/components/_class/notification.tsx b/components/_class/notification.tsx
index 8eacd0cc..9e56dc5f 100644
--- a/components/_class/notification.tsx
+++ b/components/_class/notification.tsx
@@ -5,7 +5,7 @@ function getId(noticeProps) {
   if (noticeProps.id) {
     return noticeProps.id;
   }
-  return `arco_notice_id_${Date.now()}`;
+  return `arco_notice_id_${Math.random().toFixed(10).slice(2)}`;
 }
 
 interface BaseNoticeState {
@@ -25,20 +25,24 @@ class BaseNotice extends Component<any, BaseNoticeState> {
   }
 
   add = (noticeProps) => {
-    const oldNotices = this.state.notices;
-    // update notice
-    if (noticeProps.id && ~oldNotices.findIndex((notice) => notice.id === noticeProps.id)) {
-      this.update(noticeProps);
-      return noticeProps.id;
-    }
     const id: string = getId(noticeProps);
-    const newNotices = oldNotices.concat({
-      ...noticeProps,
-      id,
-    });
-    this.setState({
-      notices: newNotices,
-      position: noticeProps.position,
+
+    this.setState((prevState) => {
+      const oldNotices = prevState.notices;
+
+      // update notice
+      if (noticeProps.id && ~oldNotices.findIndex((notice) => notice.id === noticeProps.id)) {
+        this.update(noticeProps);
+        return prevState;
+      }
+
+      return {
+        notices: oldNotices.concat({
+          ...noticeProps,
+          id,
+        }),
+        position: noticeProps.position,
+      };
     });
     return id;
   };


Test patch:
diff --git a/components/InputNumber/__test__/index.test.tsx b/components/InputNumber/__test__/index.test.tsx
index 4d677eb4..a765c578 100644
--- a/components/InputNumber/__test__/index.test.tsx
+++ b/components/InputNumber/__test__/index.test.tsx
@@ -130,10 +130,14 @@ describe('InputNumber ', () => {
   });
 
   it('props precision correctly', () => {
-    const wrapper = mountInputNumber(
-      <InputNumber defaultValue={6} min={0} max={15} step={0.01} precision={3} />
-    );
-    expect(+wrapper.find('input').prop('value')).toBe(6);
+    let wrapper = mountInputNumber(<InputNumber defaultValue={6} step={0.01} precision={3} />);
+    expect(wrapper.find('input').prop('value')).toBe('6.000');
+
+    wrapper = mountInputNumber(<InputNumber defaultValue={6.1234} precision={3} />);
+    expect(wrapper.find('input').prop('value')).toBe('6.123');
+
+    wrapper = mountInputNumber(<InputNumber defaultValue={6.1235} precision={3} />);
+    expect(wrapper.find('input').prop('value')).toBe('6.124');
   });
 
   describe('formatter', () => {