предисловие
Поскольку реакция 16.8,react-hooks
С момента своего рождения он использовался в работеhooks
, Более года, проект реакции, с которым я связался, постепенно использовалfunction
Вместо этого компоненты без состоянияclasss
Компонент состояния состояния также обобщает некоторые события в течение периода. Специально для недавнего трехмесячного проекта я использовал небольшие кастомные хуки, чтобы справиться с повторяющейся логикой в проектах компании, и общее ощущение неплохое. Сегодня я расскажу вам о своей работеreact-hooks
Опыт, и некоторые дизайнерские идеи нестандартных крючков, и поделиться опытом работы со всеми.
Индивидуальный дизайн крючков
Снова вернуться к этому вопросу? Что такое крючки.
react-hooks
После реагирования 16.8 новая API крюка реактивных направлена на увеличение повторного использования и логики кода, а для того, чтобы составить дефекты, которые не имеют жизненного цикла, не имеют жизненного цикла и отсутствие состояния управления данными. Автор думает,react-hooks
Идея и первоначальное намерение заключаются в том, чтобы детализировать и объединить компоненты для формирования независимой среды рендеринга, уменьшить количество рендерингов и оптимизировать производительность.
Еще одна статья, которую могут прочитать партнеры, не разбирающиеся в реактивных хуках:Как используются реактивные хуки?
Что такое нестандартные хуки
Пользовательские крючки вreact-hooks
В качестве расширения основы можно сформулировать крючки для удовлетворения потребностей бизнеса в соответствии с потребностями бизнеса, при этом больше внимания уделяется логическим единицам. Согласно различным бизнес-сценариям, что нам нужноreact-hooks
Что делать, как инкапсулировать часть логики и использовать ее повторно — такова изначальная цель кастомных хуков.
Как разработать индивидуальный крючок, спецификация дизайна
Логика + Компоненты
крючки сосредоточиться налогическое мультиплексирование, это наш проект не только на уровне повторного использования компонентов. Хуки позволяют нам инкапсулировать часть общей логики. Он будет доступен из коробки, когда он нам понадобится.
Пользовательские условия, управляемые хуками
hooks
По сути функция. Выполнение функции определяет контекст выполнения самого компонента без состояния. Каждый раз, когда функция выполняется (по сути, обновление компонента), выполняется настройкаhooks
Видно, что выполнение самого компонента точно такое же, как выполнение хуков.
Такprop
Модификации,useState,useReducer
Использование — это условие обновления компонента без сохранения состояния, тогда это условие управляет выполнением хуков.
Мы используем картинку, чтобы представить вышеуказанные отношения.
Пользовательские крючки - общий шаблон
На заказ разработан намиreact-hooks
Должен быть таким длинным.
const [ xxx , ... ] = useXXX(参数A,参数B...)
Когда мы пишем пользовательские хуки, нам нужноособенный ~ особенный ~ особенныйОсновное внимание уделяетсяпройти в чем,что вернуть. Возвращается то, что нам действительно нужно. Больше похоже на фабрику, сырье обрабатывается и, наконец, возвращается к нам. как показано ниже
Пользовательские хуки - условные
Если пользовательские хуки спроектированы неправильно, например возвращают функцию, которая изменяет состояние, но не добавляет условий, это может привести к ненужному выполнению контекста и даже к циклическому выполнению рендеринга компонентов.
Например: мы пишем очень простой хук дляОтформатируйте массив для преобразования нижнего регистра в верхний..
import React , { useState } from 'react'
/* 自定义hooks 用于格式化数组将小写转成大写 */
function useFormatList(list){
return list.map(item=>{
console.log(1111)
return item.toUpperCase()
})
}
/* 父组件传过来的list = [ 'aaa' , 'bbb' , 'ccc' ] */
function index({ list }){
const [ number ,setNumber ] = useState(0)
const newList = useFormatList(list)
return <div>
<div className="list" >
{ newList.map(item=><div key={item} >{ item }</div>) }
</div>
<div className="number" >
<div>{ number }</div>
<button onClick={()=> setNumber(number + 1) } >add</button>
</div>
</div>
}
export default index
Как в вышеупомянутом вопросе, мы форматируем пропущенные от родительского компонентаlist
массив и преобразовать нижний регистр в верхний, но когда мы нажимаемadd
. В идеале массив не нужно перезаписыватьformat
, но фактическое выполнение следуетformat
. Несомненно, увеличение производительности.
Поэтому, когда мы настраиваем пользовательские хуки, мы должны добавить условную производительность.
Итак, давайте разберемся с этим так.
function useFormatList(list) {
return useMemo(() => list.map(item => {
console.log(1111)
return item.toUpperCase()
}), [])
}
Шикарное решение вышеуказанной проблемы.
Таким образом, полезные пользовательские хуки должны быть сопоставленыuseMemo ,useCallback
Ждатьapi
использовать вместе.
Пользовательские хуки в действии
Подготовка: создание демо-проекта
Для включения реальных бизнес-сценариев и пользовательскихhooks
Соединенные вместе, я использую здесьtaro-h5
построил мобильныйreact
проект. Используется для описания обычая, используемого в реальной работе.hooks
место действия.
адрес демо-проекта:Пользовательские хуки, демонстрационные проекты
В будущем будет обновлено больше пользовательских крючков, или заинтересованные студенты могут обратить внимание на этот проект или также могут поддерживать этот проект вместе.
Структура проекта
page
Папка включает отображение пользовательских хуковdemo
страница,hooks
Внутри папки находится содержимое пользовательских хуков.
Отображение результатов
каждыйlistItem
Запишите эффект отображения каждого выполненного пользовательского хука, а также других один за другим.hooks
.我们接下来看看hooks具体实现。
Фактический бой 1: Управляйте полосой прокрутки — эффект потолка, эффект градиента —useScroll
Предыстория: Один из проектов компании h5 должен управлять эффектом градиента + высоты + потолка во время процесса прокрутки полосы прокрутки.
1 для достижения эффекта
1 Во-первых, красный блок создает эффект потолка. 2 Блок розового цвета фиксируется сверху, но имеет небольшое смещение, а также эффект постепенного перехода в прозрачность.
2 НастроитьuseScroll
Идеи дизайна
Нужно реализовать функцию:
1 Прислушайтесь к прокрутке полосы прокрутки.
2 Рассчитайте пороговое значение потолка, значение градиента, прозрачность.
3 изменитьstate
визуализировать вид.
Ну, дальше давайте использоватьhooks
для достижения вышеуказанной работы.
страница
import React from 'react'
import { View, Swiper, SwiperItem } from '@tarojs/components'
import useScroll from '../../hooks/useScroll'
import './index.less'
export default function Index() {
const [scrollOptions,domRef] = useScroll()
/* scrollOptions 保存控制透明度 ,top值 ,吸顶开关等变量 */
const { opacity, top, suctionTop } = scrollOptions
return <View style={{ position: 'static', height: '2000px' }} >
<View className='white' />
<View id='box' style={{ opacity, transform: `translateY(${top}px)` }} >
<Swiper
className='swiper'
>
<SwiperItem className='SwiperItem' >
<View className='imgae' />
</SwiperItem>
</Swiper>
</View>
<View className={suctionTop ? 'box_card suctionTop' : 'box_card'}>
<View
style={{
background: 'red',
boxShadow: '0px 15px 10px -16px #F02F0F'
}}
className='reultCard'
>
</View>
</View>
</View>
}
мы проезжаемscrollOptions
сохранить прозрачность,top
value , потолочные выключатели и другие переменные, а затем вернутьref
так какdom
Сборщик элементов. Следующий шаг — если хуки реализованы.
useScroll
export default function useScroll() {
const dom = useRef(null)
const [scrollOptions, setScrollOptions] = useState({
top: 0,
suctionTop: false,
opacity: 1
})
useEffect(() => {
const box = (dom.current)
const offsetHeight = box.offsetHeight
const radio = box.offsetHeight / 500 * 20
const handerScroll = () => {
const scrollY = window.scrollY
/* 控制透明度 */
const computerOpacty = 1 - scrollY / 160
/* 控制吸顶效果 */
const offsetTop = offsetHeight - scrollY - offsetHeight / 500 * 84
const top = 0 - scrollY / 5
setScrollOptions({
opacity: computerOpacty <= 0 ? 0 : computerOpacty,
top,
suctionTop: offsetTop < radio
})
}
document.addEventListener('scroll', handerScroll)
return function () {
document.removeEventListener('scroll', handerScroll)
}
}, [])
return [scrollOptions, dom]
}
конкретные дизайнерские идеи
1 Мы используемuseRef
чтобы получить нужный элемент
2 использоватьuseEffect
для инициализации событий привязки/отвязки
3 использоватьuseState
чтобы сохранить состояние для изменения и уведомить компонент для рендеринга.
Мы можем игнорировать процесс вычисления в середине и, наконец, добиться ожидаемого эффекта.
Об оптимизации производительности
Вот пункт оптимизации производительности, который не имеет никакого отношения к самим хукам, мы меняемtop
значение, попробуйте использовать изменениеtransform
Вместо прямого изменения верхнего значения значение Y по следующим причинам
1 transform
GPU-ускорениеCSS3
свойства, с точки зрения удобства выполнения лучше, чем прямое изменениеtop
стоимость.
2 дюймаios
конец, фиксированное позиционирование часто меняетсяtop
значение, происходит совместимость экрана-заставки.
Фактический бой 2: Состояние формы управления-useFormChange
Предыстория: Но мы сталкиваемся с такими сценариями, как поиск в заголовке списка, отправка формы и т. д., и нам нужно изменить каждый из них один за другим.formItem
изvalue
Связывать события одно за другим сложнее, поэтому в обычной разработке мы используем хук для унифицированного управления состоянием формы.
1 Добиться эффекта
Демонстрационный эффект выглядит следующим образом
получить форму
Сброс форма
2 НастроитьuseFormChange
Идеи дизайна
Нужно реализовать функцию
1 Управляет значением каждой формы. 2 С отправкой формы получите всю функцию данных формы. 3 Нажмите «Сброс», чтобы сбросить функцию формы.
страница
import useFormChange from '../../hooks/useFormChange'
import './index.less'
const selector = ['嘿嘿', '哈哈', '嘻嘻']
function index() {
const [formData, setFormItem, reset] = useFormChange()
const {
name,
options,
select
} = formData
return <View className='formbox' >
<View className='des' >文本框</View>
<AtInput name='value1' title='名称' type='text' placeholder='请输入名称' value={name} onChange={(value) => setFormItem('name', value)}
/>
<View className='des' >单选</View>
<AtRadio
options={[
{ label: '单选项一', value: 'option1' },
{ label: '单选项二', value: 'option2' },
]}
value={options}
onClick={(value) => setFormItem('options', value)}
/>
<View className='des' >下拉框</View>
<Picker mode='selector' range={selector} onChange={(e) => setFormItem('select',selector[e.detail.value])} >
<AtList>
<AtListItem
title='当前选择'
extraText={select}
/>
</AtList>
</Picker>
<View className='btns' >
<AtButton type='primary' onClick={() => console.log(formData)} >提交</AtButton>
<AtButton className='reset' onClick={reset} >重置</AtButton>
</View>
</View>
}
useFormChange
/* 表单/表头搜素hooks */
function useFormChange() {
const formData = useRef({})
const [, forceUpdate] = useState(null)
const handerForm = useMemo(()=>{
/* 改变表单单元项 */
const setFormItem = (keys, value) => {
const form = formData.current
form[keys] = value
forceUpdate(value)
}
/* 重置表单 */
const resetForm = () => {
const current = formData.current
for (let name in current) {
current[name] = ''
}
forceUpdate('')
}
return [ setFormItem ,resetForm ]
},[])
return [ formData.current ,...handerForm ]
}
Конкретный анализ процесса:
1 Мы используемuseRef
кэшировать данные для всей формы.
2 использоватьuseState
Выполнение обновления в одиночку не требует чтения состояния useState.
3 Объявите метод формы сбросаresetForm
, установить элемент ячейки формыchange
метод,
Здесь стоит упомянуть вопрос о том, чтозачем использоватьuseRef
кэшироватьformData
данные вместо прямого использованияuseState
.
Причина первая
Мы все знаем, когда использоватьuseMemo,useCallback
ЖдатьAPI
, если цитироватьuseState
, необходимо передать значение useState как deps, иначе из-заuseMemo,useCallback
кэшированныйuseState
старое значение, не может получить новое значение, ноuseRef
разные, могут быть прочитаны/изменены напрямуюuseRef
кэшированные данные внутри.
Причина втораяСинхронизироватьuseState
useState
за одно использованиеuseState
Изменятьstate
значение, мы не можем получить последнююstate
, следующее демо
function index(){
const [ number , setNumber ] = useState(0)
const changeState = ()=>{
setNumber(number+1)
console.log(number) //组件更新 -> 打印number为0 -> 并没有获取到最新的值
}
return <View>
<Button onClick={changeState} >点击改变state</Button>
</View>
}
мы можем использоватьuseRef
а такжеuseState
добиться синхронизации
function index(){
const number = useRef(0)
const [ , forceUpdate ] = useState(0)
const changeState = ()=>{
number.current++
forceUpdate(number.current)
console.log(number.current) //打印值为1,组件更新,值改变
}
return <View>
<Button onClick={changeState} >点击改变state</Button>
</View>
}
оптимизация производительностииспользоватьuseMemo
оптимизироватьsetFormItem ,resetForm
метод, чтобы избежать повторных объявлений, что приводит к снижению производительности.
Фактический бой третий: контрольная таблица/список - useTableRequset
Предыстория: Когда нам нужно управлять таблицей/списком с разбиением на страницы и условиями запроса.
1 Добиться эффекта
1 Унифицированное управление табличными данными, включая список, номер страницы, общее количество страниц и другую информацию 2 Реализуйте переключение номеров страниц и обновите данные.
2 НастроитьuseTableRequset
Идеи дизайна
1 нам нужноstate
Чтобы сохранить данные списка, общее количество страниц, текущую страницу и другую информацию.
2 Необходимо предоставить метод для изменения данных пейджинга и повторного запроса данных.
Давайте посмотрим на конкретный план реализации.
function getList(payload){
const query = formateQuery(payload)
return fetch('http://127.0.0.1:7001/page/tag/list?'+ query ).then(res => res.json())
}
export default function index(){
/* 控制表格查询条件 */
const [ query , setQuery ] = useState({})
const [tableData, handerChange] = useTableRequest(query,getList)
const { page ,pageSize,totalCount ,list } = tableData
return <View className='index' >
<View className='table' >
<View className='table_head' >
<View className='col' >技术名称</View>
<View className='col' >icon</View>
<View className='col' >创建时间</View>
</View>
<View className='table_body' >
{
list.map(item=><View className='table_row' key={item.id} >
<View className='col' >{ item.name }</View>
<View className='col' > <Image className='col col_image' src={Icons[item.icon].default} /></View>
<View className='col' >{ item.createdAt.slice(0,10) }</View>
</View>)
}
</View>
</View>
<AtPagination
total={Number(totalCount)}
icon
pageSize={Number(pageSize)}
onPageChange={(mes)=>handerChange({ page:mes.current })}
current={Number(page)}
></AtPagination>
</View>
}
useTableRequset
/* table 数据更新 hooks */
export default function useTableRequset(query, api) {
/* 是否是第一次请求 */
const fisrtRequest = useRef(false)
/* 保存分页信息 */
const [pageOptions, setPageOptions] = useState({
page: 1,
pageSize: 3
})
/* 保存表格数据 */
const [tableData, setTableData] = useState({
list: [],
totalCount: 0,
pageSize: 3,
page:1,
})
/* 请求数据 ,数据处理逻辑根后端协调着来 */
const getList = useMemo(() => {
return async payload => {
if (!api) return
const data = await api(payload || {...query, ...pageOptions})
if (data.code == 0) {
setTableData(data.data)
fisrtRequest.current = true
}
}
}, [])
/* 改变分页,重新请求数据 */
useEffect(() => {
fisrtRequest.current && getList({
...query,
...pageOptions
})
}, [pageOptions])
/* 改变查询条件。重新请求数据 */
useEffect(() => {
getList({
...query,
...pageOptions,
page: 1
})
}, [query])
/* 处理分页逻辑 */
const handerChange = useMemo(() => (options) => setPageOptions({...options }), [])
return [tableData, handerChange, getList]
}
demo
useRef
useState
3 использовать дваuseEffect
Обработайте их отдельно.Для изменения условия запроса списка или изменения состояния подкачки запустите хук побочного эффекта и повторно запросите данные.Чтобы различить эффект двух изменений состояния, на самом деле можно использовать одно из них.effect
обрабатывать.
4 Предоставьте два метода, а именно запрос данных и логику обработки пейджинга.
оптимизация производительности
1 Мы используемuseRef
Чтобы кэшировать, является ли это первым рендерингом, цель состоит в том, чтобы при инициализации дваuseEffect
Хуки будут выполняться, чтобы избежать повторных запросов данных.
2 Для запроса данных и обработки логики пейджинга, чтобы избежать повторных объявлений, мы используемuseMemo
быть оптимизированным.
Стоит отметить, что логика постобработки данных запроса инкапсулируется вместе с кастомными хуками, в реальных проектах нужно формулировать свои хуки по согласованному с бэкендом формату возврата данных.
Фактический бой четыре: контролировать эффект перетаскивания-useDrapDrop
фон: сtransform
а такжеhooks
Эффект перетаскивания достигается без настройки позиционирования.
1 Добиться эффекта
Независимостьhooks
связать независимымdom
элемент, чтобы он мог достичь эффекта свободного перетаскивания.
2 useDrapDrop
Конкретные идеи реализации
Функции, которые необходимо реализовать:
1 по индивидуальному заказуhooks
Вычисленные значения x,y получаются путем сложенияtransform
изtranslate
Свойство задает текущий вычисляемыйx,y
Для достижения эффекта перетаскивания.
2 Настроитьhooks
Вы можете схватить текущийdom
Элемент контейнер.
страница
export default function index (){
const [ style1 , dropRef ]= useDrapDrop()
const [style2,dropRef2] = useDrapDrop()
return <View className='index'>
<View
className='drop1'
ref={dropRef}
style={{transform:`translate(${style1.x}px, ${style1.y}px)`}}
>drop1</View>
<View
className='drop2'
ref={dropRef2}
style={{transform:`translate(${style2.x}px, ${style2.y}px)`}}
>drop2</View>
<View
className='drop3'
>drop3</View>
</View>
}
будь осторожен:мы бесполезны,left
,а такжеtop
изменить положение,css3
изtransform
Можно браузер, чтобы избежать перегруппировки и с обратным холодильником, изменение ориентации сильнее, чем оптимизация прямой производительности сверху, левое значение.
Как мы рассмотрим, что среда симуляции - это мобильный терминал H5, поэтому используйтеwebview
изtouchstart , touchmove ,ontouchend
события для имитации.
Основной код -useDrapDrop
/* 移动端 -> 拖拽自定义效果(不使用定位) */
function useDrapDrop() {
/* 保存上次移动位置 */
const lastOffset = useRef({
x:0, /* 当前x 值 */
y:0, /* 当前y 值 */
X:0, /* 上一次保存X值 */
Y:0, /* 上一次保存Y值 */
})
/* 获取当前的元素实例 */
const currentDom = useRef(null)
/* 更新位置 */
const [, foceUpdate] = useState({})
/* 监听开始/移动事件 */
const [ ontouchstart ,ontouchmove ,ontouchend ] = useMemo(()=>{
/* 保存left right信息 */
const currentOffset = {}
/* 开始滑动 */
const touchstart = function (e) {
const targetTouche = e.targetTouches[0]
currentOffset.X = targetTouche.clientX
currentOffset.Y = targetTouche.clientY
}
/* 滑动中 */
const touchmove = function (e){
const targetT = e.targetTouches[0]
let x =lastOffset.current.X + targetT.clientX - currentOffset.X
let y =lastOffset.current.Y + targetT.clientY - currentOffset.Y
lastOffset.current.x = x
lastOffset.current.y = y
foceUpdate({
x,y
})
}
/* 监听滑动停止事件 */
const touchend = () => {
lastOffset.current.X = lastOffset.current.x
lastOffset.current.Y = lastOffset.current.y
}
return [ touchstart , touchmove ,touchend]
},[])
useLayoutEffect(()=>{
const dom = currentDom.current
dom.ontouchstart = ontouchstart
dom.ontouchmove = ontouchmove
dom.ontouchend = ontouchend
},[])
return [ { x:lastOffset.current.x,y:lastOffset.current.y } , currentDom]
}
Конкретные дизайнерские идеи:
1 Для эффекта перетаскивания нам нужно получить его в реальном времениdom
информация о позиции элемента, поэтому нам нуженuseRef
чтобы захватить элемент dom.
2 Так как мы используемtransfrom
Измените местоположение, поэтому вам нужно сохранить текущее местоположение и последний разtransform
Расположение, поэтому мы используем useRef для кэширования местоположения.
3 Мы проходимuseRef
Изменятьx,y
значение, но новая позиция должна быть отображена, поэтому мы используемuseState
специально для создания обновлений компонентов.
4 При инициализации нам нужно привязать события к текущему элементу, т.к. нам может понадобиться именно информация о расположении элементов во время инициализации, поэтому мы используемuseLayoutEffect
крючок, чтобы связатьtouchstart , touchmove ,ontouchend
и т.д. события.
Суммировать
Выше приведено мое резюме о пользовательских хуках реагирования и некоторых практических сценариях применения.В нашем проекте 80% сценариев списка форм могут быть решены с помощью вышеуказанных хуков.
Это поверхностно на бумаге, и я ничего не знаю об этом., Действительно хорошо играть, играть крючки, это процесс, который накапливается с течением времени, как разработать крючок, который соответствует бизнес-сценарию, требует от нас постоянной борьбы и подведения итогов.
В конце концов, если вы считаете, что это неплохо, ставьте лайк + следите за волной и продолжайте делиться техническими статьями.
Общедоступный номер: Front-end Sharing