0.6.1 • Published 2 years ago

@karrotframe/navigator-plugin v0.6.1

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
2 years ago

@karrotframe/navigator-plugin

npm.io npm.io npm.io

English

유연하고 확장가능한 @karrotframe/navigator 을 위한 plugin

  • 🧩 @karrotframe/navigator 라이프사이클 hook 을 이용한 이벤트 제어
  • 📭 라이프사이클 hook 에 미들웨어 지원으로 단계별 이벤트 제어
  • 🖇️ plugin 내부 상태 관리를 통한 다양한 사용자 시나리오 지원


설치

$ yarn add @karrotframe/navigator-plugin

간단한 plugin 만들기

plugin 정의

plugins/index.ts

import type {
  PluginType,
  NavigatorPluginType,
} from '@karrotframe/navigator-plugin'

const pluginName = 'SimplePlugin'

export const useSimplePlugin = (): PluginType & {
  pluginName: string
} => {
  return pluginName
}

export const simplePlugin: NavigatorPluginType = {
  name: pluginName,
  executor: useSimplePlugin,
}

생성한 plugin 적용하기

App.tsx

import { simplePlugin } from './plugins'

const App: React.FC = () => {
  return (
    <Navigator plugins={[simplePlugin]}>
      <Screen path="/" component={Main} />
    </Navigator>
  )
}

Main.tsx

import { useSimplePlugin } from './plugins'

const Main: React.FC = () => {
  const { pluginName } = useSimplePlugin()
  return (
    <div>
      <span>Main</span>
      <span>{pluginName}</span>
    </div>
  )
}

Navigator 의 라이프사이클 hook 을 이용한 plugin 만들기

plugin 정의

plugins/index.ts

import type {
  NavigatorPluginType,
  PluginType,
} from '@karrotframe/navigator-plugin'

export const loggerPlugin: NavigatorPluginType = {
  name: 'loggerPlugin',
  executor: (): PluginType => ({
    lifeCycleHooks: {
      onPoppedWithData: async ({ from, data }) => {
        console.log('from: ', from)
        console.log('data: ', data)
      },
    },
  }),
}

onPoppedWithData hook 은 pop().send(data) 를 호출할 때 불리는 hook 이에요.

인자로 pop() 을 호출하는 screen path 인 from 값과, pop().send(data) 에서 전달한 인자인 data 값을 받아요.

lifecycle hook 은 onPoppedWithData 이외에도 여러가지가 존재해요.


plugin 에서 state 를 관리하면서 해당 state 를 어플리케이션에서 사용하는 방법

plugin 정의

plugins/index.ts

import React, { createContext, useContext, useState, useMemo } from 'react'
import type {
  NavigatorPluginType,
  PluginType,
} from '@karrotframe/navigator-plugin'

// 전역 state 를 관리하기 위한 context 생성
export const ContextDataPlugin = createContext<{
  data: any
  setData: (data: any) => void
}>(null as any)

// 전역 state 를 전달하기 위한 provider 생성:
export const DataPluginProvider: React.FC = (props) => {
  const [data, setData] = useState<any>(null)
  return (
    <ContextDataPlugin.Provider value={{ data, setData }}>
      {props.children}
    </ContextDataPlugin.Provider>
  )
}

export const useDataPlugin = (): PluginType & {
  dataFromNextPage: (params: { from: string }) => any
} => {
  const context = useContext(ContextDataPlugin)

  return useMemo(() => {
    return {
      lifeCycleHooks: {
        onPoppedWithData: async ({ from, data }) => {
          // lifecycle hook 에서 plugin 의 state 를 제어해요
          context.setData({ [from]: data })
        },
      },
      // dataFromNextPage 을 컴포넌트에서 호출해서 컴포넌트가 plugin 의 state 에 접근 가능하도록 해요
      dataFromNextPage: ({ from }: { from: string }) => context?.data?.[from],
    }
  }, [context])
}

export const dataPlugin: NavigatorPluginType = {
  name: 'dataPlugin',
  provider: DataPluginProvider,
  executor: useDataPlugin,
}

plugin 내부에서 state 를 제어하고, 이 state 를 컴포넌트에서 접근할 수 있도록 context api 를 사용해요.

예시에서 생성한 provider 는, 플러그인을 Navigator 에 적용할 때 Navigator 를 감싸는 provider 로 작동해요.

이러한 방법으로 plugin 의 state 를 Navigator 의 전역에서 사용이 가능해요.

생성한 플러그인 적용하기

App.tsx

import { dataPlugin } from './plugins'

const App: React.FC = () => {
  return (
    <Navigator plugins={[dataPlugin]}>
      <Screen path="/" component={Main} />
      <Screen path="/other" component={Other} />
    </Navigator>
  )
}

Main.tsx

import { useDataPlugin } from './plugins'

const Main: React.FC = () => {
  const { dataFromNextPage } = useDataPlugin()
  const result = useMemo(
    () => dataFromNextPage({ from: '/other' }),
    [dataFromNextPage]
  )

  return (
    <div>
      <span>Main</span>
      <span>{result}</span>
    </div>
  )
}

lifecycle hook 에 미들웨어 적용하기

plugin 정의

import type {
  BeforePushType,
  NavigatorPluginType,
  PluginType,
} from '@karrotframe/navigator-plugin'

// hook 에 적용할 미들웨어를 하나로 묶어주는 역할을 해요
import { composeMiddlewares } from '@karrotframe/navigator-plugin'

const filterPathMiddleware = async (
  ctx: BeforePushType,
  next: () => Promise<BeforePushType | void>
): Promise<BeforePushType | void> => {
  if (ctx.to === 'not valid') {
    // 미들웨어에서 value 를 가공해서 다음 미들웨어에게 전달할 수 있어요
    await next({
      ...ctx,
      to: 'valid',
    })
  }
  // next() 에 인자를 전달하지 않은 경우, 다음 미들웨어는 hook 이 기본적으로 받는 인자를 전달 받아요
  await next()
}
const loggerMiddleware = async ({
  to,
}: BeforePushType): Promise<BeforePushType | void> => {
  console.log('to: ', to)
}

export const pluginWithMiddleware: NavigatorPluginType = {
  name: 'pluginWithMiddleware',
  executor: (): PluginType => ({
    lifeCycleHooks: {
      beforePush: composeMiddlewares<BeforePushType>([
        filterPathMiddleware,
        loggerMiddleware,
      ]),
    },
  }),
}

composeMiddlewares 로 middleware 를 묶을 경우, hook 에 전달할 콜백 함수의 두번째 인자인 next 함수를 호출 가능해요.

미들웨어로 hook 의 lifecycle 을 단계적으로 다룰 수 있고, 값을 가공해서 next() 의 인자로 전달하여

다음 미들웨어가 기본적으로 받는 hook 의 context 인자 대신, 선행한 미들웨어가 가공한 context 인자를 받을 수 있어요.

Lifecycle Hooks

beforePush

push() 를 호출 전에 콜백을 실행해요.

nametypedescriptionexample
toStringpush() 호출을 통해 이동하려는 path 에요."/product"
screenInstancesIScreenInstance[]클라이언트에서 보고 있는 화면(screen) 인스턴스 정보를 저장한 배열이에요
screenInstancePtrnumber현재(current) 화면(screen)을 가리키는 pointer 에요.3
optionsOptionspush, replace, pop 을 콜백 함수에서 호출 할 수 있어요

screenInstances

;[
  {
    id: 2,
    screenId: '/product',
    nestedRouteCount: 0,
    present: false,
    as: '/product',
  },
]

onPushed

target path 로 route 이동(push)을 실행하기 직전에 콜백 함수를 실행해요.

nametypedescriptionexample
toStringpush() 호출을 통해 이동하려는 path 에요."/product"
screenInstancesIScreenInstance[]클라이언트에서 보고 있는 화면(screen) 인스턴스 정보를 저장한 배열이에요
screenInstancePtrnumber현재(current) 화면(screen)을 가리키는 pointer 에요.3
optionsOptionspush, replace, pop 을 콜백 함수에서 호출 할 수 있어요

screenInstances

;[
  {
    id: 2,
    screenId: '/product',
    nestedRouteCount: 0,
    present: false,
    as: '/product',
  },
]

beforeReplace

replace() 를 호출 전에 콜백을 실행해요.

nametypedescriptionexample
toStringreplace() 호출을 통해 이동하려는 path 에요."/account"
optionsOptionspush, replace, pop 을 콜백 함수에서 호출 할 수 있어요

onReplaced

target path 로 route 이동(replace)을 실행하기 직전에 콜백 함수를 실행해요.

nametypedescriptionexample
toStringreplace() 호출을 통해 이동하려는 path 에요."/account"
optionsOptionspush, replace, pop 을 콜백 함수에서 호출 할 수 있어요

beforePop

pop() 를 호출 전에 콜백을 실행해요.

nametypedescriptionexample
fromStringpop() 호출하는 시점의 path 에요."/product/1"
screenInstancesIScreenInstance[]클라이언트에서 보고 있는 화면(screen) 인스턴스 정보를 저장한 배열이에요
screenInstancePtrnumber현재(current) 화면(screen)을 가리키는 pointer 에요.3
optionsOptionspush, replace, pop 을 콜백 함수에서 호출 할 수 있어요

screenInstances

;[
  {
    id: 2,
    screenId: '/product',
    nestedRouteCount: 0,
    present: false,
    as: '/product',
  },
]

onPopped

주어진 depth 만큼 이전 route 로 이동을 실행하기 직전에 콜백 함수를 실행해요.

nametypedescriptionexample
fromStringpop() 호출하는 시점의 path 에요."/product/1"
screenInstancesIScreenInstance클라이언트에서 보고 있는 화면(screen) 인스턴스 정보를 저장한 배열이에요
screenInstancePtrnumber현재(current) 화면(screen)을 가리키는 pointer 에요.3
optionsOptionspush, replace, pop 을 콜백 함수에서 호출 할 수 있어요

screenInstances

;[
  {
    id: 2,
    screenId: '/product',
    nestedRouteCount: 0,
    present: false,
    as: '/product',
  },
]

onPoppedWithData

pop().send() 를 호출할 때 콜백 함수를 실행해요.

nametypedescriptionexample
fromStringpop() 호출하는 시점의 path 에요."/product/1"
dataobjectpop().send() 의 인자로 전달하는 data 에요{ name: 'John Doe' }
screenInstancesIScreenInstance클라이언트에서 보고 있는 화면(screen) 인스턴스 정보를 저장한 배열이에요
screenInstancePtrnumber현재(current) 화면(screen)을 가리키는 pointer 에요.3
optionsOptionspush, replace, pop 을 콜백 함수에서 호출 할 수 있어요

screenInstances

;[
  {
    id: 2,
    screenId: '/product',
    nestedRouteCount: 0,
    present: false,
    as: '/product',
  },
]

beforeRegisterScreen

Navigator 컴포넌트에 Screen 컴포넌트를 child 컴포넌트로 선언하면 render 할 때 해당 screen 을 등록해요.

이 screen 등록 전에 콜백 함수를 호출해요.

nametypedescriptionexample
screenIScreenScreen 컴포넌트에 props 로 전달하는 screen 정보를 담는 오브젝트에요
screensIScreen[]Navigator 의 child 컴포넌트로 선언한 각 screen 정보를 배열에 담고 있어요.

screen

{
   id: '/main',
   path: '/main',
   component:  Main
}

onRegisterScreen

Navigator 컴포넌트에 Screen 컴포넌트를 child 컴포넌트로 선언하면 render 할 때 해당 screen 을 등록해요.

이 screen 을 등록한 후에 콜백 함수를 호출해요.

nametypedescriptionexample
screenIScreenScreen 컴포넌트에 props 로 전달하는 screen 정보를 담는 오브젝트에요
screensIScreen[]Navigator 의 child 컴포넌트로 선언한 각 screen 정보를 배열에 담고 있어요.

screen

{
   id: '/main',
   path: '/main',
   component:  Main
}

beforeInsertScreenInstance

push 등의 route 이벤트가 발생하면, navigator 에 등록한 screen 정보를 기반으로 ScreenInstance 를 생성해요.

이 ScreenInstance 를 어플리케이션에 등록해서, 각 screen 화면을 개별적인 인스턴스로 다루는 과정을 거칠 때

어플리케이션에 ScreenInstance 를 등록하기 전에 콜백 함수를 호출해요.

nametypedescriptionexample
screenInstanceIScreenInstance클라이언트에서 보고 있는 화면(screen) 인스턴스 정보를 가지고 있어요.
screenInstancesIScreenInstance[]지금까지 등록한 screenInstance 을 배열로 가지고 있어요.
ptrnumber현재 등록하려는 screenInstance 를 가리키는 pointer 에요.5
optionsOptionssetScreenInstances 나 setScreenInstancePtr 을 콜백 함수에서 호출 할 수 있어요

screenInstances

;[
  {
    id: 4,
    screenId: '/product',
    nestedRouteCount: 0,
    present: false,
    as: '/product',
  },
]

onInsertScreenInstance

push 등의 route 이벤트가 발생하면, navigator 에 등록한 screen 정보를 기반으로 ScreenInstance 를 생성해요.

이 ScreenInstance 를 어플리케이션에 등록해서, 각 screen 화면을 개별적인 인스턴스로 다루는 과정을 거칠 때

어플리케이션에 ScreenInstance 를 등록 직후에 콜백 함수를 호출해요.

nametypedescriptionexample
screenInstanceIScreenInstance클라이언트에서 보고 있는 화면(screen) 인스턴스 정보를 가지고 있어요.
screenInstancesIScreenInstance[]지금까지 등록한 screenInstance 을 배열로 가지고 있어요. 방금 등록한 screenInstance 또한 배열 요소로 가지고 있어요.
ptrnumber현재 등록하려는 screenInstance 를 가리키는 pointer 에요.5
optionsOptionssetScreenInstances 나 setScreenInstancePtr 을 콜백 함수에서 호출 할 수 있어요

screenInstances

;[
  {
    id: 4,
    screenId: '/product',
    nestedRouteCount: 0,
    present: false,
    as: '/product',
  },
]

beforeMapScreenInstance

특정 상황에서, 특정 screenInstance 의 프로퍼티를 수정하는 경우가 있어요.

이 때 특정 screenInstance 를 지정해서 프로퍼티를 수정하는 mapper 함수를 호출하기 전에

콜백 함수를 실행해요.

nametypedescriptionexample
screenInstancesIScreenInstance[]지금까지 등록한 screenInstance 을 배열로 가지고 있어요. 방금 등록한 screenInstance 또한 배열 요소로 가지고 있어요.
ptrnumber현재 수정하려는 screenInstance 를 가리키는 pointer 에요.4
optionsOptionsmapperScreenInstance 를 콜백 함수에서 호출 할 수 있어요.

screenInstances

;[
  {
    id: 2,
    screenId: '/product',
    nestedRouteCount: 0,
    present: false,
    as: '/product',
  },
]

onMapScreenInstance

특정 상황에서, 특정 screenInstance 의 프로퍼티를 수정하는 경우가 있어요.

이 때 특정 screenInstance 를 지정해서 프로퍼티를 수정하는 mapper 함수를 호출하여

특정 screenInstance 의 프로퍼티를 변경한 직후에

콜백 함수를 실행해요.

nametypedescriptionexample
screenInstancesIScreenInstance[]지금까지 등록한 screenInstance 을 배열로 가지고 있어요. 방금 등록한 screenInstance 또한 배열 요소로 가지고 있어요.
ptrnumber수정한 screenInstance 를 가리키는 pointer 에요.4
optionsOptionsmapperScreenInstance 를 콜백 함수에서 호출 할 수 있어요.

screenInstances

;[
  {
    id: 2,
    screenId: '/product',
    nestedRouteCount: 0,
    present: false,
    as: '/product',
  },
]

beforeAddScreenInstancePromise

push 이벤트를 발생시킬 때, pop() 이벤트가 발생하거나 pop().send() 를 호출할 때

promise 인 push 이벤트를 resolve 하는 resolve 함수를 screenInstanceId 에 대응해

오브젝트로 등록해요.

이 오브젝트를 global state 에서 다루는 screenInstancePromiseMap 에 등록하기 전에

콜백 함수를 실행해요.

nametypedescriptionexample
screenInstanceIdstringscreenInstancePromise 를 등록하려는 screenInstanceId 값이에요.
screenInstancesIScreenInstance[]지금까지 등록한 screenInstance 을 배열로 가지고 있어요.
screenInstancePtrnumber현재(current) 화면(screen)을 가리키는 pointer 에요4
screenInstancePromiseIScreenInstancePromisescreenInstanceId 에 대응하는 screenInstance 의 push 이벤트를 resolve 하기 위한 resolve 함수를 저장한 오브젝트에요

screenInstances

;[
  {
    id: 2,
    screenId: '/product',
    nestedRouteCount: 0,
    present: false,
    as: '/product',
  },
]

onAddScreenInstancePromise

push 이벤트를 발생시킬 때, pop() 이벤트가 발생하거나 pop().send() 를 호출할 때

promise 인 push 이벤트를 resolve 하는 resolve 함수를 screenInstanceId 에 대응해

오브젝트로 등록해요.

이 오브젝트를 global state 에서 다루는 screenInstancePromiseMap 에 등록한 직후에

콜백 함수를 실행해요.

nametypedescriptionexample
screenInstanceIdstringscreenInstancePromise 를 등록하려는 screenInstanceId 값이에요.
screenInstancesIScreenInstance[]지금까지 등록한 screenInstance 을 배열로 가지고 있어요.
screenInstancePtrnumber현재(current) 화면(screen)을 가리키는 pointer 에요4
screenInstancePromiseIScreenInstancePromisescreenInstanceId 에 대응하는 screenInstance 의 push 이벤트를 resolve 하기 위한 resolve 함수를 저장한 오브젝트에요

screenInstances

;[
  {
    id: 2,
    screenId: '/product',
    nestedRouteCount: 0,
    present: false,
    as: '/product',
  },
]

onMountNavbar

ScreenHelmet 컴포넌트에 props 를 선언하면, @karrotframe/navigator 상단의 앱바(App Bar)를 설정할 수 있어요.

상단의 앱바는 내부 구현에서 navbar 를 mount 하여 render 를 완료해요.

navbar 를 mount 하는 시점에

콜백 함수를 실행해요.

nametypedescriptionexample
screenHelmetPropsIScreenHelmetPropsScreenHelmet 의 props 값들이에요

screenHelmetProps

{
  appendLeft: "button",
  appendRight: {
    $$typeof: Symbol(react.element)
    key: null
    props: {children: '', onClick: ƒ}
    ref: null
    type: "div"
  },
  closeButtonLocation: "left",
  customBackButton: null,
  customCloseButton: null,
  disableScrollToTop: false,
  noBackButton: false,
  noBorder: false,
  noCloseButton: false,
  onTopClick: () => { console.log("hello world"); },
  preventSwipeBack: false,
  title: "Main",
  visible: true,
}

onUnmountNavbar

ScreenHelmet 컴포넌트에 props 를 선언하면, @karrotframe/navigator 상단의 앱바(App Bar)를 설정할 수 있어요.

상단의 앱바는 내부 구현에서 navbar 를 mount 하여 render 를 완료해요.

mount 된 navbar 를 unmount 하는 시점에

콜백 함수를 실행해요.

nametypedescriptionexample
screenHelmetPropsIScreenHelmetPropsScreenHelmet 의 props 값들이에요

screenHelmetProps

{
  appendLeft: "button",
  appendRight: {
    $$typeof: Symbol(react.element)
    key: null
    props: {children: '', onClick: ƒ}
    ref: null
    type: "div"
  },
  closeButtonLocation: "left",
  customBackButton: null,
  customCloseButton: null,
  disableScrollToTop: false,
  noBackButton: false,
  noBorder: false,
  noCloseButton: false,
  onTopClick: () => { console.log("hello world"); },
  preventSwipeBack: false,
  title: "Main",
  visible: true,
}

Interfaces

type NavigatorPluginType = {
  name: string
  provider?: React.FC
  executor: () => PluginType
}
interface IScreen {
  id: string
  path: string
  Component: React.ComponentType
}
interface IScreenInstance {
  id: string
  screenId: string
  nestedRouteCount?: number
  present: boolean
  as: string
}
interface IScreenInstancePromise {
  resolve: (data: any | null) => void
}
interface IScreenInstancePromise {
  title?: React.ReactNode
  appendLeft?: React.ReactNode
  appendRight?: React.ReactNode
  closeButtonLocation?: 'left' | 'right'
  customBackButton?: React.ReactNode
  customCloseButton?: React.ReactNode
  noBorder?: boolean
  disableScrollToTop?: boolean
  onTopClick?: () => void
  visible?: boolean
  preventSwipeBack?: boolean
  noBackButton?: boolean
  noCloseButton?: boolean
}
0.6.1

2 years ago

0.6.0

2 years ago

0.5.0

2 years ago