20
头图

Some knowledge about the keep-alive function of react is here (below)

This article follows the interior of the previous article, so it starts from the ninth point

Nine, keep the page scroll

For example, table on the page contains 100 of data. If we want to see the 100th piece of data, we need to scroll a lot of distance. In many scenarios, this scroll distance must be reserved. of.

The method used here is actually quite traditional. First, a method for handling scrolling is issued in KeepAliveProvider :

     const handleScroll = useCallback(
        (cacheId, event) => {
            if (catheStates?.[cacheId]) {
                const target = event.target
                const scrolls = catheStates[cacheId].scrolls
                scrolls[target] = target.scrollTop
            }
        },
        [catheStates]
    )

Receive and execute in the Keeper component:

     const { dispatch, mount, handleScroll } = useContext(CacheContext)

    useEffect(() => {
        const onScroll = handleScroll.bind(null, cacheId)
        (divRef?.current as any)?.addEventListener?.('scroll', onScroll, true)
        return (divRef?.current as any)?.addEventListener?.('scroll', onScroll, true)
    }, [handleScroll])

In Keeper, assign the scroll property to the element:

     useEffect(() => {
        const catheState = catheStates[cacheId]
        if (catheState && catheState.doms) {
            const doms = catheState.doms
            doms.forEach((dom: any) => {
              (divRef?.current as any)?.appendChild?.(dom)
            })

        // 新增
        doms.forEach((dom: any) => {
            if (catheState.scrolls[dom]) {
                dom.scrollTop = catheState.scrolls[dom]
            }
        })
        } else {
            mount({
                cacheId,
                reactElement: props.children
            })
        }
    }, [catheStates])

If you don't actively add the method of assigning scroll , the scroll distance will not be saved, because Keeper is new every time.

10. CacheContext inside Keeper sub-component inside KeepAliveProvider

We are rendering the component in KeepAliveProvider , then if a Provider is defined inside the KeepAliveProvider KeepAliveProvider level component, then the ---463bc8b78e1fabbc It is impossible to use Consumer to get this value.

Here is a question, how to change the context of the component in KeepAliveProvider to the context of the component Keeper .

Here is the most direct way to let the user pass in Provider and value value.

  <Keeper 
   cacheId="home" 
   context={{ Provider: Provider, value: value }}>
    <Home />
 </Keeper>

After we get these two values, we directly modify the structure of Keeper in reactElement :

     mount({
        cacheId,
        reactElement: context ? 
          <context.Provider 
             value={context.value}>{props.children}</context.Provider> : 
          props.children
    })

When it is detected that context has a value, it is directly covered with a layer outside props.children , of course there is a multi-layer Provider the nesting problem has not been solved, because gradually Complicated, its usefulness has been declining, and then there is a new one bug .

11. Components that need to pass values

Have you found that all the logic of the above components is directly written in the Keeper tag, and there is no value transfer, but a more common scenario is as follows:

 function Root (){
   const [n, setN] = useState(1)
 return 
   (
    <>
       <button onClick={()=>setN(n+1)}>n+1</button>
        <Keeper>
          <Home n={n} />
        </Keeper>
    </>
 )
}

This n is Keeper the outer layer is passed to the Home component, this way of writing will lead to n but c2242a28 Home changed Home will not respond inside.

This bug I found this way, when I used this plugin in a form-based page in our team's project, table always showed empty, and input The box also cannot enter a value. After testing, it was found that the value actually changed, but it was not displayed on the component dom .

After trying for a long time, I tried it react-activation Unfortunately, it also has the same problem, which actually means that this bug may not be able to solve or it is a problem with the architecture of the plugin itself.

12. Why such a strange bug scene

At that time, this bug tortured me for a day and a half, and the parameters that were finally positioned to the outside world were no longer the parameters of the component itself. The actual rendering position of our component was the first of KeepAliveProvider One layer, while the outer layer of ---ab8545279134efbe1f00cc7fbec1de12 Keeper is still in the inner layer of KeepAliveProvider , which leads to the fact that changes in these values have no effect on the component.

It can be understood as the change of these values, such as the change of n is like the change of window.n , react the component will not respond to this change.

In fact, what we need to do is to change the value passed in from the outer layer, which can drive the style change of the component (gradually enter the pit!).

13. Take out props separately

I borrowed another keep-alive component writing method on the Internet, and changed the Keeper component to a keeper method, this method returns a component, so you can see Receive a props , and set the variable in the range of props :

 const Home = keeper(HomePage, { cacheId: 'home' })


function Root(){
 const [n, setN] = useState(1)
  return (
    <>
     <button onClick={()=>setN(n+1)}>n+1</button>
     <Home n={n}> // 此处可以传值了
    </>
  )
}

The purpose of this is to allow developers to pass in the parameters that can affect the state of the component. For example, the previous one Keeper can have multiple components in it. In this case, it is difficult to control which parameter changes will cause Which components are updated, but it is obvious in the way of components that the changes in the values received by the components props will cause the components to be updated.

The solution I thought of is to create a new propsObj KeepAliveProvider which is used to specifically store the props of each cache component. , is to separate the logic of the participating components. Many logics will monitor the changes of catheStates and execute them, but the changes of props do not need to trigger these.

   const [propsObj, setPropsObj] = useState<any>();
    return (
    <CacheContext.Provider value={{ setPropsObj, propsObj }}>
      {props.children}
      
   //.... 略
   

KeepAliveProvider The rendering inside needs to be changed to a form, reactElement become a component, don't forget to change the name to uppercase.

     // 旧的
    // {reactElement}
    
    // 新的
    {propsObj && 
      <ReactElement {...propsObj[cacheId]}></ReactElement>}

To modify Keeper file, first change the file name to keeper , and change the export method.

  export default function (
   RealComponent: React.FunctionComponent<any>, { cacheId = '' }) {
   
   return function Keeper(props: any) {
 // ... 略

Keeper Inside mount The use of the method is also slightly adjusted:

     mount({
        cacheId,
        ReactElement: RealComponent
    })

The key is here, we have to monitor the changes of props in Keeper d5e4da655c64db65f79f6e6e96e69352---, to update propsObj :

 const { propsObj, setPropsObj } = useContext(CacheContext)

    useEffect(() => {
        setPropsObj({
            ...propsObj,
            [cacheId]: props
        })
    }, [props])

14. Cache invalidation bug

We have modified the form of the plug-in above, and found that the following scenes can be rendered normally. The props of the Home component are passed in from the outside world:

 
const Home = keeper(HomePage, { cacheId: 'home' })

const RootComponent: React.FC = () => {
    return (
        <KeepAliveProvider>
            <Router>
                <Routes>
                    <Route path={'/'} element={<Mid />} />
                </Routes>
            </Router>
        </KeepAliveProvider>
    )
}
function Mid() {
    const [n, setN] = useState(1)
    return (
        <div>
            <button onClick={() => setN(n + 1)}>n+1</button>
            <Home n={n}></Home>
        </div>
    )
}

function HomePage(props: { n: number }) {
    return <div>home {props.n}</div>
}

But at this time, if you switch pages and then return to the home page, home page cache will be invalid.

In fact, because we monitor the changes of props in real time, the next re-rendering will cause props to change, and then the value will be initialized, causing the component to return to its earlier configuration, but .... isn't this a cache failure?

Every time the component props is reset, the relevant data of the component will be reset, try to change the home component as follows:

 function HomePage(props: { n: number }) {
    const [x, setX] = useState(1)
    return (
        <div>
            <button onClick={() => setX(x + 1)}>x + 1</button>
            <div>home {props.n}</div>
            <div>home: x {x}</div>
        </div>
    )
}

The above writing method will cause each activation of the home component, only the value of ---3ce5ed65b7c87550a7b445994519b0ae x will be retained, and the value of n will be the same as the incoming one.

This change may lead to bug , assuming that only n > 2 can make x > 3 , at this time we let n = 5 x = 4 , then switch to another page and come back, it becomes n = 1, x=4 , which violates our initial constraints, and so on. In a real and complex development environment, this phenomenon will cause All kinds of weird questions.

15. The cost of cognition

The above scenario can be controlled by the developer himself, ideally keep-alive the plug-in is only used to deal with components that do not require external parameters and will not be affected by changes in external parameters, but this starts to be troublesome .

This kind of problem leads to an increase in the learning cost and the cost of using the plug-in for developers, and if a component does not need to pass parameters, we wrap it with keep-alive , and then need to pass parameters later , the cost of change is troublesome to think about.

The official website of the existing components on the Internet (April 10, 2022 17:16:22) basically does not seriously tell users about the relevant issues, and often focuses on introducing "how to use" and explaining their own advantages. Cause the user to be tortured inexplicably bug .

There is also a problem with the method of passing Provider , it is necessary to pass the code Provider which may not be the code of this page.

If you want to solve keep-alive related problems, you can change the way of thinking. It is best to support a wave in the source code of react . For example, you can specify that some components will not be destroyed. In fact, we can pay attention to it. The follow-up version of react18 , now this time period react18 has released the official version.

16. How to upgrade to react18

Method 1: create-react-app Create a new project

At this stage, you can create a react18 project directly by using the following command:

 npx create-react-app my_react

image.png

The following usage --template specifying a template doesn't work, because the template code hasn't been updated:

 npx create-react-app my_react --template typescript

Here you can view the templates for all react projects that can be specified by the create-react-app project .

Method 2: Retrofit of old projects

First, directly change the version numbers of react and react-dom in the dependencies to "^18.0.0" .

Both methods need to modify index.js

There will be an error message when starting the project:
image.png

Legacy index.js
image.png

New version index.js

image.png

Not much else has changed.

Seventeen, the usage of react18 Offscreen component

Offscreen allow React keep this state by hiding the component instead of unloading it, React will also call the same lifecycle hook as when it is unloaded, but Preserve the status of React component and DOM element.

React Activatio n also recommend that you pay attention to this attribute:

image.png

Offscreen What is the official statement, you can see the translation in this article: React v18.0 New Features Official Documentation [Chinese and English

image.png

Test case for Offscreen :

image.png

Unfortunately, the Offscreen component has not been launched in the current version, and it is still in an unstable stage, but we can preview its usage through the test cases in react18 :

image.png

It is still not clear from the above writing method Offscreen how to use it, I only know that it may appear in the form of a component, and a mode attribute needs to be passed in. More usage is expected to be officially launched as soon as possible.

end

Let us look forward to react18 to solve this problem keep-alive this is the case this time, I hope to make progress with you.


lulu_up
5.7k 声望6.9k 粉丝

自信自律, 终身学习, 创业者