Реагировать на проект
содержание
один,стек технологий
два,Установка проекта
три,Архитектура проекта
Четыре,тестовое задание
Пятерки,развертывать
шесть,Вход в дом
Семь,Меню навигации
Восемь,Совместное использование состояния Redux-saga
9,И конфигурация
десять,конец
11,Ссылка на гитхаб
1. Стек технологий
Подробнее, пожалуйста, обратитесь к
package.json
- Create-react-app 3.0+
- Yarn
- React 16.9.0
- Redux-saga
- React Router
- Less
- Библиотека запросов Axios
- Webpack 4.0+
- ES6 + Babel
- Antd @3.23.6
项目说明:
1. Маршрутизация ленивой загрузки
2. Неверный маршрут соответствует странице 404
3. Запрос API инкапсулирует класс инструмента Axios
4. redux-saga обрабатывает асинхронные запросы
5. полоса загрузки nprogress
6. Аутентификация маршрутизации
7. Совместное использование состояния в режиме декоратора, перенос формы
8. Добавьте функцию перехода маршрутизации в Redux
7. Интерфейс представляет собой API java-сервера, и он пока не будет извлекаться, поэтому проект не может нормально войти в систему. соответствующее требование. Наконец, redux-saga действительно ароматная~~~
2. Установка проекта
В этом проекте используется пакет зависимостей для управления пряжей, и пряжа должна быть установлена.
yarn install //安装依赖
yarn start //运行
3. Структура проекта
⊙ Структура каталогов
.
├─ config/ # Webpack 配置目录
├─ public/ # 模板文件
├─ dist/ # build 生成的生产环境下的项目
├─ scripts/ # Webpack环境变量配置
├─ src/ # 源码目录(开发都在这里进行)
│ ├─ assets/ # 放置需要经由 Webpack 处理的静态文件
│ ├─ components/ # 组件
│ │ ├─ Layout/ # 全局布局
│ │ ├─ PrivateRoute/ # 路由守卫
│ ├─ store/ # Redux-sagas
│ │ ├─ actions/ # (Actions)
│ │ ├─ reducers/ # (Reducers)
│ │ ├─ sagas/ # (Sagas)
│ │ ├─ index.js # (Store文件管理)
│ ├── router/ # 路由(ROUTE)
│ ├── service/ # 服务(SERVICE,统一Api管理)
│ ├── utils/ # 工具库
│ ├── pages/ # 视图页(pages)
│ ├── index.js # 启动文件
│ ├── App.js # 主入口页
├── .gitignore # (配置)需被 Git 忽略的文件(夹)
├── package.json
4. Тест
Инструмент тестирования еще не добавлен
5. Развертывание
yarn build
После сборки, если вы хотите открыть его в локальной онлайн-среде, рекомендуется сначала установить локальный сервер http-server.
npm install http-server -g
После успешной установки запустите непосредственно в папке сборки, чтобы получить доступhttp-server
команда, чтобы открыть локальную среду службы. Вы также можете настроить порт самостоятельно. Конкретные команды см.http-server
Проблема с путем после упаковки приводит к тому, что страница становится пустой
После запуска локального сервера в каталоге сборки возможно, что проект пустой и ресурсы не могут быть загружены, просто настройте его в package.jsonhomepage
свойства в порядке
//package.json 文件增加配置
"homepage": ".",
6. Вход в дом
Файл записи в основном определяет страницу маршрутизации, поскольку этот проект представляет собой одностраничное приложение, поэтому в основной записи нужно только настроить маршрутизацию из трех модулей, корневой адрес маршрутизации./
соответствовать<IndexLayout />
,login
соответствовать<Login />
,404
соответствовать<ErrorPage />
Чтобы маршрут загружался лениво, при импорте маршрута можно обернуть слой Компонента асинхронным компонентом, а чтобы найти соответствующее имя файла при загрузке, можно установить/* webpackChunkName: "name" */
ОК, следующим образом
// 该文件为实现类似github页面加载的那个加载条
import LoadableComponent from '@/utils/LoadableComponent'
const Login = LoadableComponent(()=>import(/* webpackChunkName: "login" */ '@/pages/Login'))
Цитируется здесьHashRouter
Режим маршрутизации, режим BrowserRouter требует фонового взаимодействия, в противном случае это вызовет пустую ошибку при обновлении текущего адреса маршрутизации во время упаковки.
входной файлApp.js
Выложи весь код
import React, { Component } from 'react'
import IndexLayout from '@/components/Layout/index'
import { connect } from 'react-redux';
import LoadableComponent from '@/utils/LoadableComponent'
import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom'
const Login = LoadableComponent(()=>import(/* webpackChunkName: "login" */ '@/pages/Login'))
const ErrorPage = LoadableComponent(()=>import(/* webpackChunkName: "errorPage" */ '@/pages/ErrorPage'))
//装饰器模式链接Redux数据,省略很多复杂代码,真香。注意:不要的对象可以传个null
@connect(
state => ({
id_token: state.loginReducer.id_token
})
)
class App extends Component {
render() {
return (
<Router>
<Switch>
//此处重定向的地址为登录后的首页面地址
<Route exact path="/" render={ () => <Redirect to="/apply" push /> } />
//此处404页面只是声明路由地址,暂未匹配路由
<Route path="/404" component={ ErrorPage } />
//路由鉴权,如果没有id_token则跳转到登录页
<Route path="/login" render={() => {
return this.props.id_token ? <Redirect to="/" /> : <Login />
}} />
//登录后的主模板组件
<Route render={ () => <IndexLayout /> } />
</Switch>
</Router>
)
}
}
export default App
6. Меню навигации
В проекте левая и правая верстка, нет хедера, футера. Интерфейс относительно инновационный...
import React, { Component } from 'react'
import ContentMain from '@/components/Layout/ContentMain' //主内容组件
import SliderNav from '@/components/Layout/SliderNav' //菜单栏组件
import { Layout } from 'antd'
const { Content, Sider } = Layout;
class IndexLayout extends Component {
render() {
return (
<Layout>
<Sider
collapsible
trigger={null}
>
<SliderNav/>
</Sider>
<Layout>
<Content style={{background: '#f7f7f7'}}>
<ContentMain/>
</Content>
</Layout>
</Layout>
)
}
}
export default IndexLayout
Код строки меню не публикуется один за другим, содержимое немного длинное, а столбец маршрутизации строки меню имеет конфигурацию ввода подменю, если соответствующий массив маршрутизации настроен в соответствии с форматом. все导航菜单布局
существуетComponents
библиотека компонентовLayout
каталог папок. Вот основная информация о конфигурации маршрута панели навигации. Обратите внимание, что конфигурация маршрутизации здесь — это не та же информация, что и в предыдущей конфигурации маршрутизации, первая — это общий адрес маршрутизации, а маршрутизация — правильная.Content
вся маршрутная информация.
import React, { Component } from 'react'
import { withRouter, Switch, Redirect, Route } from 'react-router-dom'
import LoadableComponent from '@/utils/LoadableComponent'
//路由鉴权组件,包裹所有`Content`的页面,如果token失效,则跳回`Login页面`
import PrivateRoute from '@/components/PrivateRoute'
const Apply = LoadableComponent(()=>import(/* webpackChunkName: "apply" */ '@/pages/Apply'))
const Case = LoadableComponent(()=>import(/* webpackChunkName: "case" */ '@/pages/Case'))
@withRouter
class ContentMain extends Component {
render () {
return (
<div style={{padding: '20px 32px'}}>
<Switch>
<PrivateRoute exact path='/apply' component={ Apply }/>
<PrivateRoute exact path='/case' component={ Case }/>
//404页面在这里匹配路由,就能正确匹配错误路由了
<Route render={ () => <Redirect to="/404" /> } />
<Redirect exact from='/' to='/apply'/>
</Switch>
</div>
)
}
}
export default ContentMain
Восьмое, совместное использование состояния Redux-saga
при принятии решения об использованииRedux-saga
Раньше также считалось цитироватьRedux-thunk
делать разделение состояния, в конце концовRedux-thunk
Начать намного проще. ноRedux-thunk
Синхронный и асинхронный код необходимо размещать в одном файле, если слишком много одностраничных интерфейсов, это приведет к спагетти-коду, который не способствует пониманию и сопровождению. Вот решил процитироватьRedux-saga
. Посередине много ям, потому что редко встречается на GithubRedux-saga
Сравнивая полные серии предметов, которые можно взять напрокат, каждый раз, когда вы застрянете, вы будете читать различные материалы. Далее будут перечислены проблемы, с которыми легко столкнуться в процессе разработки, чтобы друзья, ссылающиеся на этот проект, меньше наступали на ямы...
1. Сначала вставьте файл конфигурации магазина напрямую
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducers';
import sagas from './sagas'
import { routerMiddleware } from 'react-router-redux';
const sagaMiddleware = createSagaMiddleware();
const createHistory = require('history').createHashHistory;
const history = createHistory(); // 初始化history
const routerWare = routerMiddleware(history);
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware, routerWare));
const store = createStore(
reducer,
enhancer
)
sagaMiddleware.run(sagas)
export default store
представлен здесьsagas
классифицируются по цитируемости страницsaga
файл, чтобы избежать записи всех асинхронных запросов в один и тот же файл. Поскольку в этом проекте всего три модуля: страница входа, страница приложения и страница обращения, целесообразно разделить его на три модуля.
// saga模块化引入
import { fork, all } from 'redux-saga/effects'
// 异步逻辑
import { loginSagas } from './login'
import { applySagas } from './apply'
import { caseSagas } from './case'
// 单一进入点,一次启动所有Saga
export default function* rootSaga() {
yield all([
fork(loginSagas),
fork(applySagas),
fork(caseSagas)
])
}
redux-saga
Я не буду подробно останавливаться на правильном использовании .Студенты, которые хотят цитировать, могут перейти к введению официальной документации. Или возьмите исходный код этого проекта, следуйте за тыквой и нарисуйте совок, и напишите его несколько раз, и вы будете знать больше. Тем не менее, посылка здесь заключается в том, что вы должны сначала понятьEs6的Generator函数
.
специфическийredux-saga
Асинхронные ссылки используются во многих местах проекта, я исправлю это, когда будет времяredux-saga
Использовать учебник.
Вот две цитатыredux-saga
легкая яма
- Маршрут перехода после завершения асинхронного запроса
- Асинхронный запрос redux-saga выполняется только один раз, например интерфейс подкачки, независимо от того, как вы разделите страницу, она будет загружена только в первый раз.
① Маршрут перехода после завершения асинхронного запроса
react
а такжеvue
Скачок маршрутизации немного непоследовательный,vue
Он инкапсулирует всю информацию о маршрутизации, если вы ссылаетесьvue-router
, вы можете ссылаться на переход маршрута по своему усмотрению. ноreact
разные, вjs文件
в случае цитированияreact-router
можно прямо цитировать<Link></Link>
илиthis.props.history.push('/login')
способ перейти маршрут. но цитируетсяredux-saga
После того, как состояние передано, обычно требуется перейти к информации о маршрутизации после завершения асинхронного запроса, но здесьthis.props.history.push('/login')
Этот способ не действует. Если вы хотите переключаться между маршрутами при совместном использовании состояния, требуется дополнительная настройка.
1. Первая установкаhistory
а такжеreact-router-redux
yarn add history react-router-redux
2. Вstore
внутри кавычек
import createSagaMiddleware from 'redux-saga';
import { routerMiddleware } from 'react-router-redux';
const sagaMiddleware = createSagaMiddleware();
const createHistory = require('history').createHashHistory;
const history = createHistory(); //初始化history
const routerWare = routerMiddleware(history);
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
//这里是包裹两个中间件,saga和状态共享路由的中间件,想要在saga中跳转页面,routerWare这个中间件是必不可少的!!!
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware, routerWare));
3. Вsaga
применение в
import { push } from 'react-router-redux';
function* login() {
if (...) {// 登录成功,路由跳转
yield put(push('/login')) //Generator指令跳转
}
}
②Асинхронный запрос redux-saga выполняется только один раз
На самом деле основная причина этого в том, чтоGenerator指令
незнакомый
Следующий код должен быть добавлен к каждому методу генератораwhile(true){}
, с несколькимиGenerator
методyield all([])
Монитор, чтобы можно было делать по одномуsaga
После того, как запрос задачи закончится, приходите в следующий раз(例如分页)
Для одного и того же запроса он будет прослушивать каждый раз снова, а затем продолжать вводитьGenerator函数
внутри, так что интерфейс запрашивается не только один раз.
Этот крошечный маленький жук действительно заставил меня сильно страдать...
function * getSearchRequest() {
while(true){ //保持监听连接
const resData = yield take(types.GET_SEARCH_DATA);
const response = yield call(seachData, resData.payload)
yield put(getSearchDataSuccess(response))
}
}
function * getDetailRequest() {
while(true){
const resData = yield take(types.GET_DRAFT_DETAIL_REQUEST);
const response = yield call(searchDetail, resData.payload)
yield put(getDetailSuccess(response));
}
}
export function * caseSagas() {
yield all([
fork(getSearchRequest),
fork(getDetailRequest)
]);
}
Девять, конфигурация Antd
Этот проект относится к библиотеке компонентов пользовательского интерфейса Antd. Конечно, он слишком раздут, чтобы упаковывать и загружать все компоненты одновременно.Предложение по конфигурации загрузки по запросу, данное на официальном сайте, находится в проекте.yarn run eject
Предыдущий явно не соответствует пользовательской конфигурации большинства онлайн-кодов, поэтому необходимо настроитьantd
Загрузка по требованию, но также необходимо настроить отдельно
Здесь я поставлю все конфигурации Babel напрямую и решу две проблемы знаний. antd загружается по запросу и настраивается в режиме декоратора.
Режим декоратора должен сначала установить зависимости, а затем настроить babel.
yarn add babel-plugin-transform-decorators-legacy
//package.json
"babel": {
"plugins": [
[
"@babel/plugin-proposal-decorators", //引用@connect、@withRouter装饰器模式必须配置babel
{
"legacy": true
}
],
[
"import", //antd按需加载配置
{
"libraryName": "antd",
"libraryDirectory": "es",
"style": true //这里如果设置为true的话则为自定义主题,否则就是加载全部antd css样式
}
]
]
}
как указано выше, еслиantd
загружать конфигурацию по запросуstyle
собственностьtrue
Если да, то настройка пользовательской темы еще не закончена, продолжаем...
существуетwebpack.config.js
файл,
// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = ...
if (preProcessor) {
let loader = {
loader: require.resolve(preProcessor),
options: {
sourceMap: true,
},
}
if (preProcessor === "less-loader") {
//以下为antd的所有主题配色,更多的变量可访问
//https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less
loader.options.modifyVars = {
'primary-color': '#C89F64', //主题颜色
'link-color': '#1DA57A', //主题link颜色
'border-radius-base': '2px'
}
loader.options.javascriptEnabled = true
}
loaders.push(
{
loader: require.resolve('resolve-url-loader'),
options: {
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
loader
);
}
return loaders;
};
10. Концовка
Весь проект фактически разработан, жаль только, что API-запрос нельзя открыть, а можно только дать ссылку. Ниже я приложу исходный код Github, включая проект дизайна, не так сложно посмотреть исходный код с UI. Если вы столкнетесь с тем же модулем, на который можно сослаться, я буду очень рад~~~
Приложение: React - это полностью компонентная концепция разработки. Все является компонентом. При работе над этим проектом времени было слишком мало. Этот проект занял шесть или семь рабочих дней, не считая времени на тестирование. Так что связь кода все еще немного серьезна, простите меня ~ я добавлю ее позже, когда будет много времени.Immutable.js
, и оптимизируйте некоторые модули с помощью React-хуков, чтобы сделать их более привлекательными... хе-хе.
Наконец, продолжение следует. . .