Complete the multi-tabs layout of Umi state preservation in Ant Design Pro

reason


For more than four years in the Umi community, many exchanges were conducted in the chat window, and there was not much valuable content left. Although I tried my best to organize some of the content and put it in the FAQ of the official website, there are still a lot of content. Forgotten, so I always wanted to write such a series. Just in the Umi 4 development booklet, the netizen laughed and asked me if the new version of Umi will support multi-tab? Looking at the recent discussions in the Umi team, there is no discussion of this requirement, so I replied that it should not be, but I will do it.

In fact, the need for multi-tab pages has been around for a long time, and it was discussed in pro issues #200.


Off topic, as of this writing, there are now 9919 pro issues.

If you are interested, you can search for "tabs" on the issues of antd pro, and you can see a lot of discussions. In fact, when I was in umi@3, I implemented a version, because it is a bit used with the official built-in layout plugin. Because there are fewer people using it, I mainly have more usage requirements on the mobile terminal h5, so the multi-tabs implementation in umi@3 is a modified version copied from keepalive.
There are several historical factors that make the umi@3 multi-tabs plug-in more difficult to use than expected. One of the main points is that the implementation of the keepalive plug-in in umi@3 itself is not excellent enough.
But in umi@4, after react-route was updated to version 6, Chen Junyu rewrote the keepalive demo for me. I modified it as a new version of the keepalive plug-in, both from the clarity of the code and the idealization of the solution. to a degree that makes me very satisfied.
Therefore, for the new version of the tabs plug-in, it is also what I will complete within my original plan. By modifying a bug in the alita framework today, I have fulfilled this requirement by the way.

Effect


Let’s go directly to the effect first. This is the effect achieved in the pro. By the way, both umi and pro will release new versions in the near future. You can continue to pay attention to the official website information.

ideas


1. In order to avoid some non-technical problems, the new version of the tabs plugin and the keepalive plugin are combined for implementation. Because the keepalive plugin is used a lot internally, we can use the scene to verify the robustness of the entire solution.
2. In terms of implementation, only the pages marked as "requires state retention" will be added to the multi-tabs tag, which can solve the requirement of "only some pages need multi-tabs and other pages do not".
3. On the basis of the state maintenance scheme, change and increase the code as little as possible to achieve the desired requirements.
4. For the keepalive plugin, add a new configuration to enable multi-tabs layout
accomplish
Because the Tabs component of antd can be done by itself, the page data is kept unchanged when the Tabs are switched. But basically when we use this component, it belongs to the "component level" usage, that is, in the same page, do multiple tabs switching. So we need to elevate him to the "page level".
The implementation is also very simple, that is, do not put the page in the TabPane of Tabs.
export function App() {
return <>


{panels.map(() => (

))}


{children}

}

To associate a page with Tabs, just use the route location.pathname of the current page and all the current pages that are maintained by the state as the activeKey of the Tabs and the panels data of the rendered TabPane, and use the change activeKey at the component level directly with the page jump method replace.
export function App() {
return <>

{
navigate(key);
}} activeKey={location.pathname} >
{Object.entries(keepElements.current).map(([pathname]: any) => (

))}


{children}

}

Associate the closing behavior of Tabs with the clearing cache method in state retention dropByCacheKey(targetKey); to maintain a piece of data together. It can be handled in the onEdit event in Tabs. It should be noted that if the current page is closed, it will automatically jump to the previous page. If there is only the last page left, the user will be promoted, and "a window must be reserved". The implementation is relatively simple, if you are interested, you can directly look at the alita repository.
It should be noted that we clear the cache dropByCacheKey to modify the React.useRef object. Modifying it directly will not cause the page to be redrawn. This will cause our closing Tabs event to eliminate the cache, but it needs to be entered the next time the page is entered. Only then will the Tab be removed. This obviously does not meet our needs. Today, I got stuck for a long time because of this problem, and finally I only thought of a way to answer the problem, but the writing method is very "ugly". If anyone else has a better solution, PR is welcome.
The above problem has been fixed by Brother Chen.
Put a useless useState in the multi-tabs layout. When keepElements.current is modified, go to setState synchronously. Although you don't use the state, the page will still perform a redraw when the state is modified, which can update our page. .
The key code finally realized is less than 50 lines, and the logic is very clear. It is a very friendly source code for beginners. You can understand it after reading it a few times.
import React, { useState } from 'react';
import { useOutlet, useLocation, matchPath, useNavigate } from 'react-router-dom'
import { Tabs, message } from 'antd';
import { getPluginManager } from '../core/plugin';
export const KeepAliveContext = React.createContext({});

const { TabPane } = Tabs;

export function useKeepOutlets() {
const location = useLocation();
const element = useOutlet();
const navigate = useNavigate();
const [panel, setPanel] = useState();
const runtime = getPluginManager().applyPlugins({ key: 'tabsLayout', type: 'modify', initialValue: {} });
const { local } = runtime;
const { keepElements, keepalive, dropByCacheKey } = React.useContext(KeepAliveContext);
const isKeep = isKeepPath(keepalive, location.pathname);
if (isKeep) {
keepElements.current[location.pathname] = element;
}
return <>

{
Object.entries(keepElements.current).map(([pathname, children]: any) => (

))
}


}

Related Articles

Explore More Special Offers

  1. Short Message Service(SMS) & Mail Service

    50,000 email package starts as low as USD 1.99, 120 short messages start at only USD 1.00