предисловие
После некоторого периода изучения ведро семейства React также знакомо, поэтому я продолжу писать об увлечении вещами с хуками react + Друзья, пожалуйста, лайкните и подпишитесь🤗.
Введение в проект
- Используйте Redux для централизованного управления данными, Mockjs имитирует внутренний интерфейс данных.
- Придерживайтесь идеи MVVM, компонентизации и модульности и пишите страницы с чисто рукописными функциональными компонентами.
- Используйте Immutable для сохранения данных и оптимизации рендеринга компонентов.
- Написание стилей с помощью styled-components
- React-Router v6 пишет маршрутизацию, полностью используя стиль программирования Hooks.
Раздел классификации
(Примечание: поскольку данные передаются только по одежде, это нормальная работа, если другие элементы в меню первого уровня не отображаются нормально, а в конце текста есть онлайн-адрес)
- использовать
better-scroll
Создайте базовый скользящий компонент, используйте облачный музыкальный проект NetEase компании Sanyuan и создайте компоненты для переключения горизонтальной навигации и списка продуктов на вертикальном экране. - определение
Scroll
Обязательные параметры
import PropTypes from "prop-types"
Scroll.defaultProps = {
direction: "vertical",
click: true,
refresh: true,
onScroll:null,
pullUpLoading: false,
pullDownLoading: false,
pullUp: null,
pullDown: null,
bounceTop: true,
bounceBottom: true
};
Scroll.propTypes = {
direction: PropTypes.oneOf(['vertical', 'horizental']), // 滚动的方向
refresh: PropTypes.bool, // 是否刷新
onScroll: PropTypes.func, // 滑动触发的回调函数
pullUp: PropTypes.func, // 上拉加载逻辑
pullDown: PropTypes.func, // 下拉加载逻辑
pullUpLoading: PropTypes.bool, // 是否显示上拉 loading 动画
pullDownLoading: PropTypes.bool, // 是否显示下拉 loading 动画
bounceTop: PropTypes.bool, //是否支持向上吸顶
bounceBottom: PropTypes.bool //是否支持向下吸顶
};
- Обработка инкапсуляции
Scroll
компоненты
import React, { forwardRef, useState,useEffect, useRef, useImperativeHandle, useMemo } from "react"
import BScroll from "better-scroll"
import { debounce } from "../../utils/uiOptimization";
const [bScroll, setBScroll] = useState()
const scrollContaninerRef = useRef()
const { direction, click, refresh, pullUpLoading, pullDownLoading, bounceTop, bounceBottom } = props
const { pullUp, pullDown, onScroll } = props
// 防抖
let pullUpDebounce = useMemo(() => {
return debounce(pullUp, 500)
}, [pullUp])
let pullDownDebounce = useMemo(() => {
return debounce(pullDown, 500)
}, [pullDown])
// 创建 better-scroll 实例
useEffect(() => {
const scroll = new BScroll(scrollContaninerRef.current, {
scrollX: direction === "horizental",
scrollY: direction === "vertical",
probeType: 3,
click: click,
bounce:{
top: bounceTop,
bottom: bounceBottom
}
});
setBScroll(scroll)
return () => {
setBScroll(null)
}
}, [])
// 实例绑定 scroll 事件
useEffect(() => {
if(!bScroll || !onScroll) return
bScroll.on('scroll', onScroll)
return () => {
bScroll.off('scroll', onScroll)
}
}, [onScroll, bScroll])
// 上拉判断
useEffect(() => {
if(!bScroll || !pullUp) return;
const handlePullUp = () => {
//判断是否滑动到了底部
if(bScroll.y <= bScroll.maxScrollY + 100){
pullUpDebounce()
}
};
bScroll.on('scrollEnd', handlePullUp)
return () => {
bScroll.off('scrollEnd', handlePullUp)
}
}, [pullUp, pullUpDebounce, bScroll])
// 下拉判断
useEffect(() => {
if(!bScroll || !pullDown) return;
const handlePullDown = (pos) => {
//判断用户的下拉动作
if(pos.y > 50) {
pullDownDebounce()
}
};
bScroll.on('touchEnd', handlePullDown)
return () => {
bScroll.off('touchEnd', handlePullDown)
}
}, [pullDown, pullDownDebounce, bScroll])
// 刷新实例 防止无法滑动
useEffect(() => {
if(refresh && bScroll){
bScroll.refresh()
}
})
// 刷新组件
useImperativeHandle(ref, () => ({
// 暴露 refresh 方法
refresh() {
if(bScroll) {
bScroll.refresh();
bScroll.scrollTo(0, 0);
}
},
// 暴露 getBScroll 方法,提供 bs 实例
getBScroll() {
if(bScroll) {
return bScroll;
}
}
}));
- Страница категории выглядит как простое трехуровневое меню? Правильно, это простое трехуровневое меню.
- Строительство маршрутизации
import React, { lazy, Suspense } from 'react'
const KindComponent = lazy(() => import("../pages/kind"))
export default [
{
path: "/",
element: <HomeLayout />,
children: [
...
{
path: "/kind",
element: <Suspense fallback={null}><KindComponent></KindComponent></Suspense>
},
]
}
]
- стилизованные компоненты
Lcontent
а такжеNavContainer
и компоненты пользовательского интерфейсаColumn
Сформируйте меню первого уровня.
<Lcontent>
<NavContainer>
<Column list={kindTypes} handleClick={handleUpdateCatetory} oldVal={category}/>
</NavContainer>
</Lcontent>
- Компоненты пользовательского интерфейса горизонтального вторичного меню, эти два компонента ползунка могут бытьПросмотрите соответствующий исходный код
<Horizen2 list={item.title} handleClick={handleUpdetaList} oldVal={category2} />
- использовать
useRef
Запустите Dom, чтобы получить высоту или ширину каждого элемента для накопления и инициализации высоты или ширины родительского элемента.
useEffect(() => {
let categoryDOM = Category.current
let tagElems = categoryDOM.querySelectorAll("span")
let totalWidth = 0
Array.from(tagElems).forEach(ele => {
totalWidth += ele.offsetWidth
});
totalWidth += 120
categoryDOM.style.width = `${totalWidth}px`
}, [])
Раздел поиска
- Строительство маршрутизации
import React, { lazy, Suspense } from 'react'
const SearchComponent = lazy(() => import("../pages/search"))
export default [
{
path: "/",
element: <HomeLayout />,
children: [
...
{
path: "/search",
element: <Suspense fallback={null}><SearchComponent></SearchComponent></Suspense>
},
]
}
]
- Компоненты пользовательского интерфейса окна поиска
SearchBox
// useRef 监听输入
const queryRef = useRef()
<input ref={queryRef} className="box" placeholder="输入商品名" value={query} onChange={handleChange}/>
// 光标聚焦
useEffect(() => {
queryRef.current.focus()
}, [])
// 防抖 缓存
import { debounce } from '../../utils/uiOptimization'
let handleQueryDebounce = useMemo(() => {
return debounce(handleQuery, 500)
}, [handleQuery]);
useEffect(() => {
handleQueryDebounce(query)
}, [query])
Разложить поиск
-
axios
подготовка запроса
export const getHotKeyWordsRequest = () => {
return axiosInstance.get(`/search/hot`);
}
export const getResultList = (query) => {
return axiosInstance.get(`/search/keywords=${query}`)
}
-
mock
запрос на перехват
import Mock from "mockjs";
import { hotKeyWords } from './hot';
import shopAPI from './shop';
// 匹配接口 api 拦截请求
Mock.mock(/\/search\/hot/, "get", hotKeyWords);
Mock.mock(/\/search\/keywords=.+/, "get", (options) => {
let keyword = options.url.split('=')[1]
return shopAPI.shops().data.items.filter((item) => {
return item.title.indexOf(keyword) > 0
})
})
разработка редукционного слоя
- инициализация
state
const defaultState = ({
hotKeyWords: [], // 热门关键词列表
enterLoading: false,
resultList: []
})
- определение
constants
export const SET_HOT_KEYWRODS = "search/SET_HOT_KEYWRODS"
export const SET_ENTER_LOADING = 'search/SET_ENTER_LOADING'
export const SET_RESULT_LIST = 'search/SET_RESULT_LIST'
- определение
reducer
функция
import { produce } from 'immer'
export const searchReducer = produce((state, action) => {
switch(action.type) {
case actionTypes.SET_HOT_KEYWRODS:
state.hotKeyWords = action.data
break;
case actionTypes.SET_ENTER_LOADING:
state.enterLoading = action.data
break;
case actionTypes.SET_RESULT_LIST:
state.resultList = action.data
break;
}
}, defaultState)
- Читатели могут
actionCreators.js
конкретное представление файлаaction
функции, которые здесь не перечислены. - Экспорт связанных переменных
import { searchReducer } from './reducer'
import * as actionCreators from './actionCreators'
import * as constants from './constants'
export { searchReducer, actionCreators, constants }
-
reducer
зарегистрировать глобальныйstore
import { combineReducers } from "redux";
import { searchReducer } from '../pages/search/store';
export default combineReducers({
search: searchReducer,
});
- Сделайте эти приготовления, и тогда вы можете официально подключиться
redux
Ла!
// index.jsx
import { useDispatch, useSelector } from 'react-redux'
const { hotKeyWords, enterLoading, resultList } = useSelector((state) => ({
hotKeyWords: state.search.hotKeyWords,
enterLoading: state.search.enterLoading,
resultList: state.search.resultList
}))
const dispatch = useDispatch()
const getHotKeyWordsDataDispatch = () => {
dispatch(actionTypes.getHotKeyWords())
}
const getResultListDispatch = (q) => {
dispatch(actionTypes.getResultGoodList(q))
}
- Когда поле поиска пусто, отображать список популярных слов
useEffect (() => {
setShow (true)
if (!hotKeyWords.length) {
getHotKeyWordsDataDispatch()
}
}, [])
- Нажмите на горячее слово, чтобы отправить запрос на отображение результатов поиска
const handleQuery = (q) => {
setQuery (q);
if(!q) return;
dispatch(actionTypes.changeEnterLoading(true));
getResultListDispatch(q);
}
- Конструкция пользовательского интерфейса здесь специально не показана. Заинтересованные партнеры могутПросмотрите соответствующий исходный код
раздел комментариев
- использовать
css @keyframes
Правила реализуют всплывающий слой
export const CommentsContainer = styled.div`
position: fixed;
height: 70vh;
width: 100vw;
bottom: 0;
left: 0;
right: 0;
z-index: 2000;
background: ${style["default-color"]};
animation: Popup .4s;
padding: 60px 0;
@keyframes Popup {
0% {
transform: translate3d(0, 100%, 0);
}
100% {
transform: none;
}
}
...
`
Обзор реализации Moment.js + lokijs
- новый
database
Папка, добавить создать базу данных, коллекцию
// index.js
import Loki from 'lokijs'
export const db = new Loki('goods', {
autoload: true,
autoloadCallback: databaseInitialize,
autosave: true,
autosaveInterval: 3000,
persistenceMethod: "localStorage"
})
// 创建集合
function databaseInitialize() {
const comments = db.getCollection('comments')
if (comments == null) {
db.addCollection('comments')
}
}
export function loadCollection(collection) {
return new Promise(resolve => {
db.loadDatabase({}, () => {
const _collection = db.getCollection(collection)
resolve(_collection)
})
})
}
- С базами данных и коллекциями комментарии не правши?
import moment from 'moment'
// 需引入moment.locale()文件才能转换日期,完整代码就不列出
const [data, setData] = useState([])
const [query, setQuery] = useState('')
const queryRef = useRef()
// 获取输入框的值
const handleChange = () => {
let newValue = queryRef.current.value
if (newValue.trim() != '') {
setQuery(newValue);
}
}
- Инициализировать данные списка комментариев
useEffect(() => {
loadCollection('comments')
.then((collection) => {
const entities = collection.chain()
.find()
.simplesort('$loki', 'isdesc')
.data()
setData(entities)
})
}, [])
- Добавить комментарий, чтобы вставить коллекцию
const createComment = (query) => {
if (query.trim() != '') {
setData([...data, { body: query, meta: {
created: Date.now()
}}])
loadCollection('comments')
.then((collection) => {
collection.insert([
{
body: query
}
])
})
.then(setQuery(''))
}
}
- Отображение списка комментариев
<Scroll direction={"vertical"}>
<div className="comments-box">
{
data.map((item, index) =>
<CommentsBox key={index}>
<div className="comment">
{item.body}
</div>
<div className="time">
{moment(item.meta.created).fromNow()}
</div>
</CommentsBox>)
}
</div>
</Scroll>
трудности, основные моменты
- представлять
prop-types
Библиотека выполняет квалификацию типов при обработке потоков данных. - используется при построении маршрута
React.lazy
а такжеReact.Suspense
Выполните оптимизацию производительности, внедрите сегментацию кода и улучшите работу. - использовать
React.memo
,useMemo
,useCallback
Чтобы оптимизировать отрисовку компонентов, используйтеimmer
Сохранение данных, оптимизация рендеринга компонентов - Комментарии обновляются в режиме реального времени.
MVVM
const createComment = (query) => {
if (query.trim() != '') {
// 前端数据更新
setData([...data, { body: query, meta: {
created: Date.now()
}}])
// 插入数据库 数据更新
loadCollection('comments')
.then((collection) => {
collection.insert([
{
body: query
}
])
})
.then(setQuery(''))
}
}
-
better-scroll
Принцип заключается в том, что ширина или высота родителя фиксированы, а дочерний элемент прокручивается, если он превышает длину экрана, и его экземпляр действует только на первый дочерний элемент. - Используется при переходе страницы
react-transition-group
Делайте анимационные переходы
исходный код
- Добро пожаловать звезда😘
- Проект все еще обновляется и поддерживается, приглашаем друзей для обсуждения и обмена, нажмитеПортал 👀понять больше
- Кроме того, проект упакован и запущен в онлайн.Нажмите здесь, чтобы стать здоровым сейчас😍