Загрузка приложения с несколькими глобальными зависимостями может быть сложной задачей. Интернационализация, аутентификация, библиотека компонентов, тема, маршрутизация, управление состоянием, управление сеансом, границы ошибок и т. Д. - вот лишь некоторые из частей, которые вам нужно будет предоставить в своем приложении React.
В этой статье я попытаюсь представить схему четкого разделения проблем. Здесь нет оригинальных идей. Суть его взята из блогов Эрика Эллиотта и Кента Доддса.
Конечный результат будет примерно таким:
export const unAuthenticatedPage = compose( withI18n, withStrictMode, withRouter, withAuthentication, withLoginShell ); export const authenticatedPage = compose( withI18n, withStrictMode, withRouter, withRedux, withAuthentication, withAxiosHeaders, withAppShell, withErrorBoundary ); export const App = () => { const loginInfo = getUser(); return isUserAuthenticated(loginInfo.username) ? authenticatedPage(() => <AppContainer />) : unAuthenticatedPage(() => <LoginContainer />); };
Я хочу продемонстрировать, насколько просто и красиво это выражено. В приложении два персонажа. Один аутентифицирован, а другой нет. Оба требуют разной комбинации глобальной начальной загрузки. Секрет в том, как вы пишете свои HOC-оболочки.
Обертки HOC
Вот несколько примеров компонентов более высокого порядка. Они не готовы к копипасту, это просто общие рекомендации.
withRedux
const store = createStore(combineReducers(allAsyncReducers)); const withRedux = (Component) => (props) => ( <Provider store={store}> <Component {...props} /> </Provider> );
withi18n
Настроить i18n
import { initReactI18next } from "react-i18next"; import translations from "../locales"; const withI18n = (Component) => (props) => { i18n .use(LanguageDetector) .use(initReactI18next) .init({ resources: translations }); return <Component {...props} />; };
withRouter
Настроить response-router
const withRouter = (Component) => (props) => ( <Router basename={"/atlas"}> <Component {...props} /> </Router> );
withStrictMode
export const withStrictMode = (Component) => (props) => ( <StrictMode> <Component {...props} /> </StrictMode> );
withAppShell
Общие элементы пользовательского интерфейса, которые охватывают функции приложения
export const withAppShell = (Component) => (props) => ( <div> <Header> <Navigation /> </Header> <AppContent> <Component {...props} /> </AppContent> <Footer /> </div> );
withErrorBoundary
Защитите свою функцию от сбоя всего приложения. Есть хороший пример, доступный здесь:
export const withErrorBoundary = (Component) => (props) => ( <ErrorBoundary> <Component {...props} /> </ErrorBoundary> );
Аутентификация
Например.
export const withAuthentication = (Component) => (props) => { // reducer manages the authenticated user info const [state, dispatch] = useReducer(reducer, initialState); const { setUser } = actions(dispatch); const context = { state, actions: { setUser, setToken } }; React.useEffect(() => { // get use authentication details. if (loginInfo?.username) { setUser(loginInfo.username); } else { logout(); } }, []); return ( <> <AuthenticationContext.Provider value={loginInfo.username}> <Component {...props} /> </AuthenticationContext.Provider> </> ); };
Собираем все вместе
Если вы заметили, все эти оболочки HOC имеют одинаковую сигнатуру функции:
withXXX = (Component) => (props) => { ... }
Это означает, что вы можете очень красиво их составить. У аутентифицированной страницы будет другой набор проблем, чем у неаутентифицированной страницы, такой как Redux. Вы можете составить два разных поведения страницы:
import { compose } from "ramda"; export const unAuthenticatedPage = compose( withI18n, withStrictMode, withRouter, withAuthentication, withLoginShell ); export const authenticatedPage = compose( withI18n, withStrictMode, withRouter, withRedux, withAuthentication, withAxiosHeaders, withAppShell, withErrorBoundary );
Теперь настроить приложение стало очень просто:
export const App = () => { const loginInfo = getUser(); return userIsAuthenticated(loginInfo.username) ? authenticatedPage(() => <AppContainer />) : unAuthenticatedPage(() => <LoginContainer />); };
Этот шаблон обеспечивает простой способ объединения функций вашего приложения в определенном контексте. Вы можете расширить некоторые из этих контекстов, предоставив настраиваемые хуки useContext (). Допустим, вы хотите использовать зарегистрированное имя пользователя в других частях приложения. Для таких библиотек, как Redux, контекст уже предоставлен, и крючков, предоставляемых библиотекой, достаточно.
export const useAuthentication = () => { return React.useContext(AuthenticationContext); };
Так просто. Хуки в библиотеках, таких как Redux, будут работать без труда после того, как вы включите приложение в его поставщик контекста.
Резюме
В этой статье представлен простой, но мощный паттерн, который может загружать ReactApp с очень изолированными, слабосвязанными реализациями. Расширять их или добавлять новые становится очень легко. Все, что вам нужно сделать, это создать HOC и скомпоновать его, как показано выше.
Вы также можете использовать этот шаблон для создания нескольких глобальных контекстов и сделать их доступными для функций в приложении через хуки. Подробнее об этом в моей следующей статье о государственном управлении.