Skip to content

Динамические компоненты

Наиболее подходящей демонстрацией будет пример с Layout - компонентом, изменяющим раскладку и общий дизайн приложения в зависимости от текущей страницы.

При использовании реактивных подходов есть три основных способа работы с динамическими компонентами:

  1. Вне компонента Router, используя router.activeName
tsx
import { LayoutLogin } from 'layouts/LayoutLogin'
import { LayoutAuthZone } from 'layouts/LayoutAuthZone'

function App() {
  const { router } = useRouter();
  
  const Layout = ['login', 'restore'].includes(router.activeName!) 
    ? LayoutLogin 
    : LayoutAuthZone;
  
  return (
    <Layout>
      <Router router={router} />
    </Layout>
  );
}
tsx
import { LayoutLogin } from 'layouts/LayoutLogin'
import { LayoutAuthZone } from 'layouts/LayoutAuthZone'

function App() {
  const { router } = useRouter();

  const Layout = ['login', 'restore'].includes(router.activeName!)
    ? LayoutLogin
    : LayoutAuthZone;
  
  return (
    <Layout>
      <Router router={router} />
    </Layout>
  );
}
tsx
import { LayoutLogin } from 'layouts/LayoutLogin'
import { LayoutAuthZone } from 'layouts/LayoutAuthZone'

function App() {
  const { router } = useRouter();

  return (
    <Dynamic 
      component={['login', 'restore'].includes(router.activeName!) 
        ? LayoutLogin 
        : LayoutAuthZone
      }
    >
      <Router router={router} />
    </Dynamic>
  );
}
vue
<script lang="ts" setup>
  import { computed } from 'vue';
  import { useRouter } from '../../router';
  
  import LayoutLogin from 'layouts/LayoutLogin.vue'
  import LayoutAuthZone from 'layouts/LayoutAuthZone.vue'

  const { router } = useRouter();

  const Layout = computed(() => 
    ['login', 'restore'].includes(router.activeName!) 
      ? LayoutLogin 
      : LayoutAuthZone
  );
</script>

<template>
  <component :is="Layout">
    <Router :router="router" />
  </component>
</template>
  1. Внутри компонента страницы, реагируя на динамические параметры
ts
createConfigs({
  dashboard: {
    path: '/dashboard/:tab',
    params: {
      tab: (value) => ['table', 'widgets', 'charts'].includes(value)
    },
    query: {
      editMode: (value) => ['0', '1', '2'].includes(value)
    },
    loader: () => import('./pages/dashboard'),
  }

  // other Configs
});
tsx
export default function PageDashboard() {
  const { router } = useContext(RouterContext);

  const pageState = router.state.dashboard!;
  
  return (
    <Layout editMode={pageState.query.editMode || '0'}>
      {pageState.params.tab === 'table' && <Table />}
      {pageState.params.tab === 'widgets' && <Widgets />}
      {pageState.params.tab === 'charts' && <Charts />}
    </Layout>
  );
}
tsx
export default function PageDashboard() {
  const { router } = useContext(RouterContext);

  const pageState = router.state.dashboard!;
  
  return (
    <Layout editMode={pageState.query.editMode || '0'}>
      {pageState.params.tab === 'table' && <Table />}
      {pageState.params.tab === 'widgets' && <Widgets />}
      {pageState.params.tab === 'charts' && <Charts />}
    </Layout>
  );
}
tsx
export default function PageDashboard() {
  const { router } = useContext(RouterContext);
  
  const pageState = () => router.state.dashboard!;

  return (
    <Layout editMode={pageState().query.editMode || '0'}>
      <Switch>
        <Match when={pageState().params.tab === 'table'}><Table /></Match>
        <Match when={pageState().params.tab === 'widgets'}><Widgets /></Match>
        <Match when={pageState().params.tab === 'charts'}><Charts /></Match>
      </Switch>
    </Layout>
  );
}
vue
<script lang="ts" setup>
import { useRouterStore } from './router';

const { router } = useRouterStore();

const pageState = router.state.dashboard!;
</script>

<template>
  <Layout :edit-mode="pageState.query.editMode || '0'">
    <Table v-if="pageState.params.tab === 'table'" />
    <Widgets v-else-if="pageState.params.tab === 'widgets'" />
    <Charts v-else-if="pageState.params.tab === 'charts'" />
  </Layout>
</template>
  1. Установкой одинакового loader для нескольких Config. В этом случае beforeComponentChange не будет вызываться, однако router.activeName будет меняться. Этот пример мог бы выглядеть как предыдущие, но выберем подход с реактивной функцией:
ts
createConfigs({
  dashboard: {
    path: '/dashboard',
    loader: () => import('./pages/dashboard'),
  },
  dashboardEdit: {
    path: '/dashboard/edit',
    loader: () => import('./pages/dashboard'),
  },
  dashboardAggregate: {
    path: '/dashboard/aggregate',
    loader: () => import('./pages/dashboard'),
  }
});
tsx
// with mobx adapter

import { autorun } from 'mobx'

function PageUser() {
  const { router } = useContext(RouterContext);
  
  const [ Layout, setLayout ] = useState();
  
  useEffect(() => autorun(() => {
    if (router.activeName === 'dashboard') {
      setLayout(View)
    } else if (router.activeName === 'dashboardEdit') {
      setLayout(Edit)
    } else if (router.activeName === 'dashboardAggregate') {
      setLayout(Aggregate)
    }
  }), []);
  
  if (!Layout) return null;
  
  return <Layout>Content</Layout>;
}
tsx
// with kr-observable adapter

import { autorun } from 'kr-observable'

function PageUser() {
  const { router } = useContext(RouterContext);
  
  const [ Layout, setLayout ] = useState();
  
  useEffect(() => autorun(() => {
    if (router.activeName === 'dashboard') {
      setLayout(View)
    } else if (router.activeName === 'dashboardEdit') {
      setLayout(Edit)
    } else if (router.activeName === 'dashboardAggregate') {
      setLayout(Aggregate)
    }
  }), []);
  
  if (!Layout) return null;
  
  return <Layout>Content</Layout>;
}
tsx
// with solid adapter

import { createMemo } from 'solid-js'

function PageUser() {
  const { router } = useContext(RouterContext);

  const Layout = createMemo(() => {
    if (router.activeName === 'dashboard') return View;
    if (router.activeName === 'dashboardEdit') return Edit;
    if (router.activeName === 'dashboardAggregate') return Aggregate;
    
    return null;
  });

  return (
    <Show when={Layout()}>
      {(Component) => <Component>Content</Component>}
    </Show>
  );
}
vue
<script lang="ts" setup>
// with vue adapter
  
import { computed } from 'vue';
import { useRouterStore } from './router';

const { router } = useRouterStore();

const Layout = computed(() => {
  if (router.activeName === 'dashboard') return View;
  if (router.activeName === 'dashboardEdit') return Edit;
  if (router.activeName === 'dashboardAggregate') return Aggregate;
  
  return null;
});
</script>

<template>
  <component :is="Layout" v-if="Layout">
    Content
  </component>
</template>

Эти подходы можно комбинировать в зависимости от задачи.

No AI participated in the development. MIT License