возобновить
🎉Теперь поддерживается загрузка изображений с несколькими разрешениями (в настоящее время Safari не поддерживает)
🎉 Постоянно обновляется, совместимо с ПК, планшетом, телефоном и устройствами с различным разрешением!
предисловие
Я узнал некоторое время назадReact
знания, попробуйте использоватьClass Component
а такжеHook
Существует два способа проведения проектной практики, среди которыхClass Component
Использование в основном вращается вокруг жизненного цикла,Hook
Это относительно новая функциональная реализация, которая ослабляет существование жизненного цикла, что может бытьReact
Путь в будущее.
Попробуйте использовать официальнуюcreate react app
и обеспечивают муравьиumi
построить проект,create react app
Дайте только самое основноеreact
Упаковка проекта и текущая конфигурация (маршрутизация и другие связанные конфигурации должны быть реализованы самостоятельно), иumi
Предоставление подробной конфигурации из коробки (предварительная обработка CSS состоит из выбора, AUI Framework ввести третьей стороне, автоматизированная конструкция маршрутизации пакета) может быть гибко выбран в соответствии с требованием.
использовалant design react
(P.S. Так обои менять удобнее 😂)
В связи с реализованным проектом「在线壁纸网站」
, в сравнении сreact ui
библиотека, окончательно выбраннаяsemantic ui react
.
Преимущества заключаются в следующем:
- Поддержка пользовательского стиля меток рендеринга компонентов
- Поддерживает богатые стили и конфигурации компонентов
- Поддержка инверсии цвета компонентов для упрощения реализации
暗黑模式
- Компоненты названы на основе семантики, которую легко найти и использовать.
Tip:
Эта статья в основном знакомитreact hook
Построение базового проекта, бэкенд основан наNode
Реализовать простую переадресацию и обработку интерфейса, которая пока не рассматривается в этой статье.redux
Введение и внутренняя реализация будут постепенно обновляться позже.
обои от360壁纸库
, я хотел бы выразить свою благодарность здесь.Он используется только для обучения и общения, а не для коммерческого использования.
Адрес связанного документа:
react | create react app | umi | ant design react | semantic ui react
Показать результаты
- Нажмите, чтобы просмотреть онлайн
- Опыт сканирования кода
Начинать
Далее будет представлен процесс построения проекта через два аспекта, а именно инициализацию проекта и формальную разработку проекта (компонента)😁.
Во время презентации мы сначала разработайте дизайнерские идеи и проблемы, а затем описываем детали реализации, в конечном итоге присоединят соответствующий исходный код для достижения 💪.
Пожалуйста, исправьте все ошибки в тексте, и предложения приветствуются 😘.
Завершить инициализацию проекта
для лучшего понимания и изученияReact项目
Процесс строительства и навыки, здесь мы решили использовать официально предоставленныеcreate react app
Эта инициализация проекта разделена на следующие этапы:
Разделение и создание каталогов --> Ввести соответствующий пакет зависимостей --> Инициализировать глобальный стиль CSS --> Завершить модуль обработки маршрутизации
отдел каталогов
- src
- api
「api定义及拦截器处理」
- assets
「图片等静态资源」
- basicUI
「基础UI组件」
- components
「自定义封装组件」
- conifg
「项目配置文件(主题、样式、导航等配置)」
- layouts
「布局组件」
- routes
「路由配置」
- store
「redux相关(此次内容不涉及)」
- views
「页面组件」
- api
импорт зависимостей
Разберите зависимости, используемые в этом проекте
PS: частичные зависимости добавляются во время процесса разработки проекта. Только известные необходимые зависимости могут быть введены, когда проект изначально построен.
реагировать на основные зависимости
- react
「react核心依赖」
- react-dom
「react-dom关联核心依赖」
- react-scripts
「react开发、运行等相关配置依赖」
- react-router-config
「提供路由静态配置」
- react-router-dom
「react-dom的增强,提供基础路由容器组件及路由操作能力」
Зависимости сторонних компонентов
- react-lazyload
「懒加载组件」
- styled-components
「样式组件」
- semantic-ui-react
「语义化react ui库」
- react-infinite-scroller
「无限滚动组件」
- react-transition-group
「切换动画组件」
разное
- axios
「请求处理」
импорт стиля
для разных браузеровH5标签
При одинаковом исполнении стилей все стили меток должны быть инициализированы единообразно, что здесь объединеноstyled-components
изcreateGlobalStyle
Создайте глобальный стиль инициализации.
src/style.js
import { createGlobalStyle } from 'styled-components'
// 创建全局样式
export const GlobalStyle = createGlobalStyle`
body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend, button, input, textarea, th, td { margin:0; padding:0; }
body, button, input, select, textarea { font: 100% inherit; }
h1, h2, h3, h4, h5, h6{ font-size:100%; }
address, cite, dfn, em, var { font-style:normal; }
code, kbd, pre, samp { font-family: couriernew, courier, monospace; }
small{ font-size:12px; }
ul, ol { list-style:none; }
a { text-decoration:none; cursor: pointer; }
a:hover { text-decoration:none; }
sup { vertical-align:text-top; }
sub{ vertical-align:text-bottom; }
legend { color:#000; }
fieldset, img { border:0; }
button, input, select, textarea { font-size:100%; }
table { border-collapse:collapse; border-spacing:0; }
`
существуетApp.js
Компонент глобального стиля может быть представлен в файле .
src/App.js
import React from 'react'
import { GlobalStyle } from './style' // init global css style
function App() {
return (
<div className="App">
<GlobalStyle/>
</div>
)
}
export default App
Строительство маршрутизации
Подготовка перед маршрутизацией
Перед настройкой маршрутизации для достиженияBlankLayout
а такжеBasicLayout
Благодаря простоте этого проекта, все компоненты используютсяReact.memo
Поверхностное сравнение сделано, чтобы предотвратить ненужный рендеринг, который не будет повторяться позже.
- BlankLayout (используется для сопоставления с исходным маршрутом и прямого отображения содержимого, соответствующего маршруту)
/src/layouts/BlankLayout.js
import React from 'react'
import { renderRoutes } from 'react-router-config'
const BlankLayout = ({route}) => {
return (99
<>{renderRoutes(route.routes)}</>
)
}
export default React.memo(BlankLayout)
- Passiclayout (для создания общего расположения страниц)
Он будет представлен позже здесьSticky
компоненты иcreateRef
(дляSticky
смонтировать целевой элемент) для исправленияNav
(верхняя панель навигации, подробно описанная ниже),Footer
(пользовательская информация нижнего колонтитула) компонент действует как информация нижнего колонтитула, область содержимого устанавливает минимальную высоту80vh
И визуализируйте страницу, соответствующую соответствующему подмаршруту.
/src/layouts/BasicLayout.js
import React, { createRef } from 'react'
import { renderRoutes } from 'react-router-config'
import Nav from '../components/Nav'
import Footer from '../components/Footer'
import navConfig from '../config/nav'
import { Sticky } from 'semantic-ui-react'
function BasicLayout (props) {
const contextRef = createRef()
const { route } = props
return (
<div ref={contextRef}>
<Sticky context={contextRef}>
<Nav data={navConfig}/>
</Sticky>
<div style={{ minHeight: '80vh' }}>
{renderRoutes(route.routes)}
</div>
<Footer/>
</div>
)
}
export default React.memo(BasicLayout)
Статическая конфигурация маршрутизации
сначала импортироватьlazy
а также Suspense
выполнить路由懒加载
а также延迟加载回调
И введение обычайCustomPlaceholder
Сборка (не мешает заставка, где масса компонентов альтернативная глобальная маска Загрузка) для маршрутизации первого загрузочного исполнения.
представлятьRedirect
Реализовать перенаправление корневого маршрута, импортBlankLayout
,BasicLayout
Соответствие первоначальной разводке и создание общего макета страницы соответственно.
Для последующей реализации после выбора типа обоев обновите страницу, чтобы правильно отобразить информацию о соответствующем типе обоев.Здесь метод параметра маршрута используется для реализации маршрутизации страницы обоев.
последний импорт404页面
Захватите маршрут в настоящее время невозможно соответствовать.
Прикрепил:Сравнение параметров маршрутизации React
src/router/index.js
import React, { lazy, Suspense } from 'react'
import { Redirect } from 'react-router-dom'
import BlankLayout from '../layouts/BlankLayout'
import BasicLayout from '../layouts/BasicLayout'
import CustomPlaceholder from '../basicUI/Placeholder'
// 延迟加载回调
const SuspenseComponent = Component => props => {
return (
<Suspense fallback={ <CustomPlaceholder /> }>
<Component {...props}></Component>
</Suspense>
)
}
// 组件懒加载
const PageWallPaper = lazy(() => import('../views/WallPaper'))
const PageAbout = lazy(() => import('../views/About'))
const Page404 = lazy(() => import('../views/404'))
export default [
{
component: BlankLayout,
routes: [
{
path: "/",
component: BasicLayout,
routes: [
{
path: "/",
exact: true, // 是否精确匹配
render: () => <Redirect to={"/wallpaper/5"} />
},
{
path: "/wallpaper/:id",
exact: true,
component: SuspenseComponent(PageWallPaper)
},
// ...等其他页面
{
path: "/*",
exact: true,
component: SuspenseComponent(Page404)
}
]
}
]
}
]
Реализовать верхнюю панель навигации
Концепт дизайна
Чтобы облегчить последующую информацию для настройки верхней панели навигации, учитывая, что сборка разработана так, чтобы быть гибкой и расширяемой.
Ссылка Общая топ-навигационная панель Дизайн, рассмотрите, что верхняя навигационная панель будет разделена на два состояния:
- Когда ширина достаточна (соответствует ПК, планшету и другим устройствам с большим экраном), информация меню навигации отображается горизонтально.
- Иконка веб-сайта и информация о заголовке
- Информация в левом меню (поддерживает раскрывающееся иерархическое меню)
- Информация правого меню (поддерживает раскрывающееся иерархическое меню)
- Поддержка навигации по сайту и перехода по внешним ссылкам
- Недостаточная ширина (соответствует телефону и другим мобильным устройствам). Отображение навигационной информации в раскрывающемся меню.
- Скрыть информацию о левом и правом меню и показать значок раскрывающегося меню
- Обеспечивает полноэкранное отображение меню навигации
- Поддержка навигации по сайту и перехода по внешним ссылкам
Учитывая, что в верхней панели навигации много информации о конфигурации, она извлекаетсяNav
Файлы конфигурации и инструкции по/src/config/nav.js
середина.
Подробнее о настройке панели навигации см.:Навигация конфигурации
Подробная реализация
getActiveItemByPathName
的方法用来根据路由信息比对菜单项信息,获取当前路由对应激活的菜单项。 пройти черезselectActiveItem
activeItem
Начальное значение , здесь реализуется операция инициализации активированного пункта меню.
Затем импортируйтеuseState
(hook
определить состояние компонента) и определитьactiveItem
а такжеphoneNavShow
Два состояния компонента, соответствующие активному в данный момент пункту менюkey
и контролирует, отображать ли移动端菜单组件
. Затем определите метод прослушивания изменений окна (используя функцию медиа-запроса) и вuseEffect
Включите функцию прослушивания (не забудьте удалить функцию прослушивания при уничтожении), и логика переключения между двумя состояниями в основном завершена.
ОпределенныйhandleMenuClick
Нажмите на меню, чтобы перейти к дочерней логике, разделенной外链URl
а также站内URL
, соответствующий логике открытия нового окна, установки пункта меню активации и выполнения маршрутного перехода.
Ну наконец тоmenuView
Завершите визуализацию подпунктов меню и всего кода макета.render
Реализация, основная логика черезphoneNavShow
Управляет отображением компонентов в состоянии большого экрана на мобильной стороне.PhoneNav
Компоненты (описаны ниже). Не забудьте импортироватьwithRouter
Обернут для обеспечения поддержки переходов маршрутизации.
src/components/Nav/index.js
import React, { useState, useEffect } from 'react'
import { Dropdown, Menu } from 'semantic-ui-react'
import { withRouter } from 'react-router-dom'
import PhoneNav from './PhoneNav'
function Nav (props) {
// 根据path获取activeItem
const getActiveItemByPathName = (menus, pathname) => {
let temp = ''
menus.map((item) => {
// 存在子菜单项
if (item.subitems && item.subitems.length > 0) {
item.subitems.map((i) => {
if (i.href === pathname) {
temp = i.key
return
}
})
}
if (item.href === pathname) {
temp = item.key
return
}
})
return temp
}
const selectActiveItem = () => {
const pathname = props.location.pathname
const val = getActiveItemByPathName(props.data.leftMenu, pathname)
return val === '' ? getActiveItemByPathName(props.data.rightMenu, pathname) : val
}
const [activeItem, setActiveItem] = useState(selectActiveItem())
const [phoneNavShow, setPhoneNavShow] = useState(false)
const x = window.matchMedia('(max-width: 900px)')
// 监听窗口变化 过窄收起侧边栏 过宽展开侧边栏
const listenScreenWidth = (x) => {
if (x.matches) { // 媒体查询
setPhoneNavShow(false)
} else {
setPhoneNavShow(true)
}
}
useEffect(() => {
listenScreenWidth(x) // 执行时调用的监听函数
x.addListener(listenScreenWidth) // 状态改变时添加监听器
return () => {
x.removeListener(listenScreenWidth) // 销毁时移除监听器
}
}, [x])
const handleMenuClick = (menu) => {
if (menu.externalLink) {
window.open(menu.href)
} else {
setActiveItem(menu.key)
props.history.push(menu.href)
}
}
// 根据菜单配置信息遍历生成菜单组
const menuView = (menus) => {
return menus.map((item) => {
return item.subitems && item.subitems.length ?
(
<Dropdown key={item.key} item text={item.title} style={{ color: props.data.textColor }}>
<Dropdown.Menu>
{
item.subitems.map((i) => {
return (
<Dropdown.Item onClick={ () => handleMenuClick(i) } key={i.key}>
{i.title}
</Dropdown.Item>
)
})
}
</Dropdown.Menu>
</Dropdown>
) :
(
<Menu.Item key={item.key}
active={activeItem === item.key}
style={{ color: props.data.textColor }}
onClick={ () => handleMenuClick(item) }
>
{ item.title }
</Menu.Item>
)
})
}
return (
<Menu size='huge' style={{ padding: '0 4%', background: 'black' }}
color={props.data.activeColor} pointing secondary
>
<Menu.Item header>
<img style={{ height: '18px', width: '18px' }} src={props.data.titleIcon}/>
<span style={{ color: 'white', marginLeft: '10px' }}>
{ props.data.titleText }
</span>
</Menu.Item>
{ phoneNavShow ? (
<>
<Menu.Menu position='left'>
{ menuView(props.data.leftMenu) }
</Menu.Menu>
<Menu.Menu position='right'>
{ menuView(props.data.rightMenu) }
</Menu.Menu>
</>
) : (
<Menu.Menu position='right'>
<Menu.Item>
<PhoneNav data={props.data} handlePhoneNavClick={menu => handleMenuClick(menu)}></PhoneNav>
</Menu.Item>
</Menu.Menu>
)
}
</Menu>
)
}
export default withRouter(React.memo(Nav))
Компонент PhoneNav
существуетPhoneNav
useState
activeIndex
а такжеvisible
Два состояния компонентов, соответственно указывающие на элемент расширения группы меню, который необходимо активировать, и отображать его или нет.全局下拉菜单
.
Затем определитеshowPhoneNavWrapper
Методы для достижения анимации для расширения кнопки меню и реализации управления全局下拉菜单
的显示。 определениеhandleMenuClick
Методы для достижения全局下拉菜单
Обработка щелчка дочернего элемента, которая реализуется путем обратного вызова метода щелчка меню родительского компонента и скрывает текущий全局下拉菜单
.
Ну наконец тоmenuView
Завершите визуализацию подпунктов меню и всего кода макета.render
Реализация (общая идея аналогична родительскому компоненту).
src/components/Nav/index.js
import React, { useState } from 'react'
import { PhoneNavBt, PhoneNavWrapper } from './style'
import { Icon, Menu, Accordion, Transition } from 'semantic-ui-react'
import { withRouter } from 'react-router-dom'
function PhoneNav (props) {
const [activeIndex, setActiveItem] = useState('')
const [visible, setVisible] = useState(false)
const emList = document.getElementsByClassName('phone-nav-em')
const showPhoneNavWrapper = () => {
setVisible(!visible)
if (visible) {
emList[0].style.transform = ''
emList[1].style.transition = 'all 0.5s ease 0.2s'
emList[1].style.opacity = '1'
emList[2].style.transform = ''
} else {
emList[0].style.transform = 'translate(0px,6px) rotate(45deg)'
emList[1].style.opacity = '0'
emList[1].style.transition = ''
emList[2].style.transform = 'translate(0px,-6px) rotate(-45deg)'
}
}
const handleMenuClick = (menu) => {
props.handlePhoneNavClick(menu)
setVisible(false)
emList[0].style.transform = ''
emList[1].style.transition = 'all 0.5s ease 0.2s'
emList[1].style.opacity = '1'
emList[2].style.transform = ''
}
const menuView = (menus) => {
return menus.map((item) => {
return item.subitems && item.subitems.length ?
(
<Accordion key={item.key} styled inverted style={{ background: 'black', width: '100%'}}>
<Accordion.Title
as={Menu.Header}
active={activeIndex === item.key}
index={0}
onClick={() => setActiveItem(activeIndex === item.key ? '-1' : item.key)}
>
<Icon name='dropdown' />
{ item.title }
</Accordion.Title>
{
item.subitems.map((i) => {
return (
<Accordion.Content style={{padding: '0px'}} key={i.key} active={activeIndex === item.key}>
<Menu.Item style={{ paddingLeft: '3rem', color: props.data.textColor, background: '#1B1C1D' }}
onClick={() => handleMenuClick(i) }>
{ i.title }
</Menu.Item>
</Accordion.Content>
)
})
}
</Accordion>
)
:
(
<Menu.Item style={{ color: props.data.textColor }} onClick={() => handleMenuClick(item) } key={item.key}>
{ item.title }
</Menu.Item>
)
})
}
return (
<>
<PhoneNavBt onClick={ showPhoneNavWrapper }>
<em className='phone-nav-em'></em>
<em className='phone-nav-em'></em>
<em className='phone-nav-em'></em>
</PhoneNavBt>
<Transition visible={visible} animation='fade' duration={500}>
<PhoneNavWrapper>
<Menu style={{ width: '100%' }} inverted size='huge' vertical>
{ menuView(props.data.leftMenu) }
{ menuView(props.data.rightMenu) }
</Menu>
</PhoneNavWrapper>
</Transition>
</>
)
}
export default React.memo(PhoneNav)
Профиль навигации
Подробнее о настройке панели навигации см.:конфигурация навигации
Реализовать загрузку главной страницы (ядро)
Концепт дизайна
В целях облегчения повторного использования, расширения и обновления компонентов, а также совместимости с устройствами разного разрешения считается, что он разделен на следующие функциональные компоненты модуля.
- Меню навигации по категориям обоев (Категория и выбор обоев)
- Список изображений (управлять макетом картинок, предоставить ленивую нагрузку)
- Компонент изображения (обеспечивает поддержку изображения-заполнителя, загрузку анимации перехода)
- Компонент предварительного просмотра большого изображения (предоставляет функцию предварительного просмотра большого изображения в полноэкранном режиме)
- Компонент загрузки (обеспечивает выбор разрешения и загрузку изображения)
Чтобы еще больше прояснить и разделить функции каждого компонента, функциональная логика каждого компонента сортируется с помощью карты связей, как показано на следующем рисунке:
Подробная реализация
Обои вид навигационного меню
Этот компонент относительно прост, обходит переданный родительский компонент.props.data
, может быть отображено содержимое соответствующего подменю, которое может быть объединено позжеRedux
Тема достигает функции переключения.
/src/components/MenuBar/index.js
import React from 'react'
import { Menu } from 'semantic-ui-react'
function MenuBar (props) {
return (
<>
{
props.data.length ?
<Menu secondary compact size='mini' style={{ background: 'white', width: '100%', overflow: 'auto' }}>
{
props.data.map((item, index) => {
return (
<Menu.Item onClick={() => props.onMenuClick(item)} key={index}>
{item.title}
</Menu.Item>
)
})
}
</Menu> : null
}
</>
)
}
export default React.memo(MenuBar)
Список изображений компонентов
Здесь он рассчитывается на основе ширины устройстваImgView
Ширина и высота контейнера-оболочки компонента (ImgView
автоматически заполнит контейнер пакета), чтобы обеспечить правильный размер изображения на устройствах разного размера.
затем используйтеLazyLoad
Ленивая загрузка сборки, предусмотренная на экране, чтобы прокрутить область просмотра200px
При загрузке изображения убедитесь, что загружается только изображение под текущим окном, когда оно не вытаскивается, и, наконец, передайте адрес и метку изображения вImgView
компоненты.
/src/basicUI/ImgListView/index.js
import React from 'react'
import LazyLoad from 'react-lazyload'
import ImgView from '../ImgView'
import { ImgListViewWrap, ImgViewWrap } from './style'
function ImgListView (props) {
const imgList = props.data
const width = (1 / (document.body.clientWidth / 360) * document.body.clientWidth).toFixed(3)
const height = (width * 0.5625).toFixed(3)
return (
<ImgListViewWrap>
{
imgList.length > 0 ? imgList.map((item) => {
return (
<ImgViewWrap key={item.id} width={ width + 'px' } height={ height + 'px' }>
<LazyLoad height={'100%'} offset={200} >
<ImgView
key={item.id}
onPreviewClick={() => props.handlePreview(item)}
onDownloadClick={() => props.handleDownload(item)}
url={item.url} tag={ item.utag }
/>
</LazyLoad>
</ImgViewWrap>
)
}) : null
}
</ImgListViewWrap>
)
}
export default React.memo(ImgListView)
компонент изображения
сначала поurl
Фильтр получает адреса изображений с низким разрешением (т. е. эскизы), чтобы уменьшить количество запросов данных изображения.
существуетrender
В основном он включает в себя следующие части:
- Реализовать размещение
- Градиент загрузки изображения
关于占位图片,初始状态时设置占位图片为绝对定位、默认显示,目标图片透明度为0。 пройти черезuseState
утверждениеisLoaded
onLoad
Прослушивание и модифицироватьisLoaded
состояние, в это время скройте изображение-заполнитель, измените прозрачность целевого изображения на 1, а затем завершите переключение после успешной загрузки (здесь используетсяuseCallback
Кэшируйте встроенные функции, чтобы обновления компонентов не создавали повторно анонимные функции).
Картинка по первым загрузкамCSSTransition
компоненты, на заказfade
Стиль анимации обеспечивает эффект перехода за счет изменения прозрачности.
Маска изображения использует абсолютное позиционирование относительноImgView
Ниже добавлены обратные вызовы кликов кнопок предварительного просмотра и загрузки.
/src/basicUI/ImgView/index.js
import React, { useState, useCallback } from 'react'
import { Image, Icon } from 'semantic-ui-react'
import { CSSTransition } from 'react-transition-group'
import { ImgWrap } from './style'
import loadingImg from './loading.gif'
import './fade.css'
function ImgView (props) {
const { url, tag } = props
const [isLoaded, setIsLoaded] = useState(false)
// cache memoized version of inline callback
const handleLoaded = useCallback(() => {
setIsLoaded(true)
}, [])
const filterUrl = () => {
const array = url.split('/bdr/__85/')
// 过滤url为低分辨率图片,防止加载时间较长
return array.length !== 2 ? url : array[0] + '/bdm/640_360_85/' + array[1]
}
// 正式Image未加载之前没有高度信息
return (
<ImgWrap>
<Image hidden={ isLoaded } className='img-placeholder' src={ loadingImg } rounded />
<CSSTransition
in={true}
classNames={'fade'}
appear={true}
key={1}
timeout={300}
unmountOnExit={true}
>
<Image onLoad={() => setIsLoaded(true)} style={{ opacity: isLoaded ? 1 : 0 }}
src={ filterUrl() } title={ tag } alt={ tag } rounded />
</CSSTransition>
<div className='dim__wrap'>
<span className='tag'>{ tag }</span>
<Icon onClick={ () => props.onPreviewClick() } name='eye' color='orange' />
<Icon onClick={ () => props.onDownloadClick() } name='download' color='teal' src={ filterUrl() } />
</div>
</ImgWrap>
)
}
export default React.memo(ImgView)
Компонент предварительного просмотра больших изображений
Компонент предварительного просмотра относительно прост, просто отобразите информацию об изображении и метке под глобальной маской.
/src/basicUI/ImgPreview/index.js
function ImgPreview (props) {
const { url, utag } = props.previewImg
return (
<Dimmer active={ props.visible } onClick={props.handleClick} page>
<Image style={{ maxHeight: '90vh' }} src={ url } title={ utag } alt={ utag } />
</Dimmer>
)
}
Скачать компоненты
Во-первых, он инкапсулирует класс инструмента для загрузки изображения и получает два параметра: адрес изображения и имя загруженного файла. Отправив запрос адреса изображения и установив тип возврата наblob
,Повторное использование<a>
ярлык для загрузки.
Наконечник:Невозможно из-за механизма безопасности Safari.blob
Связанные операции чтения и записи, поэтому этот метод нельзя использовать в Safari, и он должен оцениваться в компоненте загрузки как браузер Safari и предупреждать пользователя.
/src/basicUI/DownloadModal/download.js
function download (url, fileName) {
const x = new XMLHttpRequest()
x.responseType = 'blob'
x.open('GET', url, true)
x.send()
x.onload = () => {
const downloadElement = document.createElement('a')
const href = window.URL.createObjectURL(x.response) // create download url
downloadElement.href = href
downloadElement.download = fileName // set filename (include suffix)
document.body.appendChild(downloadElement) // append <a>
downloadElement.click() // click download
document.body.removeChild(downloadElement) // remove <a>
window.URL.revokeObjectURL(href) // revoke blob
}
}
Для загружаемых компонентов в соответствии с файлом конфигурации загрузки (src/config/download_options.js
), чтобы сгенерировать параметр списка загрузки, после нажатия кнопки «Загрузить» Safari оценивает и запрашивает и объединяет соответствующий адрес изображения с разрешением в соответствии с конфигурацией загрузки для загрузки.
Для получения подробной информации о конфигурации разрешения загрузки см.:Скачать конфигурацию разрешения
/src/basicUI/DownloadModal/index.js
function DownloadModal (props) {
const { url, utag } = props.downloadImg
const handleDownload = (param) => {
// Safari Tip
if (/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent)) {
alert('抱歉😅!暂不支持Safari下载!请手动保存照片!')
return
}
const array = url.split('/bdr/__85/')
array.length === 2 ? download(array[0] + param + array[1], utag + '.jpg') : download(url, utag + '.jpg')
}
return (
<Modal basic dimmer={ 'blurring' } open={ props.visible }>
<Header icon='browser' content='download options' />
<Modal.Content>
<List verticalAlign='middle'>
{ downloadOptions.length > 0
? downloadOptions.map((item, index) => {
return (
<List.Item key={ index }>
<List.Content floated='right'>
<Button onClick={ () => handleDownload(item.filterParam) }
basic color='green' icon='download' inverted size='mini' />
</List.Content>
<List.Content>
<Label>{ item.desc }</Label>
</List.Content>
</List.Item>
)
}) : null }
</List>
</Modal.Content>
<Modal.Actions>
<Button onClick={ () => props.onClose() } color='green' inverted>
OK
</Button>
</Modal.Actions>
</Modal>
)
}
export default React.memo(DownloadModal)
Страница WallPaper
PageWallPaper
Связанные к изображениям компоненты будут загружены, а логический контроль загрузки изображения, запроса и т. Д. будет завершен.
Так понятнее, а тут разборкаrender
а также逻辑处理
Две части введения:
- Введение в рендер
существуетrender
метод, первое использованиеSticky
Сборка исправленаMenuBar
Компонент в нижней части навигационной панели, перечислите типы обоевtypeList
передается компоненту и используетсяchangeImgType
Завершите обработку нажатием переключателя типа обоев.
затем используйтеInfiniteScroll
пакетImgListView
компоненты, которыеImgListView
Обработка предварительного просмотра, загрузка событий нажатия кнопки и получение списка изображенийimgList
. Бесконечно загруженные компонентыInfiniteScroll
согласно сisLoading
(независимо от того, загружается ли он),isFinished
(завершена ли вся загрузка),imgList.length
(Независимо от того, пуст ли список изображений) Определите, нужно ли поддерживать загрузку дополнительной информации (то есть будет ли прокрутка запускатьloadMore
Перезвоните).loadMore
Загрузите больше картинок в реализацию.
Наконец, согласноisLoading
,isFinished
Определяет, отображать ли подсказки пользователя, такие как загрузка, загрузка завершена и т. д.
представленImgPreview
,DownloadModal
Реализуйте поддержку предварительного просмотра больших карт и загрузки изображений.
src/views/WallPaper/index.js => render
function PageWallPaper (props) {
<!-- 这里仅展示render,逻辑处理部分后文介绍 -->
return (
<div ref={contextRef}>
{/* img type menu */}
<Sticky context={contextRef} offset={48} styleElement={{ zIndex: '10' }}>
<MenuBar onMenuClick={ changeImgType } data={typeList} />
</Sticky>
{/* loading img (infinity) */}
<InfiniteScroll
initialLoad
pageStart={0}
loadMore={ () => loadMoreImgs() }
hasMore={ !isLoading && !isFinished && imgList.length !== 0 }
threshold={50}
>
<ImgListView
handlePreview={ handlePreviewImg }
handleDownload = { handleDownloadImg }
data={ imgList }
/>
</InfiniteScroll>
{ isLoading ? <CustomPlaceholder /> : null }
{ isFinished ? <h1 style={{ textAlign: 'center' }}>所有图片已加载完成!✨</h1> : null }
{/* img preview */}
<ImgPreview handleClick={ hideImgPreview } visible={ isPreview } previewImg={ currentImg } />
{/* download options */}
<DownloadModal onClose={ hideDownloadModal } visible={ isDownload } downloadImg={ currentImg } />
</div>
)
}
- Логическое управление загрузкой изображений
сначала черезuseState
Определение различных компонентов и начальное состояние состояния, соответственно, запрос, загружено ли изображение, отображается ли предварительный просмотр, отображается ли загрузка, загружены ли все изображения, информация о выбранном изображении, изображение список, список видов (подробности см. в комментариях к коду), пройти черезcreateRef
sticky
Затем используйтеuseEffect
Полные связанные побочные эффекты, здесь используйте дваuseEffect
Добейтесь разделения интересов.
ПервыйuseEffect
, второй параметр[]
МоделированиеcomponentDidMount
Эффекты жизненного цикла, здесьgetTypes()
Получите тип обоев.
второйuseEffect
, второй параметр[queryInfo]
,Прямо сейчасqueryInfo
После замены позвонитеupdateImgList()
Метод обновляет список изображений.
дляgetTypes()
а такжеupdateImgList()
реализация, черезaxios
Отправьте запрос и сохраните нормальный результат в соответствующем состоянии компонента.
существуетupdateImgList()
isFinished
дляtrue
Array.concat()
imgList
fasle
.
Обратный вызов для клика по категории обоевchangeImgType()
, если будет установлено, что тип обоев не соответствует текущей странице, он перейдет на страницу (необходимо ввестиwithRouter
поддержки), затем вернитесь к началу страницы и восстановите исходное состояние компонента, в котором объект запроса изменен.queryInfo
изtype
условие.
Загрузить обратный вызов для прокручиваемого спискаloadMoreImgs
в, наборisLoading
дляtrue
и изменитьqueryInfo
Параметры запроса на этот раз запустят второйuseEffect
В качестве побочного эффекта завершается обновление списка изображений.
Наконец, ГМ.useCallback
Кэшируйте связанные встроенные функции, чтобы обновления компонентов не создавали повторно анонимные функции для повышения производительности.
src/views/WallPaper/index.js
import React, { useState, useEffect, createRef, useCallback } from 'react'
import { withRouter } from 'react-router-dom'
import { getCategories, getPictureList } from '../../api/getData'
function PageWallPaper (props) {
const [queryInfo, setQueryInfo] = useState({type: props.match.params.id || 5, start: 0, count: 30}) // query info
const [isLoading, setIsLoading] = useState(true) // is loading img
const [isPreview, setIsPreview] = useState(false) // is preview img
const [isDownload, setIsDownload] = useState(false) // is download modal show
const [isFinished, setIsFinished] = useState(false) // is all img loading finished
const [currentImg, setCurrentImg] = useState({}) // current img info
const [imgList, setImgList] = useState([])
const [typeList, setTypeList] = useState([])
const contextRef = createRef()
useEffect(() => {
getTypes()
}, [])
useEffect(() => {
updateImgList()
}, [queryInfo])
const getTypes = async () => {
const res = await getCategories()
if (res.data) {
setTypeList(res.data.data)
}
}
// update img list
const updateImgList = async () => {
const res = await getPictureList({...queryInfo})
if (res.data) {
if (res.data.data.length === 0) {
setIsFinished(true)
} else {
setImgList(imgList.concat(res.data.data))
}
setIsLoading(false)
}
}
const changeImgType = (item) => {
if (item.key !== queryInfo.type) {
props.history.push('/wallpaper/' + item.key)
}
document.body.scrollTop = 0
document.documentElement.scrollTop = 0
// init state
setImgList([])
setIsLoading(true)
setIsFinished(false)
setQueryInfo({...queryInfo, type: item.key })
}
const loadMoreImgs = () => {
setIsLoading(true)
setQueryInfo({...queryInfo, start: queryInfo.start + queryInfo.count})
}
// cache memoized version of inline callback
// click preview
const handlePreviewImg = useCallback((img) => {
setCurrentImg(img)
setIsPreview(true)
}, [])
// click download
const handleDownloadImg = useCallback((img) => {
setCurrentImg(img)
setIsDownload(true)
}, [])
// hide ImgPreview
const hideImgPreview = useCallback(() => {
setIsPreview(false)
}, [])
// hide DownloadModal
const hideDownloadModal = useCallback(() => {
setIsDownload(false)
}, [])
}
export default withRouter(React.memo(PageWallPaper))
На данный момент дизайн страницы обоев и разработка логики загрузки завершены, а эффект загрузки изображения и логическая развязка будут продолжать оптимизироваться в будущем.
нижний колонтитул, страница исключений
Наконец, разработка нижнего колонтитула и страницы исключений завершена.Страница может быть оформлена в соответствии с личными предпочтениями, в основном на основе стиля, иhook
Корреляций не так много, поэтому я не буду их здесь повторять.
Подробнее о настройке нижнего колонтитула см.:.
Эпилог
React
Hook
Его использование также увеличивает ваше собственное понимание и мышление в процессе проектирования и разделения компонентов.
Если в статье есть упущения или ошибки, просьба указать на них.
Проект все еще дорабатывается и обновляется, и мы приветствуем предложения и вдохновение.
Проект был открытgithub
Добро пожаловать на загрузку и использование, в будущем будет улучшено больше функций 🎉Исходный код и описание проекта
Наконечник:Не забудьте, если вам нравитсяstar
О 😘, если есть вопросы 🧐 добро пожаловатьissues
Активный обмен.
Другие статьи:
Node реализует автоматическое развертывание переднего плана с нуля
Инструмент автоматического развертывания сборки с открытым исходным кодом ⚡ auto-deploy-app