为了解决这个问题,多次与平台交涉,他们给出的解决方案非常的操蛋,路由跳转返回唯一不变的是history.state.key,他们让我把所有的状态根据key存到全局状态中,这。。。
那不是随便一个状态都必须存全局,我表示无法接收,并且次架构已经定下来,也不可能大改了,因此我只能求助于keep-alive,便上网搜起来了React下keep-alive的实现方式。
搜了一圈发现,网上大部分的文章要么直接贴keep-alive的代码,没有任何讲解,要么全程装逼,然后结尾甩个自己写的库给你,定制性极差,非常不友好。于是我就想搞懂原理后,写一个通俗易懂的文章,只讲解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>三. 简单实现
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'))效果如下