ant-design/29c55c4d-async.9a07827d.js
2024-11-20 18:30:54 +00:00

1 line
9.7 KiB
JavaScript

(("undefined"!=typeof globalThis?globalThis:self).makoChunk_antd=("undefined"!=typeof globalThis?globalThis:self).makoChunk_antd||[]).push([["29c55c4d"],{"29c55c4d":function(e,a,t){"use strict";t.d(a,"__esModule",{value:!0}),t.d(a,"texts",{enumerable:!0,get:function(){return n;}}),t("adeae428");let n=[{value:"For heavy components, some bug fixes or new features can easily destroy the original performance optimization inadvertently over time. Recently, we are refactoring the Table to troubleshoot and restore the performance loss caused by some historical updates. Here, we introduce some common troubleshooting method and frequently meet problems.",paraId:0},{value:"Before that, we recommend you to read the official ",paraId:1},{value:"Perf tool",paraId:1},{value:" to choose what you need.",paraId:1},{value:"In most cases, invalid rendering is not as dramatic as an un-optimized loop. However, in some scenarios such as large forms, tables, and lists, due to the large number of sub components, the performance impact of invalid rendering overlays is also terrible.",paraId:2,tocIndex:0},{value:"For example, in antd v4, in order to improve Table hover highlighting experience of ",paraId:3,tocIndex:0},{value:"rowSpan",paraId:3,tocIndex:0},{value:", we added an event listener for ",paraId:3,tocIndex:0},{value:"tr",paraId:3,tocIndex:0},{value:", and added an additional ",paraId:3,tocIndex:0},{value:"className",paraId:3,tocIndex:0},{value:" for the selected row in ",paraId:3,tocIndex:0},{value:"td",paraId:3,tocIndex:0},{value:" to support multiple row highlighting capability. However, because ",paraId:3,tocIndex:0},{value:"td",paraId:3,tocIndex:0},{value:" consumes ",paraId:3,tocIndex:0},{value:"hoverStartRow",paraId:3,tocIndex:0},{value:" and ",paraId:3,tocIndex:0},{value:"hoverEndRow",paraId:3,tocIndex:0},{value:" data in the context, non-related rows will ",paraId:3,tocIndex:0},{value:"re-render",paraId:3,tocIndex:0},{value:" due to changes of ",paraId:3,tocIndex:0},{value:"hoverStartRow",paraId:3,tocIndex:0},{value:" and ",paraId:3,tocIndex:0},{value:"hoverEndRow",paraId:3,tocIndex:0},{value:".",paraId:3,tocIndex:0},{value:"Problems like this are repeated in heavy components, so we need some helper way to determine the number of renders. In the latest ",paraId:4,tocIndex:0},{value:"rc-table",paraId:4,tocIndex:0},{value:", we encapsulate a ",paraId:4,tocIndex:0},{value:"useRenderTimes",paraId:4,tocIndex:0},{value:" method. It will mark the monitored rendering times on React Dev Tools through React's ",paraId:4,tocIndex:0},{value:"useDebugValue",paraId:4,tocIndex:0},{value:" in development mode:",paraId:4,tocIndex:0},{value:"// Sample Code, please view real world code if needed\nimport React from 'react';\n\nfunction useRenderTimes<T>(props: T) {\n // Render times\n const timesRef = React.useRef(0);\n timesRef.current += 1;\n\n // Cache for prev props\n const cacheProps = React.useRef(props);\n const changedPropKeys = getDiff(props, cacheProps.current); // Some compare logic\n\n React.useDebugValue(timesRef.current);\n React.useDebugValue(changedPropKeys);\n\n cacheProps.current = props;\n}\n\nexport default process.env.NODE_ENV !== 'production' ? useRenderTimes : () => {};\n",paraId:5,tocIndex:0},{value:"Generally on the root node of the component, we will create a Context based on ",paraId:6,tocIndex:2},{value:"props",paraId:6,tocIndex:2},{value:" and ",paraId:6,tocIndex:2},{value:"state",paraId:6,tocIndex:2},{value:" to pass the aggregated data down. But in some cases, the actual content of the Context may not change and trigger the re-render of the child component:",paraId:6,tocIndex:2},{value:"// pseudocode\nconst MyContext = React.createContext<{ prop1: string; prop2: string }>();\n\nconst Child = React.memo(() => {\n const { prop1 } = React.useContext(MyContext);\n return <>{prop1}</>;\n});\n\nconst Root = ({ prop1, prop2 }) => {\n const [count, setCount] = React.useState(0);\n\n // Some logic to trigger rerender\n React.useEffect(() => {\n setCount(1);\n }, []);\n\n return (\n <MyContext.Provider value={{ prop1, prop2 }}>\n <Child />\n </MyContext.Provider>\n );\n};\n",paraId:7,tocIndex:2},{value:"In the example, although ",paraId:8,tocIndex:2},{value:"prop1",paraId:8,tocIndex:2},{value:" and ",paraId:8,tocIndex:2},{value:"prop2",paraId:8,tocIndex:2},{value:" have not changed, it is obvious that ",paraId:8,tocIndex:2},{value:"value",paraId:8,tocIndex:2},{value:" in MyContext is a new object, causing the child component to re-render even if ",paraId:8,tocIndex:2},{value:"prop1",paraId:8,tocIndex:2},{value:" has not changed. So we need to Memo the Context ",paraId:8,tocIndex:2},{value:"value",paraId:8,tocIndex:2},{value:":",paraId:8,tocIndex:2},{value:"// pseudocode\nconst context = React.useMemo(() => ({ prop1, prop2 }), [prop1, prop2]);\n\nreturn (\n <MyContext.Provider value={context}>\n <Child />\n </MyContext.Provider>\n);\n",paraId:9,tocIndex:2},{value:"Note: You can configure eslint ",paraId:10,tocIndex:2},{value:"rules",paraId:10,tocIndex:2},{value:" to avoid this case.",paraId:10,tocIndex:2},{value:"Also, refer to the example above. If we put both ",paraId:11,tocIndex:3},{value:"prop1",paraId:11,tocIndex:3},{value:" and ",paraId:11,tocIndex:3},{value:"prop2",paraId:11,tocIndex:3},{value:" in the Context, then even if ",paraId:11,tocIndex:3},{value:"prop1",paraId:11,tocIndex:3},{value:" does not change, ",paraId:11,tocIndex:3},{value:"prop2",paraId:11,tocIndex:3},{value:" changes will cause the child component to re-render. Therefore, we can split the Context into several according to the function, thereby reducing the scope of influence:",paraId:11,tocIndex:3},{value:"// pseudocode\nconst MyContext1 = React.createContext<{ prop1: string }>();\nconst MyContext2 = React.createContext<{ prop2: string }>();\n\n// Child\nconst { prop1 } = React.useContext(MyContext1);\n\n// Root\n<MyContext1.Provider value={context1}>\n <MyContext2.Provider value={context2}>\n <Child />\n </MyContext2.Provider>\n</MyContext1.Provider>;\n",paraId:12,tocIndex:3},{value:"In ",paraId:13,tocIndex:3},{value:"rc-table",paraId:13,tocIndex:3},{value:", we split it into multiple to optimize rendering performance:",paraId:13,tocIndex:3},{value:"BodyContext",paraId:14,tocIndex:3},{value:"ExpandedRowContext",paraId:14,tocIndex:3},{value:"HoverContext",paraId:14,tocIndex:3},{value:"PerfContext",paraId:14,tocIndex:3},{value:"ResizeContext",paraId:14,tocIndex:3},{value:"StickyContext",paraId:14,tocIndex:3},{value:"TableContext",paraId:14,tocIndex:3},{value:"If you have used Redux, then you may be familiar with ",paraId:15,tocIndex:4},{value:"useSelector",paraId:15,tocIndex:4},{value:", which only rerender when the data that needs to be consumed changes. In React, there is also a related RFC(",paraId:15,tocIndex:4},{value:"#118",paraId:15,tocIndex:4},{value:")(",paraId:15,tocIndex:4},{value:"#119",paraId:15,tocIndex:4},{value:") about ",paraId:15,tocIndex:4},{value:"useContextSelector",paraId:15,tocIndex:4},{value:", which will also be implemented in React 18 in the future:",paraId:15,tocIndex:4},{value:"Before the API is officially launched, there are many third-party libraries implement (of course, you can also use redux directly). It is no longer necessary to consider the problem of function splitting Context through ",paraId:16,tocIndex:4},{value:"useContextSelector",paraId:16,tocIndex:4},{value:", which also reduces the mental burden of developers:",paraId:16,tocIndex:4},{value:"// pseudocode\nconst Child = React.memo(() => {\n const prop1 = useContextSelector(MyContext, (context) => context.prop1);\n return <>{prop1}</>;\n});\n",paraId:17,tocIndex:4},{value:"After optimizing in various ways, we still have to face a problem. If some rendering needs to pass through the external render method, and it happens that the method uses a closure. Then ",paraId:18,tocIndex:5},{value:"React.memo",paraId:18,tocIndex:5},{value:" is unaware:",paraId:18,tocIndex:5},{value:"// pseudocode\nimport React from 'react';\n\nconst MyComponent = React.memo(({ valueRender }: { valueRender: () => React.ReactElement }) =>\n valueRender(),\n);\n\nconst App = () => {\n const countRef = React.useRef(0);\n const [, forceUpdate] = React.useState({});\n\n React.useEffect(() => {\n countRef.current += 1;\n forceUpdate({});\n }, []);\n\n // In real world, class component often meet this by `this.state`\n const valueRender = React.useCallback(() => countRef.current, []);\n\n return <MyComponent valueRender={valueRender} />;\n};\n",paraId:19,tocIndex:5},{value:"Due to the existence of closures, we cannot determine whether the final DOM has changed before calling the ",paraId:20,tocIndex:5},{value:"render",paraId:20,tocIndex:5},{value:" method, which is why we optimized the Table through memo in the early days of antd v4 and removed some of it over time (Actually, Table still has some scenarios where this problem needs to be solved).",paraId:20,tocIndex:5},{value:"Considering that Table provides ",paraId:21,tocIndex:5},{value:"shouldCellUpdate",paraId:21,tocIndex:5},{value:" method, we plan to adjust Table rendering logic in the future. When the Parent node renders, the Table will be completely re-rendered, and when the Table is updated internally (such as horizontal scrolling position synchronization), it will hit the cache and skip.",paraId:21,tocIndex:5},{value:"antd Table optimization is still in progress, and we will continue to pay attention to new features of React and new ideas from the community. If you have any ideas, welcome to discuss on Github. In addition, for the suggestion of self-developed components, we recommend that after each optimization, a corresponding test case should be created, and the source issue should be noted for future retrospection. That's all. Thank you for reading.",paraId:22,tocIndex:6}];}}]);