diff --git a/components/anchor/Anchor.tsx b/components/anchor/Anchor.tsx index 82b871267c77..851a2b965392 100644 --- a/components/anchor/Anchor.tsx +++ b/components/anchor/Anchor.tsx @@ -257,6 +257,16 @@ export default class Anchor extends React.Component ({ + registerLink: this.registerLink, + unregisterLink: this.unregisterLink, + scrollTo: this.handleScrollTo, + activeLink: link, + onClick: onClickFn, + }), + ); + render() { const { getPrefixCls, direction } = this.context; const { @@ -310,13 +320,7 @@ export default class Anchor extends React.Component ); - const contextValue = memoizeOne((link, onClickFn) => ({ - registerLink: this.registerLink, - unregisterLink: this.unregisterLink, - scrollTo: this.handleScrollTo, - activeLink: link, - onClick: onClickFn, - }))(activeLink, onClick); + const contextValue = this.getMemoizedContextValue(activeLink, onClick); return ( diff --git a/components/anchor/__tests__/cached-context.test.tsx b/components/anchor/__tests__/cached-context.test.tsx new file mode 100644 index 000000000000..0fa0db6f93b6 --- /dev/null +++ b/components/anchor/__tests__/cached-context.test.tsx @@ -0,0 +1,51 @@ +import React, { memo, useState, useRef, useContext } from 'react'; +import { mount } from 'enzyme'; +import Anchor from '../Anchor'; +import AnchorContext from '../context'; + +// we use'memo' here in order to only render inner component while context changed. +const CacheInner = memo(() => { + const countRef = useRef(0); + countRef.current++; + // subscribe anchor context + useContext(AnchorContext); + return ( +
+ Child Rendering Count: {countRef.current} +
+ ); +}); + +const CacheOuter = () => { + // We use 'useState' here in order to trigger parent component rendering. + const [count, setCount] = useState(1); + const handleClick = () => { + setCount(count + 1); + }; + // During each rendering phase, the cached context value returned from method 'Anchor#getMemoizedContextValue' will take effect. + // So 'CacheInner' component won't rerender. + return ( +
+ + Parent Rendering Count: {count} + + + +
+ ); +}; + +it("Rendering on Anchor without changed AnchorContext won't trigger rendering on child component.", () => { + const wrapper = mount(); + const childCount = wrapper.find('#child_count').text(); + wrapper.find('#parent_btn').at(0).simulate('click'); + expect(wrapper.find('#parent_count').text()).toBe('2'); + // child component won't rerender + expect(wrapper.find('#child_count').text()).toBe(childCount); + wrapper.find('#parent_btn').at(0).simulate('click'); + expect(wrapper.find('#parent_count').text()).toBe('3'); + // child component won't rerender + expect(wrapper.find('#child_count').text()).toBe(childCount); +}); diff --git a/components/locale-provider/__tests__/cached-context.test.tsx b/components/locale-provider/__tests__/cached-context.test.tsx new file mode 100644 index 000000000000..fb51a5e75b3c --- /dev/null +++ b/components/locale-provider/__tests__/cached-context.test.tsx @@ -0,0 +1,53 @@ +import React, { memo, useState, useRef, useContext } from 'react'; +import { mount } from 'enzyme'; +import LocaleProvider, { ANT_MARK } from '..'; +import LocaleContext from '../context'; + +const defaultLocale = { + locale: 'locale', +}; +// we use'memo' here in order to only render inner component while context changed. +const CacheInner = memo(() => { + const countRef = useRef(0); + countRef.current++; + // subscribe locale context + useContext(LocaleContext); + return ( +
+ Child Rendering Count: {countRef.current} +
+ ); +}); + +const CacheOuter = () => { + // We use 'useState' here in order to trigger parent component rendering. + const [count, setCount] = useState(1); + const handleClick = () => { + setCount(count + 1); + }; + // During each rendering phase, the cached context value returned from method 'LocaleProvider#getMemoizedContextValue' will take effect. + // So 'CacheInner' component won't rerender. + return ( +
+ + Parent Rendering Count: {count} + + + +
+ ); +}; + +it("Rendering on LocaleProvider won't trigger rendering on child component.", () => { + const wrapper = mount(); + wrapper.find('#parent_btn').at(0).simulate('click'); + expect(wrapper.find('#parent_count').text()).toBe('2'); + // child component won't rerender + expect(wrapper.find('#child_count').text()).toBe('1'); + wrapper.find('#parent_btn').at(0).simulate('click'); + expect(wrapper.find('#parent_count').text()).toBe('3'); + // child component won't rerender + expect(wrapper.find('#child_count').text()).toBe('1'); +}); diff --git a/components/locale-provider/index.tsx b/components/locale-provider/index.tsx index e7ce697287d1..8c9134c3d391 100644 --- a/components/locale-provider/index.tsx +++ b/components/locale-provider/index.tsx @@ -78,12 +78,14 @@ export default class LocaleProvider extends React.Component ({ + ...localeValue, + exist: true, + })); + render() { const { locale, children } = this.props; - const contextValue = memoizeOne(localeValue => ({ - ...localeValue, - exist: true, - }))(locale); + const contextValue = this.getMemoizedContextValue(locale); return {children}; } }