Skip to content

Instantly share code, notes, and snippets.

@hazratgs
Last active September 8, 2022 23:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hazratgs/af78031b44fa6238bd75b28f2ec92458 to your computer and use it in GitHub Desktop.
Save hazratgs/af78031b44fa6238bd75b28f2ec92458 to your computer and use it in GitHub Desktop.
Feature flags

Feature Flags

Библиотека для работы с фича-флагами на клиенте (веб)

Фича-флаг это самый простоый выключатель/вкючатель фичи, с ее помощью можно выкатывать спокойно не готовые фичи (чего я бы не стал делать), они полезны тем, что если что-то пошло не так, можно быстро выключить фичу без необходимости хотфикса.

Использование

Необходимо обернуть приложение в FeatureProvider и передать конфиг в props.config

import { FeatureProvider } from 'feature-flags'

. . .

const flagConfig = {
  url: 'https://example.com'
}

const features = ['showNews', 'showPopup', 'canDoSmth'] // Интересующий нас список фича-флагов

const App = () => {
  return (
    <FeatureProvider features={features} config={flagConfig}>
      <Router history={history}>
        <Pages />
      </Router>
    </Provider>
  )
}

Далее в нужном слое (slice) приложения можем воспользоваться одним из вариантов получения данных о фичи:

. . . 
import { useFeature } from 'feature-flags'

const AuthForm = () => {
  . . .
  const isTwoFactory = useFeature('two-factory')

  . . .

  return (
    <s.AuthForm>
      . . .
      {isTwoFactory && <TwoFactory />}
    </s.AuthForm>
  )

}

Дополнительно

Динамическая подгрузка

Если необходимо динамически подгружать флаги, будь то при переходе на другую страницу или в прочих условиях, используйте метод get(request: TFeatureFlagsRequest) из контекста FeatureFlags:

import { FeatureFlags } from 'feature-flags'

. . .

const News = () => {
  const isShowNews = useFeature('showNews')

  const { get } = useContext(FeatureFlags)

  useEffect(() => {
    get({ keys: ['showNews'], lang: 'ru', userAgent: window.navigation.userAgent })
  }, [get])

  if (!isShowNews) {
    return <EmptyPage />
  }

  . . .
}

С помощью метода .get() можем подгружать фича-флаги в самых разных сценариях, например при смене локации.

Перезапрашивать все флаги в случае изменения одного из базовых параметров

Запрос фича-флагов при изменении конфига или одного из свойств FeatureProvider. Изменение следующих пропсов для FeatureProvider вызывают повторный запрос фича-флагов:

config, keys, lang, userAgent

Хранение данных

В данной примитивной реализации данные хранятся исключительно внутри контекста, кажется лучшим способо хранения я бы использовал .sessionStorage().

export * from './useFeatureFlags'
export * from './types'
import { IFeatureFlagsResponse } from '../types'
type TNormalizeFeatureFlagsResponseArgs = {
response?: IFeatureFlagsResponse[]
}
export const normalize = ({ response = [] }: TNormalizeFeatureFlagsResponseArgs) => {
return response.filter(({ value }) => value).map(({ key }) => key)
}
export type TFeatureFlag = string
export type TFeatureFlagsRequest = {
userAgent?: string
lang?: string
keys?: TFeatureFlag[]
}
export interface IFeatureFlags {
features: TFeatureFlag[]
get?: (data?: TFeatureFlagsRequest) => void
}
export type TFeatureFlagsConfig = {
url: string
}
export interface IFeatureProviderProps {
config: TFeatureFlagsConfig
features?: TFeatureFlag[]
lang?: string
userAgent?: string
}
export interface IFeatureFlagsResponse {
key: string,
value: boolean
}
import { createContext, FC, useCallback, useContext, useEffect, useState } from 'react'
import { stringify } from 'querystring'
import { useFetch } from 'use-http'
import { normalize } from './lib'
import {
IFeatureFlags,
IFeatureFlagsResponse,
IFeatureProviderProps,
TFeatureFlag,
TFeatureFlagsRequest,
} from './types'
const initialFeatures: IFeatureFlags = {
features: [],
}
export const FeatureFlags = createContext<IFeatureFlags>(initialFeatures)
export const FeatureProvider: FC<IFeatureProviderProps> = ({ config, features: keys, lang, userAgent, children }) => {
const [featureFlags, setFeatureFlags] = useState<TFeatureFlag[]>(initialFeatures.features)
const { get } = useFetch<IFeatureFlagsResponse[]>(config.url)
const getFeatureFlagsHandler = useCallback(async (request?: TFeatureFlagsRequest) => {
const response = await get(`?${stringify(request)}`)
setFeatureFlags(normalize({ response }))
}, [get])
const value = {
features: featureFlags,
get: getFeatureFlagsHandler,
}
useEffect(() => {
getFeatureFlagsHandler({ keys, lang, userAgent })
}, [getFeatureFlagsHandler, keys, lang, userAgent])
return (
<FeatureFlags.Provider value={value}>
{children}
</FeatureFlags.Provider>
)
}
export const useFeature = (feature: TFeatureFlag) => {
const { features } = useContext(FeatureFlags)
return features.includes(feature)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment