Freeze

Freeze 是组件缓存组件(替代 3.x 的 KeepAlive)。当组件切换时,Freeze 会将旧组件冻结(暂停响应式更新)并缓存起来,而不是销毁它。下次再切换回来时,直接从缓存中恢复,保留之前的操作状态。

基本用法

tsx
import { Freeze, shallowRef } from 'vitarx'

// 定义两个组件
function ComponentA() {
  return <div>组件 A</div>
}

function ComponentB() {
  return <div>组件 B</div>
}

function App() {
  const current = shallowRef(ComponentA)

  return (
    <div>
      <button
        onClick={() => {
          current.value = ComponentA
        }}
      >
        显示 A
      </button>
      <button
        onClick={() => {
          current.value = ComponentB
        }}
      >
        显示 B
      </button>
      <Freeze is={current} />
    </div>
  )
}

切换到组件 B 时,组件 A 不会被销毁,而是被冻结并缓存。再切回组件 A 时,直接从缓存恢复,之前的状态(如表单输入、滚动位置等)都会保留。

属性

属性类型必填说明
isComponent | null | undefined | false动态组件类型,传 null/undefined/false 则不渲染
keyunknown唯一标识,配合 is 使用,区分同一组件的不同实例
propsAnyProps | null | undefined传递给组件的属性对象
includeComponent[]需要缓存的组件列表,只缓存列表中的组件
excludeComponent[]不需要缓存的组件列表,优先级高于 include
maxnumber最大缓存数量,超出时采用 LRU 策略淘汰(默认 0 不限制)

props — 传递属性

通过 props 属性可以向当前渲染的组件传递数据:

tsx
import { Freeze, shallowRef, reactive } from 'vitarx'

function DetailPage({ id }: { id: number }) {
  return <div>详情页 ID: {id}</div>
}

function App() {
  const current = shallowRef(DetailPage)
  const pageProps = reactive({ id: 1 })

  return (
    <div>
      <button
        onClick={() => {
          pageProps.id = 1
        }}
      >
        ID 1
      </button>
      <button
        onClick={() => {
          pageProps.id = 2
        }}
      >
        ID 2
      </button>
      <Freeze is={current} props={pageProps} />
    </div>
  )
}

include / exclude — 控制缓存范围

默认情况下,Freeze 会缓存所有切换过的组件。你可以通过 includeexclude 来精确控制哪些组件需要缓存:

tsx
import { Freeze, shallowRef } from 'vitarx'

function Home() {
  return <div>首页</div>
}
function Settings() {
  return <div>设置</div>
}
function Profile() {
  return <div>个人中心</div>
}

function App() {
  const current = shallowRef(Home)

  return (
    // 只缓存 Home 和 Settings,不缓存 Profile
    <Freeze is={current} include={[Home, Settings]} />
  )
}

exclude 的优先级高于 include,即如果一个组件同时出现在两个列表中,它不会被缓存:

tsx
// 缓存除 Settings 以外的所有组件
<Freeze is={current} exclude={[Settings]} />

max — 限制缓存数量

当缓存组件过多时,会占用大量内存。通过 max 可以限制最大缓存数量,超出时采用 LRU(最近最少使用)策略淘汰最久未使用的组件:

tsx
<Freeze is={current} max={5} />

key — 区分同一组件的不同实例

如果你需要让同一个组件根据不同参数缓存不同的实例,可以使用 key 属性:

tsx
import { Freeze, shallowRef, ref } from 'vitarx'

function DetailPage({ id }: { id: number }) {
  return <div>详情页 ID: {id}</div>
}

function App() {
  const currentId = ref(1)
  const current = shallowRef(DetailPage)

  return (
    <div>
      <button
        onClick={() => {
          currentId.value = 1
        }}
      >
        详情 1
      </button>
      <button
        onClick={() => {
          currentId.value = 2
        }}
      >
        详情 2
      </button>
      {/* key 不同,会缓存两个独立的实例 */}
      <Freeze is={current} key={currentId} props={{ id: currentId }} />
    </div>
  )
}

完整示例

下面是一个模拟 Tab 页签切换的完整示例,展示 Freeze 如何保留各 Tab 页的状态:

tsx
import { Freeze, shallowRef, reactive } from 'vitarx'

/** 表单组件 — 切换后保留输入内容 */
function FormTab() {
  const form = reactive({ name: '', email: '' })

  return (
    <div>
      <h3>表单</h3>
      <div>
        <label>姓名:</label>
        <input
          value={form.name}
          onInput={(e) => {
            form.name = (e.target as HTMLInputElement).value
          }}
        />
      </div>
      <div>
        <label>邮箱:</label>
        <input
          value={form.email}
          onInput={(e) => {
            form.email = (e.target as HTMLInputElement).value
          }}
        />
      </div>
    </div>
  )
}

/** 计数器组件 — 切换后保留计数 */
function CounterTab() {
  const count = reactive({ value: 0 })

  return (
    <div>
      <h3>计数器</h3>
      <p>当前计数:{count.value}</p>
      <button
        onClick={() => {
          count.value++
        }}
      >
        +1
      </button>
      <button
        onClick={() => {
          count.value--
        }}
      >
        -1
      </button>
    </div>
  )
}

/** 设置组件 — 不缓存 */
function SettingsTab() {
  return (
    <div>
      <h3>设置</h3>
      <p>这是设置页面,切换后不会被缓存</p>
    </div>
  )
}

function App() {
  const tabs = [
    { label: '表单', component: FormTab },
    { label: '计数器', component: CounterTab },
    { label: '设置', component: SettingsTab }
  ]
  const current = shallowRef(FormTab)

  return (
    <div>
      <div style="margin-bottom: 16px;">
        {tabs.map((tab) => (
          <button
            key={tab.label}
            onClick={() => {
              current.value = tab.component
            }}
            style={{
              marginRight: '8px',
              fontWeight: current.value === tab.component ? 'bold' : 'normal'
            }}
          >
            {tab.label}
          </button>
        ))}
      </div>
      {/* 只缓存 FormTab 和 CounterTab,不缓存 SettingsTab */}
      <Freeze is={current} include={[FormTab, CounterTab]} max={5} />
    </div>
  )
}

试试在"表单"页输入内容,切换到"计数器"页点击几次,再切回"表单"页——你会发现之前的输入内容还在!

下一步