React. Lazy loading

Доброго времени суток.


Занимаюсь разработкой проекта на React и Redux. Хочу в этой статье описать архитектуру своего проекта.

И так начнем. Файловая структура:


Для подключения редьюсеров создаем класс singleton reducerRegister:
./reducerRegister.js
class ReducerRegistry {
  constructor () {
    if (!ReducerRegistry.instance) {
      this._emitChange = null
      this._reducers = {}
      ReducerRegistry.instance = this
    }
    return ReducerRegistry.instance
  }

  getReducers () {
    return {...this._reducers}
  }

  register (name, reducer) {
    this._reducers = {...this._reducers, [name]: reducer}
    if (this._emitChange) {
      this._emitChange(this.getReducers())
    }
  }

  setChangeListener (listner) {
    this._emitChange = listner
  }
}

const reducerRegistry = new ReducerRegistry()

export default reducerRegistry

С помощью этого класса редьюсеры могут сами себя регистрировать в store.

Создаем store:


./configureStore
export default function configureStore (initialState) {
  const combine = (reducers) => {
    const reducerNames = Object.keys(reducers)
    Object.keys(initialState).forEach(item => {
      if (reducerNames.indexOf(item) === -1) {
        reducers[item] = (state = null) => state
      }
    })
    reducers['router'] = connectRouter(history)
    return combineReducers(reducers)
  }

  const reducer = combine(reducerRegistry.getReducers())
  const store = createStore(reducer, initialState, compose(composeWithDevTools(applyMiddleware(thunk)), applyMiddleware(routerMiddleware(history))))

  reducerRegistry.setChangeListener(reducers => {
    store.replaceReducer(combine(reducers))
  })

  return store
}

С помощью функции store.replaceReducer загружаем редьюсеры в store.

Основной файл


Добавляем маршруты и подключаем redux
./index.js
const Cabinet = React.lazy(() => import('./moduleCabinet/Index'))

let store = configureStore({
  profile: {loading: null}
})

class App extends Component {
  render () {
    const history = createBrowserHistory()
    return (
      <Router basename="/">
        <ConnectedRouter history={history}>
          <Suspense fallback={<Loader/>}>
            <Switch>
              <Route exact path="/" component={Main} />
              <Route path="/admin" render={(props) => <RouteAdmin {...props} />}/>
              <Route path="/cabinet" component={props =>  <Cabinet {...props} />}}/>
              <Route
                path="/"
                component={() => <div>page not found</div>}
              />
            </Switch>
          </Suspense>
        </ConnectedRouter>
      </Router>
    )
  }
}

if (document.getElementById('app')) {
  ReactDOM.render(
    <Provider store={store}>
      <App/>
    </Provider>,
    document.getElementById('app')
  )
}

С помощью React.lazy делаем ленивую загрузку компонентов.React.lazy доступен, начиная с версии 16.6: React. Lazy loading. В элементе Suspense обрабатывается загрузка компонента.

AdminModule может загрузить только авторизованный пользователь, для этого используем компонент RouteAdmin:
./RouteAdmin.js
const NotAccess = (props) => {
  return (
    <div>
      <h1 className="text-danger">Доступ закрыт</h1>
    </div>
  )
}

export default class RouteAdmin extends Component {
  constructor (props) {
    super(props)
    this.state = {
      component: null
    }
  }

  componentDidMount () {
    axios.post('/admin').then(data => data.data).then(data => {
      if (data.auth === true) {
        const Admin = React.lazy(() => import('./moduleAdmin/Index'))
        this.setState({component: Admin})
      } else {
        this.setState({component: NotAccess})
      }

    })
  }

  render () {
    const Component = this.state.component
    return (
      <Route path="/admin" {...this.props} render={this.state.component}/>
    )
  }
}


Реализация модуля


Основной файл — добавляем маршруты модуля
./moduleAdmin/Index.js
export default class IndexComponent extends Component {
  constructor (props) {
    super(props)
  }

  render () {
    return (
      <>
        <Route to="/admin/Profiles" component={Profiles} />
        ...
      </>
    )
  }
}


./moduleAdmin/pages/Profiles.js
class Profiles extends Component {
    componentDidMount() {
        this.props.getInfo()
    }

    render() {
        if (this.props.loading === Process.Start) {
            return <Loader />
        }
        if (this.props.loading === Process.Success) {
            return (
                <div>
                    <h1>Profiles</h1>
                </div>
            )
        }
        return null
    }
}

const mapStateToProps = (state) => {
    return {
        loading: state.profiles.loading
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        getInfo: () => dispatch(getInfo())
    }
}

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(Profiles)


Создаем редьюсер


Тут же регистрируем его в store:
./moduleAdmin/redux/profile.js
const Process = {
    Start: 0, Success: 1, Error: 2
}

export const getInfo = () => {
    return (dispatch) => {
        dispatch({ type: PROFILES_GET_START })
        axios.post('/news').then((data) => {
            dispatch({ type: PROFILES_GET_SUCCESS, payload: data.data })
        }).catch(e => {
            dispatch({ type: PROFILES_GET_ERROR, payload: e })
        })
    }
}

const initialState = {
    error: null, loading: null, data: null
}

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case PROFILES_GET_START: {
            return { ...state, loading: Process.Start }
        }
        case PROFILES_GET_SUCCESS: {
            return { ...state, loading: Process.Success, data: action.payload}
        }
        case PROFILES_GET_ERROR: {
            return { ...state, loading: Process.Error, error: action.payload }
        }
        default: {
            return state
        }
    }
}
reducerRegistry.register('profiles', reducer)


Надеюсь моя статья поможет в реализации вашего проекта, а ваши комментарии помогут улучшить мой.
Теги:
javascript, react, redux, lazyload

Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.