• React中keep-alive的作用和原理
  • 发布于 2个月前
  • 330 热度
    0 评论
一. 为啥要使用keep-alive
其实本人对在React中使用keep-alive非常的鄙夷,觉得这又是一个为了面试题而面试题的需求,因为这个东西如果处理的不好的话,特别容易存在内存泄露问题。但是最近真的遇到了这个使用场景,起因是我需要在别人微前端架构下开发移动端模块,这个架构的路由没有使用任何的第三方库,是他们自己开发的,在跳转页面的时候,就会出现如下情况。A组件跳转到B组件的时候,会把A组件中所有的状态销毁,导致再回到A组件的时候,此时A组件已经不是原来的A组件了。

为了解决这个问题,多次与平台交涉,他们给出的解决方案非常的操蛋,路由跳转返回唯一不变的是history.state.key,他们让我把所有的状态根据key存到全局状态中,这。。。


那不是随便一个状态都必须存全局,我表示无法接收,并且次架构已经定下来,也不可能大改了,因此我只能求助于keep-alive,便上网搜起来了React下keep-alive的实现方式。


搜了一圈发现,网上大部分的文章要么直接贴keep-alive的代码,没有任何讲解,要么全程装逼,然后结尾甩个自己写的库给你,定制性极差,非常不友好。于是我就想搞懂原理后,写一个通俗易懂的文章,只讲解keep-alive的实现原理,没有任何的多余代码,任何人都可在其基础上自行定制。


二. keep-alive原理
React下keep-alive的实现原理非常的简单,keep-alive的核心原理图如下

keep-alive的实现原理非常的取巧,核心原理是将要渲染的组件放到不会被卸载的地方渲染,然后把渲染好的dom移动到keep-alive组件要显示的位置下,伪代码如下:
 <div class="keep-alive-container">
   <div>渲染好的dom</div>
 </div>
 <div class="keep-alive"></div>
 
 <script>
   // 堆代码 duidaima.com
   // 永远不会被卸载的组件
   const keepAliveContainer = document.querySelector('.keep-alive-container')
   // 要持久化的组件在要渲染在页面上的位置
   const keepAlive = document.querySelector('.keep-alive')
   // 移动渲染好的dom
   Array
     .from(keepAliveContainer.childNodes)
     .forEach(node => {
       keepAlive.appendChild(node)
     })
 </script>
三. 简单实现
以下是keep-alive的最简单实现,性能极差,可优化项极多,但是简单明了的阐述了React实现keep-alive的基本代码
 import {
   useRef,
   useState,
   useEffect,
   useContext,
   createContext,
   createElement
 } from 'react'
 import ReactDom from 'react-dom'
 
 const KeepAliveContext = createContext()
 
 /** 用于创建keepAlive组件的方法 */
 function createKeepAliveElement(id, element) {
 
   return (props) => {
 
     const { elements, onPushKeepAlive } = useContext(KeepAliveContext)
     const keepAliveRef = useRef()
 
     // 每次props发生变化,重新渲染组件
     useEffect(() => {
       onPushKeepAlive(
         id,
         createElement(element, props),
       )
     }, [props])
 
     // 监听组件状态变化
     useEffect(() => {
       // 如果nodes存在,移动到当前组件的根节点下
       if (elements[id]?.nodes) {
         const keepAliveDom = keepAliveRef.current
         elements[id].nodes.forEach(node=>{
           keepAliveDom.appendChild(node)
         })
       }
     }, [elements])
 
     return (
       <div
         ref={keepAliveRef}
         style={{ display: 'contents' }}
       />
     )
   }
 
 }
 
 function KeepAliveProvider({ children }) {
 
   /**
    * 存储需要借鸡生蛋的组件,存储结构如下
    * {
    *   [唯一标识]: {
    *      id:      唯一标识 
    *      element: 存储jsx,
    *      nodes:   存储真实的dom
    *   }
    * }
    */
   const [elements, setElement] = useState({})
 
   // 通过次方法添加keepAlive组件
   const onPushKeepAlive = (id, element) => {
     const newElements = { ...elements }
     newElements[id] = { ...elements[id], id, element,  }
     setElement(newElements)
   }
 
   return (
     <KeepAliveContext.Provider value={{ elements, onPushKeepAlive }}>
       {children}
       {/* 借鸡生蛋的地方 */}
       {
         Object.values(elements).map(({ id, element }) => (
           <div
             key={id}
             style={{ display: 'none' }}
             ref={(dom) => {
               // 把渲染好的dom存到状态里
               if (dom && dom.children.length) {
                 const newElements = { ...elements }
                 newElements[id].nodes = Array.from(dom.children)
                 setElement(newElements)
               }
             }}
           >
             {element}
           </div>
         ))
       }
     </KeepAliveContext.Provider>
   )
 
 }
 
 // 创建一个可持久化的form组件
 const Form = createKeepAliveElement('form', () => {
 
   const [value, setValue] = useState('')
 
   return (
     <input
       value={value}
       onChange={e => setValue(e.target.value)}
     />
   )
 
 })
 
 // 创建一个可持久化的计数组件
 const Count = createKeepAliveElement('count', () => {
 
   const [count, setCount] = useState(0)
 
   return (
     <button onClick={() => setCount(count + 1)}>
       {count}
     </button>
   )
 
 })
 
 function App() {
 
   const [showForm, setShowForm] = useState(true)
 
   return (
     <KeepAliveProvider>
       <button onClick={() => setShowForm(!showForm)}>切换组件</button>
       {/* 每次切换的时候都销毁前组件 */}
       {showForm ? <Form /> : <Count />}
     </KeepAliveProvider>
   )
 }
 
 ReactDom.render(<App />, document.querySelector('#root'))
效果如下

用户评论