1. Что такое реактивные крючки?
**React-hooks — это новый API-интерфейс реакции после реакции 16.8. Цель состоит в том, чтобы повысить возможность повторного использования и логику кода, а также восполнить дефект, заключающийся в том, что компоненты без состояния не имеют жизненного цикла и состояния управления данными. Автор считает, что идея и первоначальное намерение реактивных хуков заключается в гранулировании и унификации компонентов для формирования независимой среды рендеринга, сокращения количества рендерингов и оптимизации производительности.
использоватьОбратный звонок✅
использоватьконтекст✅
использоватьЭффект✅
использоватьLayoutEffect ✅
использоватьMemo ✅
использоватьредуктор✅
использоватьСсылка✅
использовать состояние✅ Выше приведены основные API реактивных хуков.Далее я поделюсь с вами использованием этих API и мерами предосторожности при их использовании.
2. Зачем использовать хуки
Почему мы используем хуки реакции?Во-первых, состояние с сохранением состояния традиционного объявления класса имеет это важное преимущество.
1 react-hooks могут сделать наш код более логичным и могут извлекать общедоступные методы и общедоступные компоненты.
2 Идея реактивных хуков ближе к функциональному программированию. Используйте объявление функции вместо объявления класса.Хотя класс также является синтаксическим сахаром конструктора es6, хуки реакции написаны с большим количеством функций и компонентов, что, несомненно, повышает эффективность разработки кода (нет необходимости писать цикл объявления и жизнь, как класс компоненты объявления, функция периодического рендеринга и т. д.)
3 react-hooks могут разбивать огромные компоненты класса на множество мелких компонентов, useMemo и другие методы позволяют компонентам или переменным разрабатывать подходящее для себя независимое пространство рендеринга, что может повысить производительность и в определенной степени уменьшить количество рендерингов. Здесь стоит упомянуть, что если компонент рендеринга, отвечающий за запрос данных ➡️ view update, написан в react-hooks, он будет иметь лучший эффект с отличными библиотеками с открытым исходным кодом, такими как immutable (здесь особое внимание ⚠️, если неразборчивое использование хуков не только не улучшит производительность, но повлияет на производительность и вызовет всевозможные неожиданные проблемы).
3. Как использовать хуки
Далее я обсужу с вами основные API реактивных хуков, конкретное использование
1 хранилище данных useState, отправка обновлений
Появление useState позволяет реагирующим компонентам без состояния иметь собственное состояние, подобное компонентам с состоянием. Параметром useState может быть конкретное значение или функция для оценки сложной логики. Функция возвращает исходное значение, а usestate возвращает массив, первый элемент массива используется для чтения значения состояния в это время, второй элемент — это функция диспетчеризации обновления данных, рендеринга компонента, а параметр функции — это значение, которое необходимо обновить. useState и useReduce — это хуки, которые могут инициировать повторный рендеринг компонента.При использовании useState следует обратить особое внимание на то, что когда useState диспетчеризирует выполнение функции обновления, весь компонент функции будет выполнен один раз от начала до конца. , поэтому он должен взаимодействовать с useMemo, usecallback и т. д. Это одна из причин, по которой злоупотребление хуками может иметь негативные последствия. Следующий код является основным применением usestate
const DemoState = (props) => {
/* number为此时state读取值 ,setNumber为派发更新的函数 */
let [number, setNumber] = useState(0) /* 0为初始值 */
return (<div>
<span>{ number }</span>
<button onClick={ ()=> {
setNumber(number+1)
console.log(number) /* 这里的number是不能够即使改变的 */
} } ></button>
</div>)
}
Простой пример выше иллюстрирует useState, но когда мы вызываем функцию обновления, значение состояния не может быть изменено немедленно, только при следующем выполнении контекста значение состояния изменится соответствующим образом.
const a =1
const DemoState = (props) => {
/* useState 第一个参数如果是函数 则处理复杂的逻辑 ,返回值为初始值 */
let [number, setNumber] = useState(()=>{
// number
return a===1 ? 1 : 2
}) /* 1为初始值 */
return (<div>
<span>{ number }</span>
<button onClick={ ()=>setNumber(number+1) } ></button>
</div>)
}
2 хук побочного эффекта обновления компонента useEffect
Если вы хотите произвести какие-то манипуляции с домом и запросить данные в функциональном компоненте, когда компонент смонтирован, дом визуализируется, тогда лучше всего использовать useEffect.Если нам нужно запросить данные при первом отображении компонента, то useEffect может действовать как componentDidMount в компоненте класса,Тем не менее, особенно важно отметить, что если вы не добавите уточнения к выполнению useEffect, каждое обновление функционального компонента будет запускать эффект, а это означает, что каждое обновление состояния или обновление реквизита будет запускать выполнение useEffect, а эффект в это время действует как componentDidUpdate и componentwillreceiveprops, поэтому разумно использовать useEffect для добавления условия ограничения выполнения к эффекту, то есть второй параметр useEffect, здесь квалификационное условие, или можно сказать чтобы быть некоторыми данными записи, собранными последним обновлением useeffect Измененная память, в новом раунде обновления, useeffect сравнит предыдущее значение памяти с текущим значением.Если есть изменение, будет выполнен новый раунд функции побочного эффекта useEffect , Второй параметр useEffect — это массив для сбора нескольких ограничений.
/* 模拟数据交互 */
function getUserInfo(a){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve({
name:a,
age:16,
})
},500)
})
}
const Demo = ({ a }) => {
const [ userMessage , setUserMessage ] :any= useState({})
const div= useRef()
const [number, setNumber] = useState(0)
/* 模拟事件监听处理函数 */
const handleResize =()=>{}
/* useEffect使用 ,这里如果不加限制 ,会是函数重复执行,陷入死循环*/
useEffect(()=>{
/* 请求数据 */
getUserInfo(a).then(res=>{
setUserMessage(res)
})
/* 操作dom */
console.log(div.current) /* div */
/* 事件监听等 */
window.addEventListener('resize', handleResize)
/* 只有当props->a和state->number改变的时候 ,useEffect副作用函数重新执行 ,如果此时数组为空[],证明函数只有在初始化的时候执行一次相当于componentDidMount */
},[ a ,number ])
return (<div ref={div} >
<span>{ userMessage.name }</span>
<span>{ userMessage.age }</span>
<div onClick={ ()=> setNumber(1) } >{ number }</div>
</div>)
}
Если нам нужно выполнить некоторые операции, такие как отмена мониторинга dom и очистка таймеров на этапе уничтожения компонента, то мы можем вернуть функцию в конце первого параметра функции useEffect, чтобы очистить эти побочные эффекты. Эквивалент компонента componentWillUnmount.
const Demo = ({ a }) => {
/* 模拟事件监听处理函数 */
const handleResize =()=>{}
useEffect(()=>{
/* 定时器 延时器等 */
const timer = setInterval(()=>console.log(666),1000)
/* 事件监听 */
window.addEventListener('resize', handleResize)
/* 此函数用于清除副作用 */
return function(){
clearInterval(timer)
window.removeEventListener('resize', handleResize)
}
},[ a ])
return (<div >
</div>)
}
Асинхронный асинхронный эффект?
Напомните всем, что useEffect не может напрямую использовать async await Синтаксический сахар
/* 错误用法 ,effect不支持直接 async await 装饰的 */
useEffect(async ()=>{
/* 请求数据 */
const res = await getUserInfo(payload)
},[ a ,number ])
Если мы хотим использовать асинхронный эффект, мы можем обернуть эффект
const asyncEffect = (callback, deps)=>{
useEffect(()=>{
callback()
},deps)
}
3useLayoutEffect визуализирует useEffect перед обновлением.
Последовательность выполнения обновления и монтирования компонента useEffect завершена -> отрисовка домена браузера завершена -> выполнить обратный вызов useEffect.
Порядок выполнения обновления и монтирования компонента useLayoutEffect завершен -> выполнить обратный вызов useLayoutEffect -> отрисовка домена браузера завершена
Таким образом, код useLayoutEffect может заблокировать отрисовку браузера. Если мы повторно запросим данные в useEffect, во время рендеринга представления, это обязательно вызовет мерцание эффекта экрана. Если используется useLayoutEffect, код функции обратного вызова будет блокировать рисование браузера, поэтому это определенно приведет к зависанию экрана и другим эффектам, поэтому использование useLayoutEffect или useEffect зависит от фактической ситуации в проекте.В большинстве случаев useEffect может быть удовлетворен.
const DemoUseLayoutEffect = () => {
const target = useRef()
useLayoutEffect(() => {
/*我们需要在dom绘制之前,移动dom到制定位置*/
const { x ,y } = getPositon() /* 获取要移动的 x,y坐标 */
animate(target.current,{ x,y })
}, []);
return (
<div >
<span ref={ target } className="animate"></span>
</div>
)
}
4 useRef для получения элементов и кэширования данных.
Как и традиционный компонент класса ref, react-hooks также предоставляет метод useRef для получения элемента.У него есть параметр, который можно использовать в качестве начального значения кэшированных данных.Возвращаемое значение может быть помечено ссылкой на элемент dom, и отмеченный узел элемента может быть получен.
const DemoUseRef = ()=>{
const dom= useRef(null)
const handerSubmit = ()=>{
/* <div >表单组件</div> dom 节点 */
console.log(dom.current)
}
return <div>
{/* ref 标记当前dom节点 */}
<div ref={dom} >表单组件</div>
<button onClick={()=>handerSubmit()} >提交</button>
</div>
}
Расширенное использование данных кэша
Конечно, useRef также играет очень важную роль для кэширования данных, мы знаем, что usestate и useReducer могут сохранить текущий источник данных, но если они обновят выполнение функции источника данных, это обязательно приведет весь компонент из нового исполнения в рендеринг.Если переменная объявлена внутри компонента, следующее обновление также будет сброшено.Если мы хотим сохранить данные спокойно, не запуская обновление функции, то useRef — отличный выбор.
** const currenRef = useRef(InitialData)
получить currentRef.current изменить currentRef.current = новое значение
Первый параметр useRef можно использовать для инициализации и сохранения данных, которые можно получить для текущего свойства.Конечно, мы также можем назначить новый источник данных текущему.
Давайте посмотрим на умное использование useRef через исходный код react-redux.(После выпуска react-hooks, react-redux использовала react-hooks для обновления основных модулей Provide и connectAdvanced. Видно, что react-hooks имеет определенные преимущества в ограничении обновлений данных и высокоуровневых компонентов, а его исходный код широко используется useMemo для оценки данных
/* 这里用到的useRef没有一个是绑定在dom元素上的,都是做数据缓存用的 */
/* react-redux 用userRef 来缓存 merge之后的 props */
const lastChildProps = useRef()
// lastWrapperProps 用 useRef 来存放组件真正的 props信息
const lastWrapperProps = useRef(wrapperProps)
//是否储存props是否处于正在更新状态
const renderIsScheduled = useRef(false)
Это кеширование данных с помощью useRef в react-redux, так что как его обновить посмотрим дальше
//获取包装的props
function captureWrapperProps(
lastWrapperProps,
lastChildProps,
renderIsScheduled,
wrapperProps,
actualChildProps,
childPropsFromStoreUpdate,
notifyNestedSubs
) {
//我们要捕获包装props和子props,以便稍后进行比较
lastWrapperProps.current = wrapperProps //子props
lastChildProps.current = actualChildProps //经过 merge props 之后形成的 prop
renderIsScheduled.current = false
}
Из приведенного выше видно, что react-redux использует метод переназначения для изменения кэшированного источника данных, чтобы избежать ненужных обновлений данных.Если для хранения данных используется useState, это неизбежно приведет к повторному рендерингу компонента.Поэтому для решения этой проблемы используется useRef.Что касается того, как реализован исходный код react-redux,Здесь мы можем сослаться на другую статью автора, анализ исходного кода react-redux..
5 useContext свободный доступ к контексту
Мы можем использовать useContext, чтобы получить значение контекста, переданное родительским компонентом. Это текущее значение является значением, установленным ближайшим поставщиком родительского компонента. Параметр useContext обычно вводится методом createContext или может быть передан из контекста родительского контекста. (параметр является контекстом). useContext может заменить context.Consumer, чтобы получить значение, хранящееся в Provider.
/* 用useContext方式 */
const DemoContext = ()=> {
const value:any = useContext(Context)
/* my name is alien */
return <div> my name is { value.name }</div>
}
/* 用Context.Consumer 方式 */
const DemoContext1 = ()=>{
return <Context.Consumer>
{/* my name is alien */}
{ (value)=> <div> my name is { value.name }</div> }
</Context.Consumer>
}
export default ()=>{
return <div>
<Context.Provider value={{ name:'alien' , age:18 }} >
<DemoContext />
<DemoContext1 />
</Context.Provider>
</div>
}
6 useReducer redux в компонентах без состояния
useReducer - это функциональный API, похожий на редукс, предоставляемый реактивными хуками, которые могут работать в компонентах без состояния.Что касается того, может ли он заменить редукс реагировать-редукс, мое личное мнение - нет, редукс может показать преимущества в сложной логике, а идея режима промежуточного программного обеспечения Redux Это также очень хорошо. Мы можем улучшить диспетчеризацию с помощью промежуточного программного обеспечения. Redux-thunk, redux-sage, redux-action и redux-promise — все это относительно хорошее промежуточное программное обеспечение, которое может программировать синхронные редукторы в асинхронные редукторы. Первый параметр, принимаемый useReducer, — это функция, мы можем думать о ней как о редюсере, параметры редьюсера — это состояние и действие в обычном редюсере, возвращают измененное состояние, второй параметр useReducer возвращает массив для начального значения. состояния, первый элемент массива — это значение состояния после обновления, а второй параметр — функция отправки, которая отправляет обновление.Запуск диспетчеризации вызовет обновление компонента.Здесь одним из компонентов, который может запросить повторную визуализацию компонента, является функция обновления диспетчеризации useState, а другим — диспетчеризация в useReducer.
const DemoUseReducer = ()=>{
/* number为更新后的state值, dispatchNumbner 为当前的派发函数 */
const [ number , dispatchNumbner ] = useReducer((state,action)=>{
const { payload , name } = action
/* return的值为新的state */
switch(name){
case 'add':
return state + 1
case 'sub':
return state - 1
case 'reset':
return payload
}
return state
},0)
return <div>
当前值:{ number }
{ /* 派发更新 */ }
<button onClick={()=>dispatchNumbner({ name:'add' })} >增加</button>
<button onClick={()=>dispatchNumbner({ name:'sub' })} >减少</button>
<button onClick={()=>dispatchNumbner({ name:'reset' ,payload:666 })} >赋值</button>
{ /* 把dispatch 和 state 传递给子组件 */ }
<MyChildren dispatch={ dispatchNumbner } State={{ number }} />
</div>
}
Конечно, реальная бизнес-логика может быть более сложной, и нам нужно выполнять более сложные логические операции в редюсере.
7 useMemo маленькая и ароматная оптимизация производительности
Я думаю, что useMemo — один из самых изысканных хуков в дизайне React, преимущество которого в том, что он может формировать независимое пространство рендеринга и может обновлять компоненты и переменные в соответствии с согласованными правилами. Условия рендеринга зависят от второго параметра deps. Мы знаем, что обновление компонента без состояния — это обновление от начала до конца. Если вы хотите повторно отобразить часть представления вместо всего компонента, useMemo — лучшее решение, позволяющее избежать ненужных обновлений и ненужного выполнения контекста. Прежде чем представить useMemo, давайте поговорим о memo, мы знаем, что компоненты, объявленные классом, могут использовать componentShouldUpdate для ограничения количества обновлений, тогда memo — это ShouldUpdate компонентов без состояния, а useMemo, о котором мы собираемся поговорить сегодня, — это более тонкий модуль ShouldUpdate. ,
Давайте сначала посмотрим на memo.Функция memo объединяет чистые компоненты pureComponent и функции componentShouldUpdate.Он сравнивает входящие реквизиты, а затем далее решает, какие реквизиты необходимо обновить в соответствии с возвращаемым значением второй функции.
/* memo包裹的组件,就给该组件加了限制更新的条件,是否更新取决于memo第二个参数返回的boolean值, */
const DemoMemo = connect(state =>
({ goodList: state.goodList })
)(memo(({ goodList, dispatch, }) => {
useEffect(() => {
dispatch({
name: 'goodList',
})
}, [])
return <Select placeholder={'请选择'} style={{ width: 200, marginRight: 10 }} onChange={(value) => setSeivceId(value)} >
{
goodList.map((item, index) => <Option key={index + 'asd' + item.itemId} value={item.itemId} > {item.itemName} </Option>)
}
</Select>
/* 判断之前的goodList 和新的goodList 是否相等,如果相等,
则不更新此组件 这样就可以制定属于自己的渲染约定 ,让组件只有满足预定的下才重新渲染 */
}, (pre, next) => is(pre.goodList, next.goodList)))
Концепция использования useMemo аналогична memo. Она заключается в том, чтобы определить, соответствуют ли текущим требованиям, чтобы решить, следует ли выполнять функцию обратного вызова useMemo, а второй параметр useMemo — это массив deps. Изменения параметра в массиве определяют, useMemo обновляет функцию обратного вызова. , возвращаемое значение useMemo является результатом обновления суждения. Его можно применять к элементам, компонентам или контекстам. Если есть еще один зацикленный элемент списка, то useMemo будет лучшим выбором.Далее давайте рассмотрим преимущества useMemo.
/* 用 useMemo包裹的list可以限定当且仅当list改变的时候才更新此list,这样就可以避免selectList重新循环 */
{useMemo(() => (
<div>{
selectList.map((i, v) => (
<span
className={style.listSpan}
key={v} >
{i.patentName}
</span>
))}
</div>
), [selectList])}
1 useMemo может уменьшить ненужные циклы и уменьшить ненужный рендеринг
useMemo(() => (
<Modal
width={'70%'}
visible={listshow}
footer={[
<Button key="back" >取消</Button>,
<Button
key="submit"
type="primary"
>
确定
</Button>
]}
>
{ /* 减少了PatentTable组件的渲染 */ }
<PatentTable
getList={getList}
selectList={selectList}
cacheSelectList={cacheSelectList}
setCacheSelectList={setCacheSelectList} />
</Modal>
), [listshow, cacheSelectList])
2 useMemo может уменьшить количество отрисовок дочерних компонентов
const DemoUseMemo=()=>{
/* 用useMemo 包裹之后的log函数可以避免了每次组件更新再重新声明 ,可以限制上下文的执行 */
const newLog = useMemo(()=>{
const log =()=>{
console.log(6666)
}
return log
},[])
return <div onClick={()=>newLog()} ></div>
}
3 useMemo заставляет функцию запускаться только при изменении зависимости, что позволяет избежать большого количества ненужных накладных расходов (обратите внимание, здесь ⚠️⚠️⚠️ заключается в том, что если контекст, обернутый useMemo, формирует независимое замыкание, он будет кэшироваться до того, как не будет соответствующего обновления условиях, значение состояния после обновления получить невозможно, как показано ниже 👇⬇️)
const DemoUseMemo=()=>{
const [ number ,setNumber ] = useState(0)
const newLog = useMemo(()=>{
const log =()=>{
/* 点击span之后 打印出来的number 不是实时更新的number值 */
console.log(number)
}
return log
/* [] 没有 number */
},[])
return <div>
<div onClick={()=>newLog()} >打印</div>
<span onClick={ ()=> setNumber( number + 1 ) } >增加</span>
</div>
}
useMemo очень хорош. После переписывания react-redux с реактивными хуками он использует много сценариев useMemo. Я проанализирую два для вас.
useMemo — это то же самое, что и свойство store didStoreComeFromProps contextValue, чтобы определить, необходимо ли сбросить и обновить подписку подписчика. Я не буду объяснять здесь react-redux. Заинтересованные студенты могут посмотреть исходный код react-redux, чтобы узнать, как использовать useMemo
const [subscription, notifyNestedSubs] = useMemo(() => {
if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY
const subscription = new Subscription(
store,
didStoreComeFromProps ? null : contextValue.subscription // old
)
const notifyNestedSubs = subscription.notifyNestedSubs.bind(
subscription
)
return [subscription, notifyNestedSubs]
}, [store, didStoreComeFromProps, contextValue])
react-redux получает соответствующее состояние, оценивая изменения хранилища избыточности
const previousState = useMemo(() => store.getState(), [store])
Говоря здесь,Если мы применим useMemo для разумной детализации наших компонентов в соответствии с их зависимостями, это может сыграть большую роль в оптимизации компонентов.
8 useCallback useMemo версия функции обратного вызова
Параметры, полученные useMemo и useCallback, одинаковы, они выполняются после изменения их зависимостей, и оба возвращают кешированное значение. Разница в том, что useMemo возвращает результат выполнения функции, а useCallback возвращает функцию. Эта функция обратного вызова After обработки, то есть, когда родительский компонент передает функцию дочернему компоненту, поскольку он является компонентом без состояния, новая функция реквизита будет каждый раз регенерироваться, так что функция, передаваемая дочернему компоненту, будет меняться каждый раз. В это время будет срабатывать обновление подкомпонента, эти обновления не нужны, в это время мы можем обработать эту функцию через usecallback, а потом передать ее подкомпоненту в качестве реквизита.
/* 用react.memo */
const DemoChildren = React.memo((props)=>{
/* 只有初始化的时候打印了 子组件更新 */
console.log('子组件更新')
useEffect(()=>{
props.getInfo('子组件')
},[])
return <div>子组件</div>
})
const DemoUseCallback=({ id })=>{
const [number, setNumber] = useState(1)
/* 此时usecallback的第一参数 (sonName)=>{ console.log(sonName) }
经过处理赋值给 getInfo */
const getInfo = useCallback((sonName)=>{
console.log(sonName)
},[id])
return <div>
{/* 点击按钮触发父组件更新 ,但是子组件没有更新 */}
<button onClick={ ()=>setNumber(number+1) } >增加</button>
<DemoChildren getInfo={getInfo} />
</div>
}
Здесь следует напомнить, что useCallback должен взаимодействовать с react.memo pureComponent, иначе это не только не улучшит производительность, но и может снизить производительность
4 Резюме
Рождение реактивных хуков не означает, что они могут полностью заменить компоненты, объявленные классом.Для компонентов с более сложным бизнесом классовый компонент по-прежнему является первым выбором, но мы можем разобрать компонент класса на функциональные компоненты. к бизнес-требованиям, какие из них отвечают за логическое взаимодействие, которое требует динамического рендеринга, а затем кооперируются с API-интерфейсами, такими как usememo, для повышения производительности. Использование react-hooks также имеет некоторые ограничения, например, его нельзя размещать в операторе управления потоком, а также есть определенные требования к контексту выполнения. В целом, react-hooks по-прежнему очень хорош, и его стоит изучить и изучить.
Сканируйте код в WeChat, подписывайтесь на официальный аккаунт и регулярно делитесь техническими статьями.